DWR, Spring and Capture (Using Spring and DWR 2.x Filters)

By rainkinz on Jan 27, 2007, comments 304

Recently a bot figured out how to submit my comment form. It was pretty funny I had more than 700 comments telling me how interesting my post on how to install a jar in maven was. I decided to add captcha support and while I was at it, make the comment form use DWR for submission so that the page didn't refresh etc.

I have created a little example application demonstrating how I integrated JCaptcha with DWR. The example is also (hopefully) serves as an example of how to use the new Spring Namespace support that has been added to DWR. I think if you're using DWR with Spring 2 and you want to keep all your configuration within Spring, you should use the Namespace support for configuring DWR. It's just easier. Please note this example only runs in a Java 5 environment and that if you're using DWR 2.0.rc2 there is a small issue with using Spring 2.0.1 or better so I've had to use Spring 2.0. This issue is fixed in CVS HEAD so if you want to build DWR yourself from source you should have no problems with Spring 2.0.1 or 2.0.2.

DWR Configuration in Spring

To keep things simple, in this example all the DWR configuration is done in the spring-catpcha-controllers.xml spring configuration. First we register the DwrController using:

<dwr:controller id="dwrController" debug="false" >

Now we need to tell DWR what domain objects we'd like to expose using javascript. DWR does this using Converters. In this case I'm really only exposing the Comment domain object:

<dwr:convert type="bean" class="com.kuripai.example.domain.Comment">
  <dwr:include method="id" />
  <dwr:include method="title" />
  <dwr:include method="postedBy" />
  <dwr:include method="website" />
  <dwr:include method="email" />
            <dwr:include method="body" />
            <dwr:include method="createdAt" />
            <dwr:include method="humanResponse" />
        </dwr:convert>

The next step is to configure and expose the service that saves comments. I've created a very simple WeblogService for this purpose which just saves the comments into a java.util.List. Obviously a production version would save the comments in a database or similar. Here is the configuration:

 <bean id="weblogService" class="com.kuripai.example.service.WeblogService" >
        <dwr:remote javascript="weblogService">

            <!--  Methods that are allowed to be exposed via javascript -->
            <dwr:include method="getComments" />
            <dwr:include method="saveComment" />

            <!-- Filter to handle Captcha -->
            <dwr:filter class="com.kuripai.example.dwr.captcha.CaptchaAjaxFilter" />
        </dwr:remote>

        <property name="validators">
            <list>
                <ref local="captchaValidator" />
                <bean class="com.kuripai.example.domain.CommentValidator" />
            </list>
        </property>
    </bean>

As you can see you configure your service as normal with the additional element telling DWR what the name of the service should be in Javascript and the elements telling DWR what methods are allowed to be exposed. Also note that I've exposed the service directly via DWR. Personally I don't normally do this. Instead I write a very simple proxy around the service and expose that instead. One reason I like to do this is to invoke regular Spring Validators and return a regular org.springframework.validation.Errors object to DWR. I'm not really sure if this is such a great idea. Just something I'm playing with at the moment.

    public Errors saveComment(Comment comment) {
        Errors errors = new BindException(comment, "comment");
        for (int i = 0; i < validators.length; i++) {
            ValidationUtils.invokeValidator(validators[i], comment, errors);
        }

        if (!errors.hasErrors()) {
            comments.add(comment);
            comment.setId(Long.valueOf((long) comments.size()));
        }
        return errors;
    }

The org.directwebremoting.AjaxFilter

You might have notices the element in the previous configuration code snippet. DWR 2 introduced the org.directwebremoting.AjaxFilter that can be configured to be invoked per request for a method exposed by DWR. I use a filter to here to get the current Captcha ID in Session and put it in a threadlocal based holder object for use in the Captcha validator.

    public Object doFilter(Object object, Method method, Object[] params, AjaxFilterChain chain) throws Exception {
        if (logger.isDebugEnabled()) {
            logger.info("Processing method '" + method.getName() + "' on service '" +
                        object + "'");
        }

        HttpSession session = WebContextFactory.get().getSession();
        CaptchaResponseHolder.setCaptchaId(session.getId());

        Object reply = chain.doFilter(object, method, params);

        return reply;
    }

The CaptchaValidator then is a regular org.springframework.validation.Validator:

    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmpty(errors, CAPTCHA_FIELD_NAME,
                "form.error.required",
                new Object[] { new DefaultMessageSourceResolvable(
                        "form.captcha") }, "Please enter the validation word");

        if (errors.getFieldError(CAPTCHA_FIELD_NAME) != null) {
            return;
        }

        CaptchaAware captchaAware = (CaptchaAware) target;
        if (!isCaptchaValid(captchaAware.getHumanResponse())) {
            errors.rejectValue(CAPTCHA_FIELD_NAME, "form.error.captchaInvalid",
                    "Please enter the security word again");
        }
    }

    private boolean isCaptchaValid(Object response) {
        String captchaId = CaptchaResponseHolder.getCaptchaId();
        if (logger.isDebugEnabled()) {
            logger.debug("Validating captcha response '" + response
                    + "' for captchaId '" + captchaId + "'");
        }
        Boolean isValid = captchaService.validateResponseForID(captchaId,
                response);
        return isValid.booleanValue();
    }

