Discussion:
JCAPTCHA integration with CAS
Axel Mendoza Pupo
2008-06-17 12:41:35 UTC
Permalink
to integrate JCAPTCHA with CAS some changes has need

In the login-webflow.xml:

1- Replace the action state bindAndValidate for this

<action-state id="bindAndValidate">
<action bean="authenticationViaFormAction" />
<transition on="success" to="captchaCheck" />
<transition on="error" to="errorCount" />
</action-state>

2- Replace the action state submit for this

<action-state id="submit">
<action bean="authenticationViaFormAction" method="submit" />
<transition on="warn" to="warn" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="errorCount" />
</action-state>

3-Add the new action states

<decision-state id="captchaCheck">
<if test="${flowScope.count != null &amp;&amp; flowScope.count >= 3}" then="captchaValidate" else="submit"/>
</decision-state>

<action-state id="captchaValidate">
<action bean="captchaValidateAction" />
<transition on="success" to="submit" />
<transition on="error" to="errorCount" />
</action-state>

<action-state id="errorCount">
<action bean="captchaErrorCountAction" />
<transition on="success" to="viewLoginForm" />
</action-state>

//*******************************************************
the code for the action bean CaptchaErrorCountAction

import org.jasig.cas.web.support.WebUtils;
import org.springframework.webflow.action.AbstractAction;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;

public final class CaptchaErrorCountAction extends AbstractAction {

protected Event doExecute(final RequestContext context) {
int count;
try {
count = (Integer)context.getFlowScope().get("count");
} catch (Exception e) {
count=0;
}

count++;

context.getFlowScope().put("count", count);

return success();
}

}

//*******************************************************
the code for the action bean captchaValidateAction

import org.jasig.cas.web.support.WebUtils;
import org.springframework.webflow.action.AbstractAction;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import com.octo.captcha.service.CaptchaServiceException;
import com.octo.captcha.service.image.ImageCaptchaService;

public final class CaptchaValidateAction extends AbstractAction {
private ImageCaptchaService jcaptchaService;
private String captchaValidationParameter = "_captcha_parameter";
protected Event doExecute(final RequestContext context) {
String captcha_response = context.getRequestParameters().get(captchaValidationParameter);
boolean valid = false;

if(captcha_response != null){
String id = WebUtils.getHttpServletRequest(context).getSession().getId();
if(id != null){
try {
valid = jcaptchaService.validateResponseForID(id, response).booleanValue();
} catch (CaptchaServiceException cse) {
}
}
}

if(valid){
return success();
}

return error();
}

public void setCaptchaService(JCaptchaServiceProxy captchaService) {
this.captchaService = captchaService;
}

public void setCaptchaValidationParameter(String captchaValidationParameter) {
this.captchaValidationParameter = captchaValidationParameter;
}
}
//*******************************************************

to generate the captcha image you must add a new controller to send the image in response to your image url

//*******************************************************
the code for the controller that process the image url

import com.octo.captcha.service.image.ImageCaptchaService;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import java.io.ByteArrayOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.*;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class CaptchaImageCreateController
implements Controller, InitializingBean
{
public CaptchaImageCreateController()
{
}
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception
{
byte captchaChallengeAsJpeg[] = null;
ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
String captchaId = request.getSession().getId();
java.awt.image.BufferedImage challenge = jcaptchaService.getImageChallengeForID(captchaId, request.getLocale());
JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(jpegOutputStream);
jpegEncoder.encode(challenge);
captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0L);
response.setContentType("image/jpeg");
ServletOutputStream responseOutputStream = response.getOutputStream();
responseOutputStream.write(captchaChallengeAsJpeg);
responseOutputStream.flush();
responseOutputStream.close();
return null;
}
public void setJcaptchaService(ImageCaptchaService jcaptchaService)
{
this.jcaptchaService = jcaptchaService;
}
public void afterPropertiesSet()
throws Exception
{
if(jcaptchaService == null)
throw new RuntimeException("Image captcha service wasn`t set!");
else
return;
}
private ImageCaptchaService jcaptchaService;
}
//*******************************************************

