Spring: Spring Security + reCaptcha

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");
		}
 
		// ... 
 
	}
}

Leave a Reply