Adding CAPTCHA to your GWT application
What is CAPTCHA?
In a world full of malicious bots, what can you do to protect your precious web application? One of the basic things that you really should do is add CAPTCHA capabilities to it. If you are not familiar with the (rather bizarre sounding) term, CAPTCHA is a simplistic way to ensure that a user is actually a real person, and not a computer. This can be done by challenging the user and asking from him to provide a response to a “problem”. Because computers are unable to solve the CAPTCHA, any user entering a correct solution is presumed to be human. The most common way is to ask the user to type letters or digits from a distorted image that appears on the screen.
Add SimpleCaptcha into your application
First, let’s create our Eclipse project. Select “File ? Web Application Project” and provide the necessary information as shown in the following image. The project’s name will be “CaptchaGwt”. Make sure that support for GWT is included, but Google App Engine is not.
Setting up the client side
Eclipse will automatically create the application’s skeleton, also creating some sample files. Locate the CaptchaGwt class which is the application’s entrypoint. Remove the existing contents and replace them with the following:
package
com.javanotes2all.client;
import
com.google.gwt.core.client.EntryPoint;
import
com.google.gwt.core.client.GWT;
import
com.google.gwt.event.dom.client.ClickEvent;
import
com.google.gwt.event.dom.client.ClickHandler;
import
com.google.gwt.event.dom.client.KeyCodes;
import
com.google.gwt.event.dom.client.KeyUpEvent;
import
com.google.gwt.event.dom.client.KeyUpHandler;
import
com.google.gwt.user.client.Window;
import
com.google.gwt.user.client.rpc.AsyncCallback;
import
com.google.gwt.user.client.ui.Button;
import
com.google.gwt.user.client.ui.Image;
import
com.google.gwt.user.client.ui.Label;
import
com.google.gwt.user.client.ui.RootPanel;
import
com.google.gwt.user.client.ui.TextBox;
public
class
CaptchaGwt implements
EntryPoint, ClickHandler {
private
final
SignupServiceAsync signupService
= GWT.create(SignupService.class);
private
final
Button sendButton
= new
Button("Sign Up");
private
final
Button reloadButton
= new
Button("Reload Image");
private
int
counter =
1;
private
Image captchaImage;
public
void
onModuleLoad() {
final
TextBox usernameField = new
TextBox();
usernameField.setText("Username
here");
final
TextBox passwordField = new
TextBox();
passwordField.setText("Password
here");
final
TextBox captchaField = new
TextBox();
captchaField.setText("CAPTCHA
Word here");
final
Label responseLabel = new
Label();
captchaImage
= new
Image("/SimpleCaptcha.jpg?counter="+counter);
usernameField.setFocus(true);
sendButton.addStyleName("sendButton");
RootPanel.get("usernameFieldContainer").add(usernameField);
RootPanel.get("passwordFieldContainer").add(passwordField);
RootPanel.get("captchaFieldContainer").add(captchaField);
RootPanel.get("sendButtonContainer").add(sendButton);
RootPanel.get("reloadButtonContainer").add(reloadButton);
RootPanel.get("captchaImageContainer").add(captchaImage);
RootPanel.get("responseLabelContainer").add(responseLabel);
reloadButton.addClickHandler(this);
class
MyHandler implements
ClickHandler, KeyUpHandler {
public
void
onClick(ClickEvent event) {
sendDataToServer();
}
public
void
onKeyUp(KeyUpEvent event) {
if
(event.getNativeKeyCode() == KeyCodes.KEY_ENTER)
{
sendDataToServer();
}
}
private
void
sendDataToServer() {
String
username = usernameField.getText();
String
password = passwordField.getText();
String
captcha = captchaField.getText();
sendButton.setEnabled(false);
signupService.performSignup(username,
password, captcha, signupCallback);
}
}
MyHandler
handler = new
MyHandler();
sendButton.addClickHandler(handler);
usernameField.addKeyUpHandler(handler);
reloadButton.click();
}
private
AsyncCallback<Boolean> signupCallback
= new
AsyncCallback<Boolean>() {
@Override
public
void
onSuccess(Boolean result) {
if
(result) {
Window.alert("CAPTCHA
was valid");
}
else
{
Window.alert("CAPTCHA
was invalid");
}
sendButton.setEnabled(true);
}
@Override
public
void
onFailure(Throwable caught) {
Window.alert("Error
occurred while communicating with server");
sendButton.setEnabled(true);
}
};
@Override
public
void
onClick(ClickEvent event) {
if
(event.getSource()==reloadButton)
{
counter++;
RootPanel.get("captchaImageContainer").remove(captchaImage);
captchaImage
= new
Image("SimpleCaptcha.jpg?counter="+counter);
RootPanel.get("captchaImageContainer").add(captchaImage);
}
}
}
Next, locate the HTML file named “CaptchaGwt.html” inside the project’s “war” file. Edit the file and add some containers for our GWT objects. The code is the following:
<!doctype
html>
<!--
The DOCTYPE declaration above will set the -->
<!--
browser's rendering engine into -->
<!--
"Standards Mode". Replacing this declaration -->
<!--
with a "Quirks Mode" doctype is not supported. -->
<html>
<head>
<meta
http-equiv="content-type"
content="text/html;
charset=UTF-8">
<!--
-->
<!--
Consider inlining CSS to reduce the number of requested files -->
<!--
-->
<link
type="text/css"
rel="stylesheet"
href="CaptchaGwt.css">
<!--
-->
<!--
Any title is fine -->
<!--
-->
<title>Web
Application
Starter
Project</title>
<!--
-->
<!--
This script loads your compiled module. -->
<!--
If you add any GWT meta tags, they must -->
<!--
be added before this line. -->
<!--
-->
<script
type="text/javascript" language="javascript"
src="captchagwt/captchagwt.nocache.js"></script>
</head>
<!--
-->
<!--
The body can have arbitrary html, or -->
<!--
you can leave the body empty if you want -->
<!--
to create a completely dynamic UI. -->
<!--
-->
<body>
<!--
OPTIONAL: include this if you want history support -->
<iframe
src="javascript:''"
id="__gwt_historyFrame"
tabIndex='-1'
style="position:absolute;width:0;height:0;border:0"></iframe>
<!--
RECOMMENDED if your web app will not function without JavaScript
enabled -->
<noscript>
<div
style="width:
22em; position: absolute; left: 50%; margin-left: -11em; color: red;
border: 1px solid red; padding: 4px;
font-family: sans-serif">
Your
web browser
must have
JavaScript
enabled
in
order for
this
application
to display
correctly.
</div>
</noscript>
<h1>CAPTCHA
Secured Web
Application</h1>
<table
align="center">
<tr>
<td
colspan="2"
style="font-weight:bold;">Please
enter your
username:</td>
<td
id="usernameFieldContainer"></td>
</tr>
<tr>
<td
colspan="2"
style="font-weight:bold;">Please
enter your
password:</td>
<td
id="passwordFieldContainer"></td>
</tr>
<tr>
<td
id="captchaImageContainer"></td>
</tr>
<tr>
<td
colspan="2"
style="font-weight:bold;">Please
enter the
word:</td>
<td
id="captchaFieldContainer"></td>
</tr>
<tr>
<td
id="reloadButtonContainer"></td>
</tr>
<tr>
<td
id="sendButtonContainer"></td>
</tr>
<tr>
<td
colspan="2"
style="color:red;"
id="responseLabelContainer"></td>
</tr>
</table>
</body>
</html>
Note that the only changes from the auto-generated file are after the <h1> tags.
Next,Our asynchronous GWT service is going to be very simple and execute only one function. The two corresponding interfaces are shown below:
package
com.javanotes2all.client;
import
com.google.gwt.user.client.rpc.RemoteService;
import
com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
@RemoteServiceRelativePath("../signup")
public
interface
SignupService extends
RemoteService {
boolean
performSignup(String username, String password, String userCaptcha);
}
package
com.javanotes2all.client;
import
com.google.gwt.user.client.rpc.AsyncCallback;
public
interface
SignupServiceAsync {
void
performSignup(String username, String password, String userCaptcha,
AsyncCallback<Boolean> callback);
}
Preparing the server side
On the server side, the main object that we use from the library is Captcha. To retrieve the Captcha’s value (and compare it to the user’s input) we have to obtain reference to the HttpSession object associated with the specific session. The HttpSession can be retrieved by the corresponding HttpServletRequest object. This is standard Java EE stuff. Do not forget that the server side GWT services inherit from the RemoteServiceServlet, which inherits from HttpServletRequest. The underlying request can be obtained by calling thegetThreadLocalRequest method. Note that, as the API mentions, this is stored thread-locally so that simultaneous invocations can have different request objects.
On the server side, the main object that we use from the library is Captcha. To retrieve the Captcha’s value (and compare it to the user’s input) we have to obtain reference to the HttpSession object associated with the specific session. The HttpSession can be retrieved by the corresponding HttpServletRequest object. This is standard Java EE stuff. Do not forget that the server side GWT services inherit from the RemoteServiceServlet, which inherits from HttpServletRequest. The underlying request can be obtained by calling thegetThreadLocalRequest method. Note that, as the API mentions, this is stored thread-locally so that simultaneous invocations can have different request objects.
The server-side concrete implementation is the following:
package
com.javanotes2all.server;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpSession;
import
nl.captcha.Captcha;
import
com.google.gwt.user.server.rpc.RemoteServiceServlet;
import
com.javanotes2all.client.SignupService;
@SuppressWarnings("serial")
public
class
SignupServiceImpl extends
RemoteServiceServlet implements
SignupService {
public
boolean
performSignup(String username, String password, String userCaptcha) {
HttpServletRequest
request
= getThreadLocalRequest();
HttpSession
session = request.getSession();
Captcha
captcha = (Captcha) session.getAttribute(Captcha.NAME);
return
captcha.isCorrect(userCaptcha);
}
}
Extending SimpleCaptcha
The final step is to setup the Servlet that will generate the image shown to the user. SimpleCaptcha can be easily extended by creating a class that inherits from the provided SimpeCaptchaServlet class. The relevant code is the following:
The final step is to setup the Servlet that will generate the image shown to the user. SimpleCaptcha can be easily extended by creating a class that inherits from the provided SimpeCaptchaServlet class. The relevant code is the following:
package
com.javanotes2all.server.servlet;
import
static
nl.captcha.Captcha.NAME;
import
java.io.IOException;
import
javax.servlet.ServletException;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletResponse;
import
javax.servlet.http.HttpSession;
import
nl.captcha.Captcha;
import
nl.captcha.backgrounds.GradiatedBackgroundProducer;
import
nl.captcha.servlet.CaptchaServletUtil;
import
nl.captcha.servlet.SimpleCaptchaServlet;
public
class
ExtendedCaptchaServlet extends
SimpleCaptchaServlet {
private
static
final
long
serialVersionUID
= 6560171562324177699L;
@Override
public
void
doGet(HttpServletRequest req, HttpServletResponse resp)
throws
ServletException, IOException {
HttpSession session =
req.getSession();
Captcha captcha = new
Captcha.Builder(_width,
_height)
.addText()
.addBackground(new
GradiatedBackgroundProducer())
.gimp()
.addNoise()
.addBorder()
.build();
session.setAttribute(NAME,
captcha);
CaptchaServletUtil.writeImage(resp,
captcha.getImage());
}
}
The “_width” and “_height” variables as passed as initialization parameters and read from the parent class. To create a new Captchaobject we use the Captcha.Builder class (which relies on the builder pattern). We then pass the object to the specific session and stream the associate BufferedImage to the servlet response.
Note that our implementation generates a new image each time the user performs a page request. This is different from the default SimpleCaptcha implementation that uses the same image for a given session.
Configuring the web application
All the components are tied up via the web application’s “web.xml” descriptor, which in our case is the following:
All the components are tied up via the web application’s “web.xml” descriptor, which in our case is the following:
<?xml
version="1.0"
encoding="UTF-8"?>
<web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee">
<!--
Servlets
-->
<servlet>
<servlet-name>signupServlet</servlet-name>
<servlet-class>com.javanotes2all.server.SignupServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>signupServlet</servlet-name>
<url-pattern>/signup</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>SimpleCaptcha</servlet-name>
<servlet-class>com.javanotes2all.server.servlet.ExtendedCaptchaServlet</servlet-class>
<init-param>
<param-name>width</param-name>
<param-value>200</param-value>
</init-param><init-param>
<param-name>height</param-name>
<param-value>50</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>SimpleCaptcha</servlet-name>
<url-pattern>/SimpleCaptcha.jpg</url-pattern>
</servlet-mapping>
<!--
Default page to serve -->
<welcome-file-list>
<welcome-file>CaptchaGwt.html</welcome-file>
</welcome-file-list>
</web-app>
We declare the GWT service (class “SignupServiceImpl”) and the welcome file. Nothing special here. Finally we declare the servlet that will take care of the image generation and handles the request under the “’/SimpleCaptcha.jpg” URL (remember this is used in the GWT entrypoint). We also provide the initialization parameters for our servlet (width and height).
That’s it! Run the project and you should see something like the following:
0 comments:
Post a Comment