This document describes integrating reCaptcha with CAS. 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>