Running the Example

That's pretty much it. I've zipped up the source code for the example here -> (I will put this up in the next few days). Unzip this into a directory of your choice and type mvn tomcat:run after completing the steps below providing of course you have maven 2 installed. You should also be able to import the project into eclipse to take a look at it.

Installing JCaptcha and Other Required Libraries in your Maven Repository

Unfortunately there seems to be a problem with the version of JCaptcha in on ibiblio. So you will need download and install the jar in you maven repository yourself following these steps (assuming you have Maven 2 installed):

  1. Download jcaptcha-1.0-RC3 binary from the projects download page.

  2. Unzip the downloaded jcaptcha binary and cd into the directory where you unzipped it.

  3. Install the jar using this command:

  mvn install:install-file -DgroupId=jcaptcha -DartifactId=jcaptcha-all \
  -Dversion=1.0-RC3 -Dpackaging=jar -Dfile=jcaptcha-all-1.0-RC3.jar

h2. Running the Example

You should now be ready to run the example. From the command line, cd into the directory where you downloaded the example. Type:

mvn clean tomcat:run 

Maven should download a whole bunch of jar files including a enough to run an embedded version of Tomcat. Which will start up and run. Navigate to http://localhost:8080/dwr-captcha/example/comments.html and you should see a regular looking comment form with captcha that never refreshes.

6403
D6bfa2c091de50015314dc003be577c9.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest Pharme191 on Aug 24, 2017 at 5:22 AM

Hello!

5220
06114c54a89a8b631f2349708f6f2f85.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest louis vuitton speedy 35 organizer on Nov 25, 2014 at 9:26 AM

Cheap Authentic louis vuitton neverfull gm bag price | louis vuitton outlet online luggage | louis vuitton speedy 40 size bags outlet shop online sale!

5777
080008b12581b84969ca0809bafeffa4.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest Comandant on Sep 11, 2015 at 10:21 AM

Simply add that this user is malicious or a hcaker. So report him to make fb more safe from hcakers, etc. you can use your brain guys( ) . But does fb really delete his account?

5843
448daf8250b9fa798f8c01f3386916ba.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest Mark on Oct 22, 2015 at 1:51 AM

hi brother Hey there to begin with seavrel thanks on your article, it absolutely was a brilliant learn, We aslo take place to entirely agree with you, now to the cheeky element are you able to tell me where you host your word wide web website it loads quite speed

5942
87b76513bf657592c96abb9bee733703.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest Lorene on Jan 06, 2016 at 7:47 PM

A few years ago I'd have to pay someone for this intanmofior.

6378
4f972f11204d18e1909b11de2ccd1366.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest Johng725 on Aug 15, 2017 at 11:23 PM

Hello There. I found your blog using msn. This is a really well written article. Ill make sure to bookmark it and return to read more of your useful information. Thanks for the post. I will definitely return. ekaefgdekkdf

6382
3c415f3c820b17881496e9d645450d79.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest Pharmf149 on Aug 16, 2017 at 4:34 PM

Hello!

6385
91276994fa6e8b72aae2091eb2ce0c6e.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest Pharmb785 on Aug 17, 2017 at 11:03 PM

Hello!

6388
6d644cf4d6879456d80a79f98e0a8ef9.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest Pharme891 on Aug 19, 2017 at 5:14 AM

Hello!

6282
Bf06483bb10dc27357aac8c525aa1ffc.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest Ahsgdfloqifg on Nov 11, 2016 at 2:37 AM

Pgksrjgiohi hw hweokfjeq ojfe jfweiogwo gwoj wijf gdhgtrj575 y6u75tyhgf 5yu5regr

6284
F333be59b635b0124300972bc4e4f13d.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest Xewrtyuoipye on Nov 13, 2016 at 9:11 AM

Xighefjeo orj wokwp dkow pwk wodj d hfdgfhgf 4756 5uhtyjur urt45

6285
9f2949ff94d6a213ff687eb1c9d97ad0.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest Iopafeopt on Nov 13, 2016 at 10:43 AM

Ugireojfe whfiwehfjwehwhfjehfwefhweh 777uiop fweh iwehf weiohf wieohf iwehf iweyu59tu328hfire iuwfodhqw934785 h3urh9wjfwgut h9wh9889wh98r h4wt93qrj29th2 rj2ghw9tfq.

6392
140f35a9dc3e709dd2c2cdfb69ddaf64.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest Pharmd794 on Aug 20, 2017 at 11:06 AM

Hello!

6397
B9dce8867c333383405b77f135e4fefc.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest Pharme662 on Aug 21, 2017 at 5:13 PM

Hello!

6400
6184fe92fbe399420e564f5ad6cec737.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest Pharmd36 on Aug 22, 2017 at 11:12 PM

Hello!

6339
80867a82b8379a7fcdba406780c66ab3.png?s=48&d=http%3a%2f%2fkuripai.com%2fassets%2fguest DennisLauff on Feb 24, 2017 at 9:10 PM

Zhheherhrh eg egemtr hrggwe wes egemtrghdfbsgd rwtwrqw

Comment: