This document describes integrating reCaptcha with CAS. reCaptcha is a free CAPTCHA service that helps to digitize books, newspapers and old time radio shows. This tutorial uses the recaptch4j library to access the reCaptcha service. The version of CAS used in this tutorial is 3.4.11.
login-webflow.xml
The following changes need to be made to WEB-INF/login-webflow.xml
:
Replace view-state "viewLoginForm" with:
<view-state id="viewLoginForm" view="casLoginView" model="credentials"> <binder> <binding property="username" /> <binding property="password" /> </binder> <on-entry> <set name="viewScope.commandName" value="'credentials'" /> </on-entry> <transition on="submit" bind="true" validate="true" to="realSubmit"> <evaluate expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" /> </transition> </view-state>
Replace action-state "realSubmit" with:
<action-state id="realSubmit"> <evaluate expression="authenticationViaFormAction.submit(flowRequestContext, flowScope.credentials, messageContext)" /> <transition on="warn" to="warn" /> <transition on="success" to="captchaCheck" /> <transition on="error" to="errorCount" /> </action-state>
Add action-states:
<action-state id="captchaValidate"> <evaluate expression="captchaValidateAction"/> <transition on="success" to="sendTicketGrantingTicket" /> <transition on="error" to="errorCount" /> </action-state> <action-state id="errorCount"> <evaluate expression="captchaErrorCountAction"/> <transition on="success" to="generateLoginTicket" /> </action-state>
Add decision-state:
<decision-state id="captchaCheck"> <if test="flowScope.count != null && flowScope.count >= 3" then="captchaValidate" else="sendTicketGrantingTicket"/> </decision-state>
Implement CaptchaErrorCountAction
CaptchaErrorCountAction.java
package com.acme.cas.captcha; import org.springframework.webflow.action.AbstractAction; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; public class CaptchaErrorCountAction extends AbstractAction { @Override protected Event doExecute(RequestContext context) throws Exception { int count; try { count = (Integer) context.getFlowScope().get("count"); } catch (Exception e) { count = 0; } count++; context.getFlowScope().put("count", count); return success(); } }
Implement CaptchaValidateAction
CaptchaValidateAction.java
package com.acme.cas.captcha; import javax.servlet.http.HttpServletRequest; import net.tanesha.recaptcha.ReCaptchaImpl; import net.tanesha.recaptcha.ReCaptchaResponse; import org.apache.commons.lang.StringUtils; import org.jasig.cas.web.support.WebUtils; import org.springframework.util.Assert; import org.springframework.webflow.action.AbstractAction; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; public class CaptchaValidateAction extends AbstractAction { private ReCaptchaImpl reCaptcha; public CaptchaValidateAction(ReCaptchaImpl recaptcha) { this.reCaptcha = recaptcha; Assert.notNull(this.reCaptcha, "ReCaptchaImpl required"); } @Override protected Event doExecute(RequestContext context) throws Exception { HttpServletRequest request = WebUtils.getHttpServletRequest(context); String recaptchaChallengeField = request.getParameter("recaptcha_challenge_field"); String recaptchaResponseField = request.getParameter("recaptcha_response_field"); String showCaptchaField = request.getParameter("showCaptcha"); if ((StringUtils.isNotBlank(showCaptchaField)) && !verifyCaptcha(request.getRemoteAddr(), recaptchaChallengeField, recaptchaResponseField)) { return error(); } return success(); } /** * Call recaptcha4j to verify captcha response * * @param remoteAddress ip address of the client * @param challenge the recaptcha challenge field * @param response the recaptcha response field * @return boolean indicating verification success or failure */ protected boolean verifyCaptcha(String remoteAddress, String challenge, String response) { ReCaptchaResponse reCaptchaResponse = reCaptcha.checkAnswer(remoteAddress, challenge, response); return reCaptchaResponse.isValid(); } }
Spring Configuration
Add the following file to WEB-INF/spring-configuration
:
captcha.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd" > <description> Configuration for reCaptcha </description> <context:annotation-config /> <mvc:annotation-driven /> <mvc:default-servlet-handler /> <context:property-placeholder location="classpath:/casserver.properties" /> <bean id="reCaptcha" class="net.tanesha.recaptcha.ReCaptchaImpl"> <property name="privateKey" value="${recaptcha.private.key}" /> <property name="publicKey" value="${recaptcha.public.key}" /> <property name="includeNoscript" value="false" /> </bean> <bean id="reCaptchaPublicKey" class="java.lang.String"> <constructor-arg value="${recaptcha.public.key}"/> </bean> <bean id="captchaErrorCountAction" class="com.acme.cas.captcha.CaptchaErrorCountAction"/> <bean id="captchaValidateAction" class="com.acme.cas.captcha.CaptchaValidateAction"> <constructor-arg> <ref local="reCaptcha"/> </constructor-arg> </bean> </beans>
Note the reCaptchaPublicKey is exposed in the context here as a bean for reference in the view.
Add reCaptcha keys to properties file
Add the recaptcha.private.key
and recaptcha.public.key
properties to src/main/resources/casserver.properties
.
casserver.properties
recaptcha.public.key=<your recaptcha public key> recaptcha.private.key=<your recaptcha private key>
Add reCaptcha to casLoginView.jsp
WEB-INF/view/jsp/default/ui/casLoginView.jsp
<c:if test="${not empty count && count >= 3}"> <div class="error"><p>Multiple failed login attempts detected! Please enter the letters shown in the image below to prove you are human.</p></div> <div id="captcha"></div> <!--[if lte IE 6]> <script type="text/javascript" src="https://www.google.com/recaptcha/api/challenge?k=<spring:eval expression="@reCaptchaPublicKey"/>"></script> <script type="text/javascript"> var isRunningIE6 = true; </script> <![endif]--> <script type="text/javascript" src="https://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script> <script> function showCaptcha() { if (typeof(isRunningIE6) === 'undefined') { Recaptcha.create('<spring:eval expression="@reCaptchaPublicKey"/>', "captcha", { theme: 'white', callback: Recaptcha.focus_response_field, tabindex: 2 }); } } showCaptcha(); </script> <input type="hidden" name="showCaptcha" value="true" /> </c:if>
Note the reCaptchaPublicKey bean is referenced here.
Add reCaptcha4j dependency in pom.xml
pom.xml
<dependency> <groupId>net.tanesha.recaptcha4j</groupId> <artifactId>recaptcha4j</artifactId> <version>0.0.7</version> </dependency>