You should configure this controller in cas-servlet.xml and map to the url that the controller will process add the following to the handlerMappingC bean in the property mappings
<prop
key="/captcha.htm">
captchaImageCreateController
</prop>

/captcha.htm or whatever url that you want to set shoul be map in the web.xml by add the following
<servlet-mapping>
<servlet-name>cas</servlet-name>
<url-pattern>/captcha.htm</url-pattern>
</servlet-mapping>

The last step is the use of this url in our authentication page by add the following JSP code
<c:if test="${not empty count && count >= 3}" >
<img src="captcha.htm" >
<input name="j_captcha_response" type="text">
</c:if>

//*******************************************************
the configuration of the beans described here

<bean id="captchaErrorCountAction" class="your.path.CaptchaErrorCountAction"/>

<bean id="captchaValidateAction" class="your.path.CaptchaValidateAction"
p:captchaService-ref="captchaService"
p:captchaValidationParameter="j_captcha_response"/>

<bean id="captchaImageCreateController" class="your.path.CaptchaImageCreateController">
<property name="jcaptchaService" ref="jcaptchaService" />
</bean>

the configuration of the bean jcaptchaService is bussiness of the developer, the following is an example
<bean id="jcaptchaService" class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService"/>
Arnaud Lesueur
2008-06-18 17:09:57 UTC
Permalink
Hi Alex,

Thanks for this integration. Could you submit this in the CAS User Manual ?
To keep a better trace of your work ?

I've just look at this integration without making my own practical test. But
your count variable is in the flowScope this mean, it is linked to the user
session. So an attacker who is removing it's user session between to
tentative won't have to defeat your captcha, right ?

Or am I missing something ?

Regards,

