Friday, April 26, 2013

Implementing Captcha Control with JSF 2.0

This post is in continuation with my previous post related to custom controls. Now comes the part where I will make a practical use of my knowledge.

JSF is all about rich web development. And in today's www world to differentiate between a human and a bot is very difficult. So in order to avoid spam and unnecessary traffic comes into picture Captcha.

I will start by first introducing on how to get started with Captcha:


  1. First of all the user who wants to use captcha service need to have a Google Account.
  2. Visit the site: reCAPTCHA website. 
  3. Login into your google account.
  4. Once the reCaptcha signup form is submitted, a page will be shown saying that:






This screen shows that you have successfully setup you captcha account.

Now in order to integrate captcha with your JSF-2.0 application.

It is required to do atleast one of the following:

  1. Add the HTML code as is, and then turn you head up and down, to work it in sync with the JSF view.
  2. Write a custom control which will do the part of generating the Captcha HTML for me, so I have a clean views to maintain and is easily manageable. (Winner for me :))

So in order to begin with this integration into a JSF web-application following steps are required:

  • Get the required library for captcha integration:
  • Create a custom Faces Component for captcha rendering:
  • Adding the component in faces-config.xml
  • Register the control in tag in the taglib.
  • Register the taglib in web.xml
  • Activate the taglib in faces.
  • Create a validator to actually validate the captcha with Google API for Captcha
  • Create a sample test page.

 So now I will take one step at a time:

Get captcha dependency:

For maven users, simply add this dependency in your pom.xml:

<dependency>
     <groupId>net.tanesha.recaptcha4j</groupId>
     <artifactId>recaptcha4j</artifactId>
     <version>0.0.7</version>
</dependency>

For non-maven users the jar is available for download here.

Once the jar is included in the classpath. We are ready to move to step-2.

Create custom Faces Component:

For the same I have posted the code below in the file, RecaptchaComponent.java
Don't foget to have a look at the overriden method public void validate(FacesContext ctx);
I will discuss the details later once, I have written the validator.

Adding the component in faces-config.xml

No we do not need to do it here, because the component class is already being annotated with @FacesComponent, so no need to do in faces-config.xml. Both the approaches are alternatives of each other.

Register the control in tag in the taglib:

Since we need to use this control as a tag inside the XHTMLs so it is required that we register the same. For this refer to taglib.xml, as shown in the code below.


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE facelet-taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"http://java.sun.com/dtd/facelet-taglib_1_0.dtd">

<facelet-taglib>

    <namespace>http://himanshu/jsf-custom-components/</namespace>
    <tag>
    <tag-name>recaptcha</tag-name>
    <component>
    <component-type>com.himanshu.jsf.custom.recaptcha</component-type>
    </component>
    </tag>
</facelet-taglib>


Register the taglib in web.xml and Activate the taglib in faces:

Both these steps can be merged into a single step, with the following tags in web.xml.


<context-param>
<param-name>javax.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/marquee-taglib.xml</param-value>
</context-param>



Create a validator to actually validate the captcha with Google API for Captcha:

We have created a custom validator which will capture the request, get the input entered in the captcha control. And will validate it using Google API for captcha (RecaptchaValidator.java)

And we are done.

Now its time to test: 


<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:customJSF="http://himanshu/jsf-custom-components/">

<f:view>

<h:head></h:head>

    <h:form id>

              
<customJSF:recaptcha id="rc" publicKey="abc" privateKey="xyz">

<f:validator validatorId="com.himanshu.jsf.captcha.validator" />

      </customJSF:recaptcha>

      <h:message for="rc" />

              
              <h:commandButton type="submit" id="login" value="Login" action="#{helloWorld.submitForm}" />


        </h:form>

</f:view>

</ui:composition>


Build the application and deploy in AS.
This will render the captcha control once the corresponding view is opened.

Here is the sample captcha generated.



But wait a minute, my validator is not getting called. Did I miss something?

Now let me rollback to the point where I had to override validate method in RecaptchaComponent.java.

Why did we do that, this was done basically because:
Validators are by default or by configuration not invoked when the value is empty. For that, either the required attribute should be used, or the javax.faces.VALIDATE_EMPTY_FIELDS context param should be set with a value of true.

