Integrating reCaptcha with CAS
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. This tutorial was based on another by Axel Mendoza Pupo that covered JCAPTCHA integration.
login-webflow.xml
The following changes need to be made to WEB-INF/login-webflow.xml
:
<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>
<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>
<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>
<decision-state id="captchaCheck"> <if test="flowScope.count != null && flowScope.count >= 3" then="captchaValidate" else="sendTicketGrantingTicket"/> </decision-state>
Implement CaptchaErrorCountAction
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
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
:
<?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
.
recaptcha.public.key=<your recaptcha public key> recaptcha.private.key=<your recaptcha private key>
Add reCaptcha to 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
<dependency> <groupId>net.tanesha.recaptcha4j</groupId> <artifactId>recaptcha4j</artifactId> <version>0.0.7</version> </dependency>