Tuesday, February 22, 2011

Spring Security 3: Integrating reCAPTCHA Service

Introduction

In this tutorial we'll integrate Spring Security 3 with the popular reCAPTCHA service. For the Spring Security module we'll based the application from the one we built for Spring Security 3 - MVC: Using a Simple User-Service Tutorial. For the reCAPTCHA module we'll based the implementation from the Using reCAPTCHA with Java/JSP article. Our design goal here is to integrate the reCAPTCHA service unobtrusively. To realize that we'll be relying on Spring Security filters.

Here's what we will do:
1. Declare two CAPTCHA filters
2. Modify a JSP file to include reCAPTCHA login
3. Add two filters in the spring-security.xml configuration

Requirements:
1. A basic understanding of how to setup a simple Spring Security 3 application (See here)
2. A basic understanding of how to setup reCAPTCHA with JSP (See here)
3. An account with the reCAPTCHA service (See here). For the account, make sure you assigned the host that matches your web application. For example http://localhost.

What is reCAPTCHA?
reCAPTCHA is a free CAPTCHA service that helps to digitize books, newspapers and old time radio shows....

A CAPTCHA is a program that can tell whether its user is a human or a computer. You've probably seen them รณ colorful images with distorted text at the bottom of Web registration forms. CAPTCHAs are used by many websites to prevent abuse from "bots," or automated programs usually written to generate spam. No computer program can read distorted text as well as humans can, so bots cannot navigate sites protected by CAPTCHAs.

About 200 million CAPTCHAs are solved by humans around the world every day. In each case, roughly ten seconds of human time are being spent. Individually, that's not a lot of time, but in aggregate these little puzzles consume more than 150,000 hours of work each day. What if we could make positive use of this human effort? reCAPTCHA does exactly that by channeling the effort spent solving CAPTCHAs online into "reading" books.

Source: http://www.google.com/recaptcha/learnmore
Here's a screenshot of the reCAPTCHA service:

Architecture

Scenario 1: Spring Security
A form-based login has two input fields: username and password. To authenticate a user, the user must enter valid information. After submitting the values, the web application will query the database if the submitted values are present. The web application performs the validation.

Scenario 2: reCAPTCHA
A reCAPTCHA form has one input field. To authenticate a user, the user must match the text images shown on the form. After submitting the values, the web application will send a POST request to the reCAPTCHA service. The service performs the validation and returns the result as a booblean value. The web application can use this result to determine if the user should be allowed access or not.

Spring Security and reCAPTCHA
With Spring Security and reCAPTCHA the same logic still applies. To authenticate a user, the user must enter his username, password, and the two words that matches the CAPTCHA text images.

Here's a screenshot of our new login page:

After submitting the values, here's what should happen:
1. A custom filter stores the entered values from the CAPTCHA form for later use. (Scenario 2)
2. The web application queries the database to check if the entered username and password exist. If invalid, deny access. If valid, continue with the remaining filters. (Scenario 1)
3. A second custom filter retrieves the stored values from the CAPTCHA form and sends a POST request to the CAPTCHA service. (Scenario 2)

Development

The Filters

Our application has two custom filters:
1. CAPTCHA Capture filter 
2. CAPTCHA Verifier filter
The purpose of the Capture filter is to store the information entered by the user in the CAPTCHA form. Whereas the Verifier filter's purpose is to send a POST request to the CAPTCHA service and wait for the result. If the result is valid, allow the user to proceed; otherwise, it will show the login page again.

We'll place these two filters in-between the FORM_LOGIN_FILTER alias which is the one responsible for the form-based login for Spring Security. Why do we need to place these two filters in-between this alias?

<security:http auto-config="true" >
 ...
 <security:custom-filter ref="captchaCaptureFilter" before="FORM_LOGIN_FILTER"/>
 <security:custom-filter ref="captchaVerifierFilter" after="FORM_LOGIN_FILTER"/>
</security:http>

Issue 1:
Placing the capture and verifier filters after the FORM_LOGIN_FILTER resets the requests parameters which means they will always be null. The verifier has nothing to verify resulting to denied access!

Issue 2:
Placing the capture and verifier filter before the alias works but you won't be able to redirect the user to the login page if in case the user has entered an invalid information. You can throw an Exception like BadCredentialsException but the exception will be shown on the web page! Again, you can't redirect to a JSP page because the response stream has been already written.

Here's the capture filter:

CaptchaCaptureFilter.java
package org.krams.tutorial.filter;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * Filter for capturing Captcha fields.
 * It's purpose is to store these values internally
 */
public class CaptchaCaptureFilter extends OncePerRequestFilter {
 
 protected Logger logger = Logger.getLogger("filter");
 private String recaptcha_response;
 private String recaptcha_challenge;
 private String remoteAddr;

 @Override
 public void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
   FilterChain chain) throws IOException, ServletException {

  logger.debug("Captcha capture filter");
  
  // Assign values only when user has submitted a Captcha value.
  // Without this condition the values will be reset due to redirection
  // and CaptchaVerifierFilter will enter an infinite loop
  if (req.getParameter("recaptcha_response_field") != null) {
   recaptcha_response = req.getParameter("recaptcha_response_field");
   recaptcha_challenge = req.getParameter("recaptcha_challenge_field");
   remoteAddr = req.getRemoteAddr();
  }
  
  logger.debug("challenge: " + recaptcha_challenge);
  logger.debug("response: " + recaptcha_response);
  logger.debug("remoteAddr: " + remoteAddr);
  
  // Proceed with the remaining filters
  chain.doFilter(req, res);
 }

 public String getRecaptcha_response() {
  return recaptcha_response;
 }

 public void setRecaptcha_response(String recaptchaResponse) {
  recaptcha_response = recaptchaResponse;
 }

 public String getRecaptcha_challenge() {
  return recaptcha_challenge;
 }

 public void setRecaptcha_challenge(String recaptchaChallenge) {
  recaptcha_challenge = recaptchaChallenge;
 }

 public String getRemoteAddr() {
  return remoteAddr;
 }

 public void setRemoteAddr(String remoteAddr) {
  this.remoteAddr = remoteAddr;
 }
}

Here's the verifier filter:

CaptchaVerifierFilter.java
package org.krams.tutorial.filter;

import java.io.IOException;
import java.util.Properties;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.tanesha.recaptcha.ReCaptchaImpl;
import net.tanesha.recaptcha.ReCaptchaResponse;
import org.apache.log4j.Logger;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * Filter for verifying if the submitted Captcha fields
 * are valid. 
 * <p>
* This filter also allows you to set a proxy if needed
 */
public class CaptchaVerifierFilter extends OncePerRequestFilter {
 
 protected Logger logger = Logger.getLogger("filter");
 private Boolean useProxy = false;
 private String proxyPort;
 private String proxyHost;
 private String failureUrl;
 private CaptchaCaptureFilter captchaCaptureFilter;
 private String privateKey;
 
 // Inspired by log output: AbstractAuthenticationProcessingFilter.java:unsuccessfulAuthentication:320) 
 // Delegating to authentication failure handlerorg.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler@15d4273
 private SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();

