Integrating the JCaptcha with Acegi security using the filter chain is a bit complex. Also there is problem in having the captcha validation field along with other form fields.
A more simpler approach is to integrate JCaptcha at the controller level, without using Acegi Security, if that approach works for you.
URL Mapping
Code:
<bean id=urlMapping class="...">
<!-- Controller which produces the captcha image -->
<entry key="captchaImage.do">
<ref bean="captchaImageController"/>
</entry>
<!-- Controller which handles the form containing captcha as just
another field -->
<entry key="/captchaForm.do>
<ref bean="captchaFormController"/>
</entry>
</bean>
<!-- Controller producing the captcha image -->
<bean id="captchaImageController" class="MyCaptchaImageController>
<property name="captchaService" ref="captchaService"/>
</bean>
<!-- Captcha Service -->
<bean id="capthcaService" class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService"/>
<!-- Form controller to handle the form submissions containing the captcha
image along with other information -->
<bean id="captchaFormController" class="MyCapthaFormController">
<property name="captchaService" ref="captchaService"/>
<!-- The view containing captcha image and validation field -->
<property name="formView" value="/catchaForm"/>
<!-- The view shown *if* user entered correct captcha id and captcha
validation is successful -->
<property name="successView" value="/captchaProtected"/>
<property name="commandClass" value="CaptchaFormBean"/>
</bean>
MyCaptchaImageController
Code:
public class MyCaptchaImageController extends AbstractController {
public ModelAndView handleRequestInternal(HttpServletRequest request,
HttpServletResponse response)
throws Exception {
String captchaId = request.getSession().getId();
BufferedImage challenge = captchaService
.getImageChallengeForID(captchaId, request.getLocale());
ByteArrayOutputStream bstream = new ByteArrayOutputStream();
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(bstream);
encoder.encode(challenge);
byte[] img = bstream.toByteArray();
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
OutputStream out = response.getOutputStream();
out.write(img);
out.flush();
out.close();
return null;
}
MyCaptchaFormController
Code:
public class MyCaptchaFormController extends SimpleFormController {
protected ModelAndView onSubmit(HttpServletRequest request,
HttpServletResponse response,
Object command, BindException errors)
throws Exception {
// ... Do the form submit actions here ....
}
protected void onBindAndValidate(HttpServletRequest request,
Object command,
BindException errors) throws Exception {
validateCaptcha(request, command, errors);
}
/**
* Validate the captcha response
*/
protected void validateCaptcha(HttpServletRequest request,
Object command,
BindException errors) throws Exception {
String captchaId = request.getSession().getId();
String response = ((CaptchaFormBean) command).getCaptchaResponse();
if (LOG.isDebugEnabled()) {
LOG.debug("Validating captcha response: '" + response + "'");
}
boolean okay = false;
if (StringUtils.isNotBlank(response)) {
try {
okay = captchaService.validateResponseForID(captchaId,
response);
} catch (CaptchaServiceException ex) {
LOG.error("Captcha exception: " + ex);
}
}
if (!okay) {
errors.rejectValue("captchaResponse",
"err.invalid.captcha.entry"
"Invalid Entry");
}
}
}
This way, I find it a lot simpler to integrate the JCaptcha. Since the captcha validation is handled at the controller level, it is much easier to provide nicer error messages etc.
-- suresh --