Może niezbyt eleganckie rozwiązanie, ale działa. Przedstawione rozwiązanie jest oparte na Spring Web Flow + JSF 2.0.
Na wejsciu do strony logowania generowany jest kod html reCaptcha. Tutaj fragment konfiguracji flow’a:
1 2 3 4 5 6 7 | <view-state id="login"> <on-render> <evaluate expression="reCaptcha.createRecaptchaHtml(null,null)" result="flowScope.captcha"/> </on-render> <transition on="login"/> <transition on="resetpass" to="resetpassword"/> </view-state> |
Tworzymy beana springowego dla reCaptcha’y:
1 2 3 4 5 6 7 | <bean id="reCaptcha" class="net.tanesha.recaptcha.ReCaptchaImpl"> <property name="privateKey" value="PRIVATE_KEY" /> <property name="publicKey" value="PUBLIC_KEY" /> <property name="includeNoscript" value="false" /> <-- po ssl --> <property name="recaptchaServer" value="https://api-secure.recaptcha.net" /> </bean> |
Formularz logowania – login.xhtml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | <form id="jf" name="f" action="#{request.contextPath}/j_spring_security_check" method="post"> <table> <tbody> <tr> <td colspan="3">Logowanie </td> </tr> <tr> <td class="fLabel" width="100"> <h:outputText value="Login" /> </td> <td> <input type="text" name="j_username" value="" class="logininput"/> </td> <td></td> </tr> <tr> <td class="fLabel"> <h:outputText value="Password" /> </td> <td> <input type="password" name="j_password" class="logininput"/> </td> <td /> </tr> <tr> <td class="fLabel">Captcha</td> <td class="captcha"> <!-- blackgloss theme --> <script type="text/javascript"> var RecaptchaOptions = {theme : 'blackglass'}; </script> <h:outputText escape="false" value="#{captcha}"/> </td> <td></td> </tr> <tr> <td class="fLabel"></td> <td colspan="2"> <a href="#" onclick="jQuery('#jf').submit()">Login</a> </td> <td></td> </tr> </tbody> </table> </form> |
Spring security config. Tutaj właściwie najważniejsze, czyli dodanie dodatkowego filtra “loginFilter”, w którym będzie sprawdzana poprawność wprowadzonego tekstu z captcha’y.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <security:http auto-config="true" access-denied-page="/app/login?access_denied=1" use-expressions="true" > <security:form-login authentication-success-handler-ref="myAuthenticationSuccessHandler" login-page="/app/login?login=1" authentication-failure-url="/app/login?login_error=1" /> <security:logout logout-success-url="/app/login" logout-url="/logout=1" /> <!-- check captcha --> <security:custom-filter before="FORM_LOGIN_FILTER" ref="loginFilter" /> </security:http> <bean id="loginFilter" class="utils.LoginFilter"> <property name="privateKey" value="PUT HERE YOUR RECAPTCHA PRIVATE KEY" /> <property name="userDAO" ref="userDAO"/> </bean> <bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager"> <property name="providers"> <list> <bean class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="userDAO"/> </bean> </list> </property> </bean> <security:authentication-manager> <security:authentication-provider user-service-ref="userDAO"> <security:password-encoder hash="sha-256" base64="true" /> </security:authentication-provider> </security:authentication-manager> |
LoginFiler.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | package utils; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import model.dao.UserDAO; import net.tanesha.recaptcha.ReCaptchaImpl; import net.tanesha.recaptcha.ReCaptchaResponse; import org.apache.log4j.Logger; /** * reCaptcha Filter */ public class LoginFilter implements Filter { transient private Logger log = Logger.getLogger(getClass()); private String privateKey; private UserDAO userDAO; public void setPrivateKey(String privateKey) { this.privateKey = privateKey; } public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; } @Override public void destroy() {} @Override public void init(FilterConfig arg0) throws ServletException {} @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { String response = req.getParameter("recaptcha_response_field"); String challenge = req.getParameter("recaptcha_challenge_field"); if (challenge != null) { String remoteAddr = req.getRemoteAddr(); ReCaptchaImpl reCaptcha = new ReCaptchaImpl(); reCaptcha.setPrivateKey(privateKey); ReCaptchaResponse reCaptchaResponse = reCaptcha.checkAnswer(remoteAddr, challenge, response); boolean captchaOK = reCaptchaResponse.isValid(); userDAO.setCaptchaOK(captchaOK); if (!captchaOK) { log.debug("CAPTCHA *NOT* OK"); } else { log.debug("CAPTCHA OK"); } } chain.doFilter(req, resp); } } |
Fragment UserDAOImpl.java, gdzie sprawdzana jest captcha i przerywana jest procedura logowania, jeśli kod z obrazka się nie zgadza:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class UserDAOImpl implements UserDAO, UserDetailsService { public void setCaptchaOK(boolean captchaOK) { this.captchaOK = captchaOK; } /** * Spring Security */ @Override public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException, DataAccessException { // check captcha if(!captchaOK) { throw new UsernameNotFoundException("Invalid captcha"); } // ... } } |