Arnaud Lesueur
Post by Axel Mendoza Pupo
to integrate JCAPTCHA with CAS some changes has need
1- Replace the action state bindAndValidate for this
<action-state id="bindAndValidate">
<action bean="authenticationViaFormAction" />
<transition on="success" to="captchaCheck" />
<transition on="error" to="errorCount" />
</action-state>
2- Replace the action state submit for this
<action-state id="submit">
<action bean="authenticationViaFormAction" method="submit" />
<transition on="warn" to="warn" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="errorCount" />
</action-state>
3-Add the new action states
<decision-state id="captchaCheck">
<if test="${flowScope.count != null &amp;&amp; flowScope.count >= 3}"
then="captchaValidate" else="submit"/>
</decision-state>
<action-state id="captchaValidate">
<action bean="captchaValidateAction" />
<transition on="success" to="submit" />
<transition on="error" to="errorCount" />
</action-state>
<action-state id="errorCount">
<action bean="captchaErrorCountAction" />
<transition on="success" to="viewLoginForm" />
</action-state>
//*******************************************************
the code for the action bean CaptchaErrorCountAction
import org.jasig.cas.web.support.WebUtils;
import org.springframework.webflow.action.AbstractAction;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
public final class CaptchaErrorCountAction extends AbstractAction {
protected Event doExecute(final RequestContext context) {
int count;
try {
count = (Integer)context.getFlowScope().get("count");
} catch (Exception e) {
count=0;
}
count++;
context.getFlowScope().put("count", count);
return success();
}
}
//*******************************************************
the code for the action bean captchaValidateAction
import org.jasig.cas.web.support.WebUtils;
import org.springframework.webflow.action.AbstractAction;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import com.octo.captcha.service.CaptchaServiceException;
import com.octo.captcha.service.image.ImageCaptchaService;
public final class CaptchaValidateAction extends AbstractAction {
private ImageCaptchaService jcaptchaService;
private String captchaValidationParameter = "_captcha_parameter";
protected Event doExecute(final RequestContext context) {
String captcha_response =
context.getRequestParameters().get(captchaValidationParameter);
boolean valid = false;
if(captcha_response != null){
String id = WebUtils.getHttpServletRequest(context).getSession().getId();
if(id != null){
try {
valid = jcaptchaService.validateResponseForID(id,
response).booleanValue();
} catch (CaptchaServiceException cse) {
}
}
}
if(valid){
return success();
}
return error();
}
public void setCaptchaService(JCaptchaServiceProxy captchaService) {
this.captchaService = captchaService;
}
public void setCaptchaValidationParameter(String
captchaValidationParameter) {
this.captchaValidationParameter = captchaValidationParameter;
}
}
//*******************************************************
to generate the captcha image you must add a new controller to send the
image in response to your image url
//*******************************************************
the code for the controller that process the image url
import com.octo.captcha.service.image.ImageCaptchaService;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import java.io.ByteArrayOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.*;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
public class CaptchaImageCreateController
implements Controller, InitializingBean
{
public CaptchaImageCreateController()
{
}
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response)
throws Exception
{
byte captchaChallengeAsJpeg[] = null;
ByteArrayOutputStream jpegOutputStream = new
ByteArrayOutputStream();
String captchaId = request.getSession().getId();
java.awt.image.BufferedImage challenge =
jcaptchaService.getImageChallengeForID(captchaId, request.getLocale());
JPEGImageEncoder jpegEncoder =
JPEGCodec.createJPEGEncoder(jpegOutputStream);
jpegEncoder.encode(challenge);
captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0L);
response.setContentType("image/jpeg");
ServletOutputStream responseOutputStream =
response.getOutputStream();
responseOutputStream.write(captchaChallengeAsJpeg);
responseOutputStream.flush();
responseOutputStream.close();
return null;
}
public void setJcaptchaService(ImageCaptchaService jcaptchaService)
{
this.jcaptchaService = jcaptchaService;
}
public void afterPropertiesSet()
throws Exception
{
if(jcaptchaService == null)
throw new RuntimeException("Image captcha service wasn`t set!");
else
return;
}
private ImageCaptchaService jcaptchaService;
}
//*******************************************************
You should configure this controller in cas-servlet.xml and map to the url
that the controller will process add the following to the handlerMappingC
bean in the property mappings
<prop
key="/captcha.htm">
captchaImageCreateController
</prop>
/captcha.htm or whatever url that you want to set shoul be map in the
web.xml by add the following
<servlet-mapping>
<servlet-name>cas</servlet-name>
<url-pattern>/captcha.htm</url-pattern>
</servlet-mapping>
The last step is the use of this url in our authentication page by add the
following JSP code
<c:if test="${not empty count && count >= 3}" >
<img src="captcha.htm" >
<input name="j_captcha_response" type="text">
</c:if>
//*******************************************************
the configuration of the beans described here
<bean id="captchaErrorCountAction"
class="your.path.CaptchaErrorCountAction"/>
<bean id="captchaValidateAction" class="your.path.CaptchaValidateAction"
p:captchaService-ref="captchaService"
p:captchaValidationParameter="j_captcha_response"/>
<bean id="captchaImageCreateController"
class="your.path.CaptchaImageCreateController">
<property name="jcaptchaService" ref="jcaptchaService" />
</bean>
the configuration of the bean jcaptchaService is bussiness of the
developer, the following is an example
<bean id="jcaptchaService"
class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService"/>
_______________________________________________
Yale CAS mailing list
http://tp.its.yale.edu/mailman/listinfo/cas
--
Arnaud Lesueur

LinkedIn: http://www.linkedin.com/in/lesueur
Axel Mendoza Pupo
2008-06-19 06:51:57 UTC
Permalink
you are right, but if an attacker use a different session in every authentication request, for CAS is a diferent user who is trying to authenticate or not ?, so I think that this case is bussiness of a superior check like a filter that count the bad authentication request of that IP address and denied request from blocked IP address to keep safe CAS from brute force.
Loading...