reCAPTCHA Your Wicket Application

Introduction

This sample project demonstrates one of plenty possible ways to integrate Google reCAPTCHA into Wicket-based web application. reCAPTCHA is a popular “CAPTCHA” implementation of bots abuse protection:

reCAPTCHA-wicket-demo-2
More information about CATCHA / reCAPTCHA is availabe at Google reCAPTHA page.

Requirements

  • Java SDK 1.7
  • Apache Maven 3.x

… as well as any Java Development IDE, a browser and connection to Internet. Check if Maven and Java SDK are properly installed:

$ mvn --version
[email protected]:~$ mvn --version
Apache Maven 3.0.4 (r1232337; 2012-12-08 09:44:56+0100)
Maven home: /home/mykhaylo/devres/apache-maven-3.0.4
Java version: 1.7.0_10, vendor: Oracle Corporation
Java home: /home/mykhaylo/devres/jdk1.7.0_10/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "3.2.0-35-generic", arch: "amd64", family: "unix"
[email protected]:~$

In this demo I am using Apache Wicket 6.4.0. Additional libraries like JUnit 4.10, Log4J 1.2.x and Jetty 7.6.x servlet container are download by the Maven as project dependencies.

Create wicket project

Open a command line shell (like bash or cmd) and go to your favorite development folder. Create a dummy wicket project using Maven archetype:

mvn archetype:generate \
-DarchetypeGroupId=org.apache.wicket \
-DarchetypeArtifactId=wicket-archetype-quickstart \
-DarchetypeVersion=6.4.0 \
-DgroupId=com.r.demo.wicket.recaptcha \
-DartifactId=recaptcha-demo \
-DarchetypeRepository=https://repository.apache.org/ \
-DinteractiveMode=false

Output

mvn archetype:generate \
-DarchetypeGroupId=org.apache.wicket \
-DarchetypeArtifactId=wicket-archetype-quickstart \
-DarchetypeVersion=6.4.0 \
-DgroupId=com.r.demo.wicket.recaptcha \
-DartifactId=recaptcha-demo \
-DarchetypeRepository=https://repository.apache.org/ \
-DinteractiveMode=false

[INFO] Scanning for projects...
Downloading: http://mavenrepo...
Downloading: http://mavenrepo...
Downloading: http://mavenrepo...
... Downloading maven archetype plugins and wicket stuff from maven repo
Downloading: http://mavenrepo...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom <<<
[INFO]
[INFO] --- maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom ---
Downloading: http://mavenrepo...
Downloading: http://mavenrepo...
Downloading: http://mavenrepo...
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: wicket-archetype-quickstart:6.4.0
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.r.demo.wicket.recaptcha
[INFO] Parameter: artifactId, Value: recaptcha-demo
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: com.r.demo.wicket.recaptcha
[INFO] Parameter: packageInPathFormat, Value: com/r/demo/wicket/recaptcha
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: com.r.demo.wicket.recaptcha
[INFO] Parameter: groupId, Value: com.r.demo.wicket.recaptcha
[INFO] Parameter: artifactId, Value: recaptcha-demo
[INFO] project created from Archetype in dir: /home/mykhaylo/devres/recaptcha-demo/recaptcha-demo
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 14.014s
[INFO] Finished at: Dec 08 09:47:15 CET 2012
[INFO] Final Memory: 12M/116M
[INFO] ------------------------------------------------------------------------
[email protected]:~/devres/recaptcha-demo$

This will produce the following project structure/files:

[email protected]:~/devres/recaptcha-demo$ find
.
./src
./src/test
./src/test/java
./src/test/java/com
./src/test/java/com/r
./src/test/java/com/r/demo
./src/test/java/com/r/demo/wicket
./src/test/java/com/r/demo/wicket/recaptcha
./src/test/java/com/r/demo/wicket/recaptcha/TestHomePage.java
./src/test/java/com/r/demo/wicket/recaptcha/Start.java
./src/test/resources
./src/test/resources/keystore
./src/main
./src/main/webapp
./src/main/webapp/style.css
./src/main/webapp/logo.png
./src/main/webapp/WEB-INF
./src/main/webapp/WEB-INF/web.xml
./src/main/java
./src/main/java/com
./src/main/java/com/r
./src/main/java/com/r/demo
./src/main/java/com/r/demo/wicket
./src/main/java/com/r/demo/wicket/recaptcha
./src/main/java/com/r/demo/wicket/recaptcha/WicketApplication.java
./src/main/java/com/r/demo/wicket/recaptcha/HomePage.java
./src/main/java/com/r/demo/wicket/recaptcha/HomePage.html
./src/main/resources
./src/main/resources/log4j.properties
./pom.xml
[email protected]:~/devres/recaptcha-demo$