That was the case here. So overriding the validate method and calling the validator registered with the RecpatchaComponent serves the purpose here.

Now we are done with developing the captcha component for JSF-2.0 and ready to integrate in our application.


9 comments:

  1. It is a very nice blog. I implemented this in my project. It is working fine. But when I use it in AJAX request, it throws me the following error.
    XMLHttpRequest cannot load http://api.recaptcha.net/challenge?k=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-XXXX. Origin http://localhost:8080 is not allowed by Access-Control-Allow-Origin.

    I tried apending "&callback=?" to the above url, still i am getting the same issue.

    Could you please help me in resolving this issue.

    ReplyDelete
    Replies
    1. You're getting this error because of XMLHttpRequest same origin policy, which basically boils down to a restriction of ajax requests to URLs with a different port, domain or protocol. This restriction is in place to prevent cross-site scripting (XSS) attacks.

      Following detail will clear the picture:

      Javascript is limited in making ajax requests. The limit comes out when you try to make requests outside your domain.
      Ex 1: your domain is example.com and you want to make a request to test.com => you cannot.
      Ex 2: your domain is example.com and you want to make a request to inner.example.com => you cannot.
      Ex 3: your domain is example.com:80 and you want to make a request to example.com:81 => you cannot.
      Ex 4: your domain is example.com and you want to make a request to example.com => you can.

      Javascript is limited by the "same origin policy" for security reasons, ie a malicious script cannot contact remote server and send sensible data.

      Try out few things:
      1. When sending HTTP request, in the request header (key:value) - Access-Control-Allow-Origin: *
      2. You can try using JSONP:
      The JSONP technique uses a completely different mechanism for issuing HTTP requests to a server and acting on the response. It requires cooperating code in the client page and on the server. The server must have a URL that responds to HTTP "GET" requests with a block of JSON wrapped in a function call. Thus, you can't just do JSONP transactions to any old server; it must be a server that explicitly provides the functionality.

      The idea is that your client-side code creates a script-tag block dynamically, with the "src" attribute set to the URL of the JSONP server. The URL should contain a parameter telling the server the name of the Javascript function you expect it to call with the JSON data. (Exactly what parameter name to use depends on the server; usually it's "callback", but I've seen some that use "jsonp".) The client must of course have that function in the global scope. In other words, if you have a function like:

      function handleJSON(json) {
      var something = json.something;
      // ... whatever ...
      }

      then your URL tells the server to call "handleJSON", and the server response should look like this:
      handleJSON({"id": 102, "something": { "more": "data", "random": true }});

      Thus when the script-tag block is loaded from the "src" URL you gave, the browser will interpret the contents (the response from the server) and your function will be called.

      It should be clear that you should only make JSONP requests to servers you trust, since they're sending back code to execute in your client, with access to any active session(s) your client has with other secured sites.

      You can have a look here for more details:

      https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS?redirectlocale=en-US&redirectslug=HTTP_access_control

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Himanshu,
    Is there any way to implement Captcha by using JSF 1.2 or ice faces 1.8.

    ReplyDelete
    Replies
    1. Yes it is very much possible, if you go through the blog above. You will see the only reason I mentioned JSF-2 is because the way component is declared according to JSF-2 specs.

      Now if you want to make it compatible to JSF-1.2 the only thing you want to do is change the way component is being initialized, which meets JSF 1.2 standards.

      If you face any issues let know.

      Delete
  4. superb code.................this explains very clearly about the integration of captcha framework with JSF


    image decoding

    ReplyDelete
  5. wow!!the great blog and the codes were self explanatory.
    thanks for sharing the code.
    keep blogging.

    captcha solver

    ReplyDelete
  6. wow!!!
    the great blog.
    the blog is very interesting and very informative.
    thanks for sharing the information.keep blogging.

    Kill Captcha

    ReplyDelete
  7. ya its a nice js developing application Scripts.. very wonderful script... and very great explanation captcha integration and captcha rendering, validate for Google API for Captcha,
    Thanks for Sharing...


    Decaptcha

    ReplyDelete