 @Override
 public void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
   FilterChain chain) throws IOException, ServletException {

  logger.debug("Captcha verifier filter");
  logger.debug("challenge: " + captchaCaptureFilter.getRecaptcha_challenge());
  logger.debug("response: " + captchaCaptureFilter.getRecaptcha_response());
  logger.debug("remoteAddr: " + captchaCaptureFilter.getRemoteAddr());
  
  // Assign values only when user has submitted a Captcha value
  if (captchaCaptureFilter.getRecaptcha_response() != null) {
   
   // Create a new recaptcha (by Soren Davidsen)
   ReCaptchaImpl reCaptcha = new ReCaptchaImpl();
   
   // Set the private key (assigned by Google)
   reCaptcha.setPrivateKey(privateKey);
 
   // Assign proxy if needed
   if (useProxy) {
    Properties systemSettings = System.getProperties();
    systemSettings.put("http.proxyPort",proxyPort);     
    systemSettings.put("http.proxyHost",proxyHost);
   }
   
   // Send HTTP request to validate user's Captcha
   ReCaptchaResponse reCaptchaResponse = reCaptcha.checkAnswer(captchaCaptureFilter.getRemoteAddr(), captchaCaptureFilter.getRecaptcha_challenge(), captchaCaptureFilter.getRecaptcha_response());
 
   // Check if valid
   if (!reCaptchaResponse.isValid()) {
    logger.debug("Captcha is invalid!");
    
          // Redirect user to login page
    failureHandler.setDefaultFailureUrl(failureUrl);
    failureHandler.onAuthenticationFailure(req, res, new BadCredentialsException("Captcha invalid!"));

   } else {
    logger.debug("Captcha is valid!");
   }
   
   // Reset Captcha fields after processing
   // If this method is skipped, everytime we access a page
   // CaptchaVerifierFilter will infinitely send a request to the Google Captcha service!
   resetCaptchaFields();
  }
  
  // Proceed with the remaining filters
  chain.doFilter(req, res);
 }

 /** 
  * Reset Captcha fields
  */
 public void resetCaptchaFields() {
  captchaCaptureFilter.setRemoteAddr(null);
  captchaCaptureFilter.setRecaptcha_challenge(null);
  captchaCaptureFilter.setRecaptcha_response(null);
 }
 
 public Boolean getUseProxy() {
  return useProxy;
 }

 public void setUseProxy(Boolean useProxy) {
  this.useProxy = useProxy;
 }

 public String getProxyPort() {
  return proxyPort;
 }

 public void setProxyPort(String proxyPort) {
  this.proxyPort = proxyPort;
 }

 public String getProxyHost() {
  return proxyHost;
 }

 public void setProxyHost(String proxyHost) {
  this.proxyHost = proxyHost;
 }

 public String getFailureUrl() {
  return failureUrl;
 }

 public void setFailureUrl(String failureUrl) {
  this.failureUrl = failureUrl;
 }

 public CaptchaCaptureFilter getCaptchaCaptureFilter() {
  return captchaCaptureFilter;
 }

 public void setCaptchaCaptureFilter(CaptchaCaptureFilter captchaCaptureFilter) {
  this.captchaCaptureFilter = captchaCaptureFilter;
 }

 public String getPrivateKey() {
  return privateKey;
 }

 public void setPrivateKey(String privateKey) {
  this.privateKey = privateKey;
 }
}

Configuration

We've already seen earlier the required configuration to activate the CAPTCHA filters. Here's the full spring-security.xml configuration file:

spring-security.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:security="http://www.springframework.org/schema/security"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/security 
   http://www.springframework.org/schema/security/spring-security-3.0.xsd">
 
 <security:http auto-config="true" use-expressions="true" access-denied-page="/krams/auth/denied" >
 
  <security:intercept-url pattern="/krams/auth/login" access="permitAll"/>
  <security:intercept-url pattern="/krams/main/admin" access="hasRole('ROLE_ADMIN')"/>
  <security:intercept-url pattern="/krams/main/common" access="hasRole('ROLE_USER')"/>
  
  <security:form-login
    login-page="/krams/auth/login" 
    authentication-failure-url="/krams/auth/login?error=true" 
    default-target-url="/krams/main/common"/>
   
  <security:logout 
    invalidate-session="true" 
    logout-success-url="/krams/auth/login" 
    logout-url="/krams/auth/logout"/>
 
  <security:custom-filter ref="captchaCaptureFilter" before="FORM_LOGIN_FILTER"/>
  <security:custom-filter ref="captchaVerifierFilter" after="FORM_LOGIN_FILTER"/>
 </security:http>
 
 <!-- For capturing CAPTCHA fields -->
 <bean id="captchaCaptureFilter" class="org.krams.tutorial.filter.CaptchaCaptureFilter" />
 
 <!-- For verifying CAPTCHA fields -->
 <!-- Private key is assigned by the reCATPCHA service -->
 <bean id="captchaVerifierFilter" class="org.krams.tutorial.filter.CaptchaVerifierFilter" 
    p:useProxy="false" 
    p:proxyPort="" 
    p:proxyHost=""
    p:failureUrl="/krams/auth/login?error=true"
    p:captchaCaptureFilter-ref="captchaCaptureFilter"
    p:privateKey="ADD-YOUR-PRIVATE-KEY-HERE"/>
 
 <!-- Declare an authentication-manager to use a custom userDetailsService -->
 <security:authentication-manager>
         <security:authentication-provider user-service-ref="userDetailsService">
           <security:password-encoder ref="passwordEncoder"/>
         </security:authentication-provider>
 </security:authentication-manager>
 
 <!-- Use a Md5 encoder since the user's passwords are stored as Md5 in the database -->
 <bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>

  <!-- An in-memory list of users. No need to access an external database layer.
      See Spring Security 3.1 Reference 5.2.1 In-Memory Authentication -->
  <!-- john's password is admin, while jane;s password is user  -->
  <security:user-service id="userDetailsService">
     <security:user name="john" password="21232f297a57a5a743894a0e4a801fc3" authorities="ROLE_USER, ROLE_ADMIN" />
     <security:user name="jane" password="ee11cbb19052e40b07aac0ca060c23ee" authorities="ROLE_USER" />
   </security:user-service>
 
</beans>
Notice the only changes we made here is add the two filters:
<security:http auto-config="true" >
 ...
 <security:custom-filter ref="captchaCaptureFilter" before="FORM_LOGIN_FILTER"/>
 <security:custom-filter ref="captchaVerifierFilter" after="FORM_LOGIN_FILTER"/>
</security:http>

And declare the beans:
<!-- For capturing CAPTCHA fields -->
 <bean id="captchaCaptureFilter" class="org.krams.tutorial.filter.CaptchaCaptureFilter" />
 
 <!-- For verifying CAPTCHA fields -->
 <!-- Private key is assigned by the reCATPCHA service -->
 <bean id="captchaVerifierFilter" class="org.krams.tutorial.filter.CaptchaVerifierFilter" 
    p:useProxy="false" 
    p:proxyPort="" 
    p:proxyHost=""
    p:failureUrl="/krams/auth/login?error=true"
    p:captchaCaptureFilter-ref="captchaCaptureFilter"
    p:privateKey="ADD-YOUR-PRIVATE-KEY-HERE"/>
The verifier filter is configurable. You can enable proxy if needed and assign a failure URL which is usually the same URL declared in your form-login tag. Don't forget to add your private key which is provided freely by reCAPTCHA!

The Login Page

Our last task is modify the login page so that the CAPTCHA form is shown along with the username and password fields. To implement the login page we just mix directly the contents from the original loginpage.jsp and the one from http://code.google.com/apis/recaptcha/docs/java.html

Here's the login page:

loginpage.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ page import="net.tanesha.recaptcha.ReCaptcha" %>
<%@ page import="net.tanesha.recaptcha.ReCaptchaFactory" %>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1>Login</h1>

<div id="login-error">${error}</div>

<c:url value="/j_spring_security_check" var="secureUrl"/>

<form action="${secureUrl}" method="post" >
    <%
        ReCaptcha c = ReCaptchaFactory.newReCaptcha("ADD-YOUR-PUBLIC-KEY-HERE", "ADD-YOUR-PRIVATE-KEY-HERE", false);
        out.print(c.createRecaptchaHtml(null, null));
    %>
 <p>
  <label for="j_username">Username</label>
  <input id="j_username" name="j_username" type="text" />
 </p>

 <p>
  <label for="j_password">Password</label>
  <input id="j_password" name="j_password" type="password" />
 </p>

 <input  type="submit" value="Login"/>   
     
</form>
        
</body>
</html>
The main changes here are the addition of the ReCaptcha:
<%
   ReCaptcha c = ReCaptchaFactory.newReCaptcha("ADD-YOUR-PUBLIC-KEY-HERE", "ADD-YOUR-PRIVATE-KEY-HERE", false);
   out.print(c.createRecaptchaHtml(null, null));
%>
Upon running the application, this will automatically build and show the CAPTCHA form.

Run the Application

To run the application, use the following URL:
http://localhost:8080/spring-security-recaptcha/
or to go directly to the login page:
http://localhost:8080/spring-security-recaptcha/krams/auth/login

Reminders:
1. Make sure you've signed-up for a reCAPTCHA account!
2. Don't forget to add your Public and Private keys in the JSP page
3. Don't forget to add the Private key in the spring-security.xml
4. Enable the proxy setting if you have one

The application has two built-in users:
username: john / password: admin
username: jane / password: user

Conclusion

That's it. We've successfully integrated reCAPTCHA with an existing Spring Security application without changing any of the classes. We've modified our configuration by just adding two simple custom filters. We've also successfully followed the guidelines presented in Using reCAPTCHA with Java/JSP article.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-security-recaptcha/

You can download the project as a Maven build. Look for the spring-security-recaptcha.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Security 3: Integrating reCAPTCHA Service ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