Add reCAPTCHA panel stub to application

I use a recaptcha4j maven artifact at this project. Add the following line into pom.xml file in dependencies section:

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

This artifact provides the project with reCAPTCHA Java client, that is the closest to the original recaptcha4j-X.X.X.jar.

Create new wicket panel stabs for reCAPTCHA component:

./src/main/java/com/r/demo/wicket/recaptcha/componets/ReCaptchaPanel.java

package com.r.demo.wicket.recaptcha.componets;

import org.apache.wicket.markup.html.panel.Panel;

public class ReCaptchaPanel extends Panel {

    public ReCaptchaPanel(String id) {
        super(id);
    }
}

./src/main/java/com/r/demo/wicket/recaptcha/componets/ReCaptchaPanel.html

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" 
      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd">
<body>
<wicket:panel>

</wicket:panel>
</body>
</html>

A basic unit test with a WicketTester:

./src/test/java/com/r/demo/wicket/recaptcha/components/ReCaptchaPanelTest.java

package com.r.demo.wicket.recaptcha.components;

import com.r.demo.wicket.recaptcha.WicketApplication;
import com.r.demo.wicket.recaptcha.componets.ReCaptchaPanel;
import org.apache.wicket.util.tester.WicketTester;
import org.junit.Before;
import org.junit.Test;

public class ReCaptchaPanelTest {

    private WicketTester tester;

    @Before
    public void setUp() throws Exception {
        tester = new WicketTester(new WicketApplication());
    }

    @Test
    public void testRender() throws Exception {
        tester.startComponentInPage(new ReCaptchaPanel("panel"));
        tester.assertComponent("panel", ReCaptchaPanel.class);
        tester.assertVisible("panel");
    }
}

Add the rendering of reCAPTCHA using ReCaptchaFactory from the recaptcha4j library. As far as ReCaptchaFactory generates a chunk of HTML code of reCAPTCHA, the most useful way is to override onComponentTagBody method of Wicket FormComponent. Created reCAPTCHA HTML chuck is located in component tag body:

ReCaptchaPanel.java

public class ReCaptchaPanel extends Panel {

    private final static String PUBLIC_KEY = "6LeaD9sSAAAAAN6wKF8Vk4A4Q-raGG4r7RyZ9IwX";
    private final static String PRIVATE_KEY = "6LeaD9sSAAAAAKC5Gyi9Oq-jZ8ps4bykRHzx4jQC";

    public ReCaptchaPanel(String id) {
        super(id);
        add(reCaptchaComponent());

    }
    private Component reCaptchaComponent() {
        return new FormComponent("reCaptchaComponent", new Model()) {
            @Override
            public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) {
                replaceComponentTagBody(markupStream, openTag,
                        ReCaptchaFactory.newReCaptcha(PUBLIC_KEY, PRIVATE_KEY, false)
                                .createRecaptchaHtml("errorText", "clean", null));
            }
        };
    }
}

I have generated public and private keys for reCAPTCHA on reCAPTCHA web site for “locahost” demo host.

New “reCaptchaComponent” component is added to the HTML layout of the ReCaptchaPanel:
ReCaptchaPanel.html

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" 
      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd">
<body>
<wicket:panel>
    <div wicket:id="reCaptchaComponent"/>
</wicket:panel>
</body>
</html>

And the new reCAPTCHA panel must be rendered on the web page of the application:
./src/main/java/com/r/demo/wicket/recaptcha/HomePage.java

public class HomePage extends WebPage {
	private static final long serialVersionUID = 1L;

	public HomePage(final PageParameters parameters) {
		super(parameters);

		add(new Label("version", getApplication().getFrameworkSettings().getVersion()));

		// TODO Add your page's components here

		add(new ReCaptchaPanel("reCaptchaPanel"));

    }
}

./src/main/java/com/r/demo/wicket/recaptcha/HomePage.html

		<div id="hd">
			<div id="logo">
				<img src="logo.png" width="50px" height="50px" alt="Wicket Logo" />
				<h1>Apache Wicket</h1>
			</div>
		</div>
		<div id="bd">
			<div wicket:id="reCaptchaPanel"/>
			<h3>Get started</h3>
			<p>
				You can even <a href="https://localhost:8443">switch to HTTPS</a>!
			</p> 
			<p>
				From here you can start hacking away at your application and wow your clients:
			</p>

That’s it! Launch the web application on Jetty servlet container that is already integrated into generated Wicket project:

[email protected]:~/devres/recaptcha-demo$ mvn clean install jetty:start
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building quickstart 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ recaptcha-demo ---
[INFO] Deleting /home/mykhaylo/devres/recaptcha-demo/target
[INFO] 
[INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ recaptcha-demo ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 2 resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ recaptcha-demo ---
[INFO] Compiling 3 source files to /home/mykhaylo/devres/recaptcha-demo/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.5:testResources (default-testResources) @ recaptcha-demo ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:2.5.1:testCompile (default-testCompile) @ recaptcha-demo ---
[INFO] Compiling 3 source files to /home/mykhaylo/devres/recaptcha-demo/target/test-classes
[INFO] 
[INFO] --- maven-surefire-plugin:2.10:test (default-test) @ recaptcha-demo ---
[INFO] Surefire report directory: /home/mykhaylo/devres/recaptcha-demo/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.r.demo.wicket.recaptcha.components.ReCaptchaPanelTest
INFO  - Application                - [WicketTesterApplication-9c4d2d86-a798-4b84-804e-eb93c6d2ae51] init: Wicket core library initializer
INFO  - RequestListenerInterface   - registered listener interface [RequestListenerInterface name=IBehaviorListener, method=public abstract void org.apache.wicket.behavior.IBehaviorListener.onRequest()]
INFO  - RequestListenerInterface   - registered listener interface [RequestListenerInterface name=IFormSubmitListener, method=public abstract void org.apache.wicket.markup.html.form.IFormSubmitListener.onFormSubmitted()]
INFO  - RequestListenerInterface   - registered listener interface [RequestListenerInterface name=ILinkListener, method=public abstract void org.apache.wicket.markup.html.link.ILinkListener.onLinkClicked()]
INFO  - RequestListenerInterface   - registered listener interface [RequestListenerInterface name=IOnChangeListener, method=public abstract void org.apache.wicket.markup.html.form.IOnChangeListener.onSelectionChanged()]
INFO  - RequestListenerInterface   - registered listener interface [RequestListenerInterface name=IRedirectListener, method=public abstract void org.apache.wicket.IRedirectListener.onRedirect()]
INFO  - RequestListenerInterface   - registered listener interface [RequestListenerInterface name=IResourceListener, method=public abstract void org.apache.wicket.IResourceListener.onResourceRequested()]
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.383 sec
Running com.r.demo.wicket.recaptcha.TestHomePage
INFO  - Application                - [WicketTesterApplication-85341b6a-8213-4a2a-abdd-e3781744c339] init: Wicket core library initializer
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.023 sec

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

[INFO] 
[INFO] --- maven-war-plugin:2.1.1:war (default-war) @ recaptcha-demo ---
[INFO] Packaging webapp
[INFO] Assembling webapp [recaptcha-demo] in [/home/mykhaylo/devres/recaptcha-demo/target/recaptcha-demo-1.0-SNAPSHOT]
[INFO] Processing war project
[INFO] Copying webapp resources [/home/mykhaylo/devres/recaptcha-demo/src/main/webapp]
[INFO] Webapp assembled in [35 msecs]
[INFO] Building war: /home/mykhaylo/devres/recaptcha-demo/target/recaptcha-demo-1.0-SNAPSHOT.war
[INFO] WEB-INF/web.xml already added, skipping
[INFO] 
[INFO] --- maven-install-plugin:2.3.1:install (default-install) @ recaptcha-demo ---
[INFO] Installing /home/mykhaylo/devres/recaptcha-demo/target/recaptcha-demo-1.0-SNAPSHOT.war to /home/mykhaylo/.m2/repository/com/r/demo/wicket/recaptcha/recaptcha-demo/1.0-SNAPSHOT/recaptcha-demo-1.0-SNAPSHOT.war
[INFO] Installing /home/mykhaylo/devres/recaptcha-demo/pom.xml to /home/mykhaylo/.m2/repository/com/r/demo/wicket/recaptcha/recaptcha-demo/1.0-SNAPSHOT/recaptcha-demo-1.0-SNAPSHOT.pom
[INFO] 
[INFO] >>> jetty-maven-plugin:7.6.3.v20120416:start (default-cli) @ recaptcha-demo >>>
[INFO] 
[INFO] <<< jetty-maven-plugin:7.6.3.v20120416:start (default-cli) @ recaptcha-demo <<<
[INFO] 
[INFO] --- jetty-maven-plugin:7.6.3.v20120416:start (default-cli) @ recaptcha-demo ---
[INFO] Configuring Jetty for project: quickstart
[INFO] webAppSourceDirectory /home/mykhaylo/devres/recaptcha-demo/src/main/webapp does not exist. Defaulting to /home/mykhaylo/devres/recaptcha-demo/src/main/webapp
[INFO] Reload Mechanic: automatic
[INFO] Classes = /home/mykhaylo/devres/recaptcha-demo/target/classes
[INFO] Context path = /
[INFO] Tmp directory = /home/mykhaylo/devres/recaptcha-demo/target/tmp
[INFO] Web defaults = org/eclipse/jetty/webapp/webdefault.xml
[INFO] Web overrides =  none
[INFO] web.xml file = file:/home/mykhaylo/devres/recaptcha-demo/src/main/webapp/WEB-INF/web.xml
[INFO] Webapp directory = /home/mykhaylo/devres/recaptcha-demo/src/main/webapp
2012-12-08 15:10:57.811:INFO:oejs.Server:jetty-7.6.3.v20120416
2012-12-08 15:10:58.147:INFO:oejpw.PlusConfiguration:No Transaction manager found - if your webapp requires one, please configure one.
2012-12-08 15:10:58.391:INFO:oejsh.ContextHandler:started o.m.j.p.JettyWebAppContext{/,file:/home/mykhaylo/devres/recaptcha-demo/src/main/webapp/},file:/home/mykhaylo/devres/recaptcha-demo/src/main/webapp/
INFO  - WebXmlFile                 - web.xml: url mapping found for filter with name wicket.recaptcha-demo: [/*]
INFO  - Application                - [wicket.recaptcha-demo] init: Wicket core library initializer
INFO  - RequestListenerInterface   - registered listener interface [RequestListenerInterface name=IBehaviorListener, method=public abstract void org.apache.wicket.behavior.IBehaviorListener.onRequest()]
INFO  - RequestListenerInterface   - registered listener interface [RequestListenerInterface name=IFormSubmitListener, method=public abstract void org.apache.wicket.markup.html.form.IFormSubmitListener.onFormSubmitted()]
INFO  - RequestListenerInterface   - registered listener interface [RequestListenerInterface name=ILinkListener, method=public abstract void org.apache.wicket.markup.html.link.ILinkListener.onLinkClicked()]
INFO  - RequestListenerInterface   - registered listener interface [RequestListenerInterface name=IOnChangeListener, method=public abstract void org.apache.wicket.markup.html.form.IOnChangeListener.onSelectionChanged()]
INFO  - RequestListenerInterface   - registered listener interface [RequestListenerInterface name=IRedirectListener, method=public abstract void org.apache.wicket.IRedirectListener.onRedirect()]
INFO  - RequestListenerInterface   - registered listener interface [RequestListenerInterface name=IResourceListener, method=public abstract void org.apache.wicket.IResourceListener.onResourceRequested()]
INFO  - WebApplication             - [wicket.recaptcha-demo] Started Wicket version 6.4.0 in DEVELOPMENT mode
********************************************************************
*** WARNING: Wicket is running in DEVELOPMENT mode.              ***
***                               ^^^^^^^^^^^                    ***
*** Do NOT deploy to your live server(s) without changing this.  ***
*** See Application#getConfigurationType() for more information. ***
********************************************************************
2012-12-08 10:15:58.581:INFO:oejs.AbstractConnector:Started [email protected]:8080
2012-12-08 10:15:58.681:INFO:oejus.SslContextFactory:Enabled Protocols [SSLv2Hello, SSLv3, TLSv1] of [SSLv2Hello, SSLv3, TLSv1]
2012-12-08 10:15:58.682:INFO:oejs.AbstractConnector:Started [email protected]:8443
[INFO] Started Jetty Server

By calling http://localhost:8080/ in browser you get something like this:

reCAPTCHA-wicket-demo-1

But captcha still does not work properly, because it is just a dummy stub. We need to code server-side validation now.

Server-side validation

First of all, I need a HTML form to post an input filed to the server. Our ReCaptchaPanel must be inside the HTML form:

./src/main/java/com/r/demo/wicket/recaptcha/HomePage.java

public class HomePage extends WebPage {
	private static final long serialVersionUID = 1L;

	public HomePage(final PageParameters parameters) {
		super(parameters);

		add(new Label("version", getApplication().getFrameworkSettings().getVersion()));

		// TODO Add your page's components here

                Form form = new Form("form");
                form.add(new ReCaptchaPanel("reCaptchaPanel"));
                form.add(new Button("submitButton"));
                add(form);
    }
}

./src/main/java/com/r/demo/wicket/recaptcha/HomePage.html

            <div id="bd">

            <form wicket:id="form">
                <p>
                    <div wicket:id="reCaptchaPanel"/>
                </p>
                <button wicket:id="submitButton">Submit</button>
            </form>

	    <h3>Get started</h3>

Than I extend ReCaptchaPanel.java with a feedback panel and a reCaptchaValidator:

ReCaptchaPanel.java

    public ReCaptchaPanel(String id) {
        super(id);
        add(reCaptchaComponent());
        add(feedbackPanel());
    }

    private FeedbackPanel feedbackPanel() {
        return new FeedbackPanel("feedbackPanel", new ContainerFeedbackMessageFilter(this));
    }

    private Component reCaptchaComponent() {
        final FormComponent reCaptcha = new FormComponent("reCaptchaComponent", new Model()) {
            @Override
            public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) {
                replaceComponentTagBody(markupStream, openTag,
                        ReCaptchaFactory.newReCaptcha(PUBLIC_KEY, PRIVATE_KEY, false)
                                .createRecaptchaHtml("errorText", "clean", null));
            }
        };

        reCaptcha.add(reCaptchaValidator());
        return reCaptcha;
    }

    private IValidator reCaptchaValidator() {
        return new INullAcceptingValidator() {
            @Override
            public void validate(IValidatable validatable) {
                if (isValid()) {
                    success(getString("recaptcha.validation.success"));
                } else {
                    validatable.error(new ValidationError().addKey("recaptcha.validation.error"));
                }
            }
        };
    }

    public boolean isValid() {
        final ReCaptchaImpl reCaptcha = new ReCaptchaImpl();
        reCaptcha.setPrivateKey(PRIVATE_KEY);

        final HttpServletRequest servletRequest = (HttpServletRequest ) ((WebRequest) getRequest()).getContainerRequest();
        final String remoteAddress = servletRequest.getRemoteAddr();
        final String challengeField = servletRequest.getParameter("recaptcha_challenge_field");
        final String responseField = servletRequest.getParameter("recaptcha_response_field");

        final ReCaptchaResponse reCaptchaResponse = reCaptcha.checkAnswer(remoteAddress, challengeField, responseField);
        return reCaptchaResponse.isValid();
    }

reCAPTCHA posts an user answer as recaptcha_response_field request parameter. Validator reads user answer from the request on the server-side and validates it via ReCaptchaResponse. Under the hood, the ReCaptchaResponse is the result of execution of Google RESTfull web service, a real verification of the user’s answer happens in the cloud.

As far as reCaptchaComponent is of FormComponent type, in other words is a form related component, we can attach a validator. I use INullAcceptingValidator because from Wicket perspective a captcha posts no value, that means a model object is null.

According with the state of ReCaptchaResponse validator registers the messages with “recaptcha.validation.success” and “recaptcha.validation.error” keys. Need to define corresponding messages in resource file:

ReCaptchaPanel.properties

recaptcha.validation.success=Captcha code is correct
recaptcha.validation.error=Wrong captcha code!

Also need to add HTML chunk for feedback panel into ReCaptchaPanel.html layout:

ReCaptchaPanel.html

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd">
<body>
<wicket:panel>
    <div wicket:id="feedbackPanel"/>
    <div wicket:id="reCaptchaComponent"/>
</wicket:panel>
</body>
</html>

That it. Recompile the project, run Jetty and enjoy the captcha!

mvn clean install jetty:start

Customize reCAPTCHA

Hide buttons and use a custom answer field

reCAPTCHA is delivered together with standard buttons and answer field. I would like to use my own fields and buttons, I need from CAPTCHA HTML chunk the image only.

I can hide useless reCAPTCHA elements using CSS style sheets:

ReCaptchaPanel.html

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd">
<body>
<wicket:panel>

    <!--
    IMPORTANT: to be moved to CSS file
    -->
    <STYLE type="text/css">
        <!--
        .recaptcha_input_area { display: none}
        #recaptcha_area tr:not(:first-child) { display: none}
        .recaptchatable { border: 0 none !important; }
        .recaptchatable #recaptcha_image { border: 0 none !important; }
        #recaptcha_area td:not(:first-child)  { display: none}
        -->
    </STYLE>

    <div wicket:id="feedbackPanel"/>
    <div wicket:id="reCaptchaComponent"/>
</wicket:panel>
</body>
</html>

Than I add a custom text field for the user’s answer:

ReCaptchaPanel.java

    private TextField<String> reCaptchaAnswerField() {
        final TextField<String> reCaptchaAnswer = new TextField<String>("reCaptchaAnswer", new Model<String>());
        reCaptchaAnswer.add(reCaptchaValidator());
        return reCaptchaAnswer;
    }

    private IValidator<String> reCaptchaValidator() {
        return new IValidator<String>() {
            @Override
            public void validate(IValidatable<String> validatable) {
                if (isValid(validatable.getValue())) {
                    success(getString("recaptcha.validation.success"));
                    
                } else {
                    validatable.error(new ValidationError().addKey("recaptcha.validation.error"));
                }
            }
        };
    }
    public boolean isValid(String responseField) {
        final ReCaptchaImpl reCaptcha = new ReCaptchaImpl();
        reCaptcha.setPrivateKey(PRIVATE_KEY);

        final HttpServletRequest servletRequest = (HttpServletRequest ) ((WebRequest) getRequest()).getContainerRequest();
        final String remoteAddress = servletRequest.getRemoteAddr();
        final String challengeField = servletRequest.getParameter("recaptcha_challenge_field");

        final ReCaptchaResponse reCaptchaResponse = reCaptcha.checkAnswer(remoteAddress, challengeField, responseField);
        return reCaptchaResponse.isValid();
    }

Now I attach the validator to reCaptchaAnswer field, because the answer is posted there. Instead of reading the answer from the request, the validator get it via IValidatable interface.

Enhance CAPTCHA. Make it required and hide upon validated

Captcha panel class gets a boolean property reCaptchaValidated that is set to true by the validator upon the captcha is successfully validated. This property take effect on visibility of the panel via isVisible() method:

ReCaptchaPanel.java

    private boolean reCaptchaValidated = false;

    private TextField<String> reCaptchaAnswerField() {
        final TextField<String> reCaptchaAnswer = new TextField<String>("reCaptchaAnswer", new Model<String>()) {
            @Override
            protected void onComponentTag(ComponentTag tag) {
                super.onComponentTag(tag);
                tag.put("value", "");
            }
            @Override
            public boolean isRequired() {
                return true;
            }
        };
        reCaptchaAnswer.add(reCaptchaValidator());
        return reCaptchaAnswer;
    }

    private IValidator<String> reCaptchaValidator() {
        return new IValidator<String>() {
            @Override
            public void validate(IValidatable<String> validatable) {
                if (isValid(validatable.getValue())) {
                    setReCaptchaToValidated();
                } else {
                    validatable.error(new ValidationError().addKey("recaptcha.validation.error"));
                }
            }
        };
    }

    public boolean isReCaptchaValidated() {
        return reCaptchaValidated;
    }

    public void setReCaptchaToValidated() {
        this.reCaptchaValidated = true;
    }

    @Override
    public boolean isVisible() {
        return super.isVisible() && !isReCaptchaValidated();
    }

Also I override two methods of reCaptchaAnswer input field. The method onComponentTag() drops the sate of the input field remained from the previous verification attempt. The method isRequired() made captcha verification required for the web flow.

reCAPTCHA-wicket-demo-3

That it!

Download sample Wicket reCAPTCHA demo project

Share