43 comments:

  1. Please can you explain how can we make the captcha appears only at the third login try?

    ReplyDelete
    Replies
    1. http://stackoverflow.com/questions/28576874/google-recaptcha-v2-verification-failed-on-server-side

      U can check this

      Delete
  2. I suggest you provide a custom authentication-failure-handler-ref. Inside this custom failure handler, you can create a session variable that counts how many times the user has tried. Then probably you can have a logic in your Controller that if this variable has been incremented 3x, redirect the client to a login page with ReCaptcha enabled. That's how I would tackle this problem

    ReplyDelete
    Replies
    1. Unfortunatelly keeping the counter in the session object is not the best idea. You can imagine a bot which tries to login using the new session after each failed login attempt.
      The above example is very nice to picture how to use the reCaptcha, but it should not be used for production. I will try to explain why. The condition about using captcha validation is based on the request (req.getParameter("recaptcha_response_field") != null). If you write a bot which will send only username and password without recaptcha_response_field then it will not be validated against captcha challenge.
      To avoid the above problems you will need to store some additional info in the persistence layer then you will be able to check if the captcha validation is required for a particular user.

      Delete
  3. Hi,

    As far as I understand the CaptchaVerifierFilter and CaptchaCaptureFilter are singletons. Which means that its not safe to store the reCaptcha response and challenge as you are. If two requests are sent to the web app at the same time the one they are handled concurrently and you'll never be sure which requests response and challenge are stored in the CaptchaCaptureFilter. You are going to have to store these values in the session or maybe as a request attribute.

    ReplyDelete
  4. I am seeing some error when I try to run it in Spring Eclipse IDE (STS)
    SEVERE: Exception starting filter springSecurityFilterChain
    java.lang.ClassCastException: org.springframework.web.filter.DelegatingFilterProxy cannot be cast to javax.servlet.Filter
    at org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:275)
    at org.apache.catalina.core.ApplicationFilterConfig.setFilterDef(ApplicationFilterConfig.java:422)
    at org.apache.catalina.core.ApplicationFilterConfig.(ApplicationFilterConfig.java:115)
    at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4001)
    at org.apache.catalina.core.StandardContext.start(StandardContext.java:4651)
    at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045)
    at org.apache.catalina.core.StandardHost.start(StandardHost.java:785)
    at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045)
    at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:445)
    at org.apache.catalina.startup.Embedded.start(Embedded.java:825)
    at org.codehaus.mojo.tomcat.AbstractRunMojo.startContainer(AbstractRunMojo.java:558)
    at org.codehaus.mojo.tomcat.AbstractRunMojo.execute(AbstractRunMojo.java:255)
    at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:107)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:209)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:84)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:59)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.singleThreadedBuild(LifecycleStarter.java:183)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:161)
    at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:319)
    at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:156)
    at org.apache.maven.cli.MavenCli.execute(MavenCli.java:534)
    at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:196)
    at org.apache.maven.cli.MavenCli.main(MavenCli.java:141)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:290)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:230)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:409)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:352)
    Jul 26, 2011 12:16:33 AM org.apache.catalina.core.StandardContext start
    SEVERE: Error filterStart

    Can you please suggest something?
    Thanks in advance

    ReplyDelete
  5. Is CaptchaCaptureFilter required ?

    ReplyDelete
  6. amazing , best Spring 3 MVC examples site!
    I'll try it asap

    ReplyDelete
  7. reCaptcha is very good option provided on sites using Captcha form .

    ReplyDelete
  8. Amazing - again! a question please , I want to use it on the registration page , is that a problem ? I have a with many fields , can it work out ? Must it be login only?

    Will there will be a problem to use reCaptcha without Spring Integrating? less secure? use the default way on pure-JSP ? is it wrong?

    Waiting for you reply and thanks


    Thanks

    ReplyDelete
  9. Ok I have found a way : here

    http://wheelersoftware.com/articles/recaptcha-java.html

    Spring MVC 2.5 example for a form with reCaptcha

    ReplyDelete
  10. @Anonymous, I use reCaptcha in a registration page. I've seen numerous websites doing the same. So I don't see any problem with it. At the end, login and registration are essentially the same. reCaptcha doesn't force to use Java. So there's no problem if you don't use Java/Spring

    ReplyDelete
  11. Thanks for the help (and for the page), very good example. I have a problem, I used this tutorial to add a select in the login form which has to have any value to permit the login.

    All works well but when the user/pass validates and the post filter calls the method onAuthenticationFailure (because is nothing selected in the select) redirects well but doesn't cancel the login (the spring security context is in session). Let me know if you wanna/need to see my code.

    ReplyDelete
  12. I answer myself. The code above doesn't log out, you have to do it programatically in the pos filter. One way of doing this is the next:


    ((SecurityContextImpl) req.getSession().getAttribute("SPRING_SECURITY_CONTEXT")).setAuthentication(null);

    ReplyDelete
  13. sir i am a beginner. i want to add recaptcha into my application. i have read the instructions given in google code. But the problem is that , when i am submitting the form it is showing message in different page. i want to add the message on failure just down to the captcha box, and upon success it should navigate to the requested page. i am using servlet and jsp. I havse been trying to do it for last 4 days. Please help me out on this.

    ReplyDelete
  14. This tutorial is referenced by many many blogs. However, this implementation has some serious flaws.

    1. The Captcha validation is completely optional. If the captcha fields are not present in the form then the validation will not be done. The validation is easily circumvented.
    2. As the response parameters are stored in a singleton bean (CaptchaCaptureFilter) and not in the current request, validation responses are not tied to the current request but always stay global.

    A better implementation would be to extend the UsernamePasswordAuthenticationFilter (or create a complete custom filter) and embed the ReCaptcha verification.

    ReplyDelete
  15. @Anonymous, thanks for the message. However, as you've noted, this is a tutorial and it is not meant for production use. I'm surprised when people take tutorials as production code. But in regards to your suggestion, can you write this better implementation so we can see it for comparison purposes? I'm glad that this tutorial is referenced by many blogs. Before this was written, I have few references (if any) that deals with this topic. So that inspired me to write one. If there are bugs here, consider such code as a snapshot.

    ReplyDelete
    Replies
    1. http://pastebin.com/xNssT40X

      In the security xml, replace the original FOLES_LOGIN_FILTER with the implementation found at the pastebin url.

      Delete
  16. @Anonymous, #1 why would you remove the captcha fields in the form, when in the first place it has to be there. It's like why remove the form tag of an html page, when you need a form. It doesn't sound logical. #2 You can add a session request type to that bean. Of course, you'll have to do some tests. Don't get me wrong, but there's a difference between stating that there's a better implementation versus writing and sharing it.

    ReplyDelete
  17. What if the user sends a login request without the captcha answer. Will he user be able to login without getting the captcha verified? Does not this break the hole idea with this extra protection. If I am correct, this solution is a little bit to simple to cheat.
    Best Regards smasseman

    ReplyDelete
  18. Ooops.
    This security risk was already commented by another user.
    Sorry about that.
    Best Regards smasseman

    ReplyDelete
  19. @krams (in reply to Jan 12, 2012 02:58 PM): Please take a look at the UserPasswordAuthenticationFilter implementation @ http://pastebin.com/xNssT40X

    In the security xml, replace the original FOLES_LOGIN_FILTER with the implementation found at the pastebin url.

    ...

    ReplyDelete
  20. hi krams
    i expecting atleast one reply to my questions,i have done this captcha example everything works fine for me ,but i facing one problem that is the captcha generated code i was entering leaving last 2 or 4 letters it is still accepting and redirecting to next page.if i enter only word before space then it was showing invalid username and password .please help me krams

    ReplyDelete
  21. @venu, the implementation included in this tutorial might have missed some use cases. It's difficult to cover all cases when the program itself hasn't tested heavily. I will have to investigate why is this happening. This might take me some time.

    ReplyDelete
  22. I'm profiling an application with a very similar configuration of reCAPTCHA in Spring Security. The results of the profiling shows that the CAPTCHA Verifier filter is filtering every server request (except static content) after the login page succedded and it's using a lot of time to process every request. For instance, in a test, captchaVerifierFilter consumes 11% of time of the whole time of the test, but only 1% is spent in bussines process and some of this bussines process are complex nto only some CRUD operations. Do you know how can i stop this behavior of the captchaVerifierFilter?

    ReplyDelete
    Replies
    1. Did you use the OncePerRequestFilter: "Filter base class that guarantees to be just executed once per request, on any servlet container. It provides a doFilterInternal method with HttpServletRequest and HttpServletResponse arguments."

      http://static.springsource.org/spring/docs/1.2.9/api/org/springframework/web/filter/OncePerRequestFilter.html

      Delete
  23. Hi Krams,
    why this project not work correctly! please help me. i can't run it.

    ReplyDelete
  24. where is FORM_LOGIN_FILTER filter defined in above example

    ReplyDelete
  25. all works great. only look and make it yourself. thanks

    ReplyDelete
  26. This comment has been removed by the author.

    ReplyDelete
  27. Hi, I am getting error the “Input error: k: Format of site key was invalid”. Could you please help me ?

    ReplyDelete
  28. I have added private key p:privateKey="6LdorQcTAAAAABMEHRhcl6dJlMJ6nWDSsFkI8_Ob"

    ReplyDelete
  29. How can I do the same in thymeleaf? A blog post on the same would be nice. since Spring and Thymeleaf are mostly integrated tools now.

    ReplyDelete
  30. Thanks for sharing this wonderful information. I hope you will share more helpful information regarding the content.
    web portal development company in chennai
    sem services in chennai
    professional web design company in chennai

    ReplyDelete
  31. Nice article very helpful and informative thanks for sharing this information. Fashion bloggers in India

    ReplyDelete
  32. This is Nice Article, anda I think I get Solution After Read This Article

    ReplyDelete
  33. Thanks before your article is the best

    ReplyDelete
  34. This comment has been removed by the author.

    ReplyDelete
  35. Thanks before your article is amazing

    ReplyDelete