My favorites | Sign in
Project Home Downloads Wiki Issues Source
READ-ONLY: This project has been archived. For more information see this post.
Search
for
reCaptchaGWT  
Updated Mar 28, 2012 by combe.ol...@gmail.com

GWT + GAE + reCaptcha

Technos utilisées :

Google Web Toolkit v2.4 (GWT) <<http://code.google.com/webtoolkit/>> Google App Engine v1.55 (GAE) <<http://code.google.com/appengine/>> reCaptcha <<http://google.com/recaptcha>> <<http://fr.wikipedia.org/wiki/ReCAPTCHA>>

Introduction

Après quelques recherche sur le net pour trouver un moyen d'inclure un captcha dans une "page" GWT (avec backend GAE) je me suis rendu compte que ce n'étais pas si courant que ça, la limitation des classes Java dans GAE pose pas mal de problème, notamment pour le traitement des images.

Une solution, est donc de passer par un service externe de captcha, je me suis orienté vers reCaptcha, comme la société à été rachetée par Google en 2009, on peut esperer que le service est une assez longue espérance de vie, et donc ne pas avoir à changer de fournisseur tous les 6 mois.

On peut trouver un très bon tuto d'intégration de reCaptcha avec GAE à cette adresse <<http://blog.cloudglow.com/2010/03/using-captcha-with-google-app-engine.html>>, c'est de ce tuto que je suis parti pour faire l'intégration dans GWT.

reCaptcha

Première étape, récupérer un "kit" reCaptcha, pour ça il faut, l'API (http://code.google.com/p/recaptcha/) et un compte reCaptcha (ou Google). Une fois le compte validé/activé, il faut créer une combinaison de clés (privée/publique) permettant de valider le "challenge" (l'image), les couples de clés sont associés à un compte Google/reCaptcha biensur, mais à une URL aussi, pour les tests, j'ai donc utilisé "localhost" comme URL pour générer les clés :

<

<printscreen>
> <
<printscreen>
>

Maintenant que nous avons les clés, nous devons encore intégrer l'API de reCaptcha dans notre projet GWT (ici un projet GWT 'standard' généré par Eclipse, en utilisant GAE). Il faut donc ajouter le jar dans le build-path du projet, ainsi que dans le répertoire war/WEB-INF/lib :

<

<printscreen>
> build-path <
<printscreen>
> war/web-inf/lib

L'environnement de travail est maintenant prêt, nous pouvons attaquer le code, voila les principales étapes :

- Modification du fichier HTML de l'appli - Modification de l'entryPoint (ou de la partie qui devra gérer le captcha côté client) - Mise en place d'un service avec Google App Engine

Fichier HTML

Pour faire fonctionner le 'widget' reCaptcha, il faut intégrer côté client le script JavaScript de celui ci. Dans cet exemple, je compte mettre le captcha sur la page principale du projet, il est possible biensûr de le mettre dans une vue (via UIBinder ou non), dans tous les cas, il faut que le script JS de reCaptcha soit inclus dans cette page (on doit pouvoir l'inclure via les Bundles de GWT, mais je n'ai pas regardé plus que ça pour le moment).

Pour inclure le script JS, c'est simple, il suffit d'ajouter cette ligne dans la partie
<head>
de notre page HTML :

-- -------------------------------------------------------------- --
<script type="text/javascript" src="http://api.recaptcha.net/js/recaptcha_ajax.js">
</script>
-- -------------------------------------------------------------- --

Ensuite, nous ajoutons dans notre partie body la div qui va contenir le captcha (injecté au chargement de la page), c'est cette partie qui peut être déplacée dans une vue :

-- -------------------------------------------------------------- --
-- -------------------------------------------------------------- --
Vous pouvez retrouver la page complète à cette adresse : <<http://code.google.com/p/eryos-mail-module/source/browse/war/Mail_module.html>>

EntryPoint / Client Side

Maintenant que le script JS de reCaptcha est défini dans notre page HTML, nous pouvons l'utiliser dans l'entry point via les méthodes JSNI. On va en définir 3 :
void createCaptcha(String publicKey, String divID, String theme); String getCaptchaChallenge(); String getCaptchaResponse();
La 1ere permet d'injecter le captcha dans notre page. Pour générer le capthca, nous avons besoin de définir la clé publique, la div HTML dans laquelle le captcha doit être injecté ainsi qu'un theme (par défaut le theme est "white"). Vous pouvez retrouver la liste des themes dispo ici : <<http://code.google.com/intl/fr-FR/apis/recaptcha/docs/customization.html>>

Le code est simple et fait juste appel à une fonction JS du script reCaptcha :
-- -------------------------------------------------------------- -- public static native void createCaptcha(String publicKey, String captchaDivId, String themeColor) /-{
$wnd.Recaptcha.create(publicKey, captchaDivId, { theme: themeColor, callback: $wnd.Recaptcha.focus_response_field });
}-
/; -- -------------------------------------------------------------- --
Les 2 autres méthodes, permettent de récupérer une String représentant le challenge et la réponse saisie par l'utilisateur. Les méthodes sont très simples elles aussi :

-- -------------------------------------------------------------- -- public static native String getCaptchaResponse() /-{
return $wnd.Recaptcha.get_response();
}-
/; -- -------------------------------------------------------------- --
-- -------------------------------------------------------------- -- public static native String getCaptchaChallenge() /-{
return $wnd.Recaptcha.get_challenge()
}-
/; -- -------------------------------------------------------------- --
Il ne reste plus qu'à faire les appels à ces méthodes et gérer les evenements, on commence donc par appeler la méthode d'initialisation au début de l'entry point :
-- -------------------------------------------------------------- -- public void onModuleLoad() {
createCaptcha(PUBLIC_KEY, "captchaContainer", "white");
} -- -------------------------------------------------------------- --
On ajoute un bouton permettant la validation du captcha par l'utilisateur, et on va mettre un eventHandler sur ce bouton :
-- -------------------------------------------------------------- -- Button validCaptchaButton = new Button("Valid Captcha"); RootPanel.get("buttonContainer").add(validCaptchaButton);

validCaptchaButton.addClickHandler(new ClickHandler(){
@Override
public void onClick(ClickEvent event) {
// TODO
}
});
-- -------------------------------------------------------------- --

Lorsqu'un click est fait sur le bouton, on récupère les 2 infos du captcha via les méthodes JSNI définies précédemment :

-- -------------------------------------------------------------- -- @Override public void onClick(ClickEvent event) {
String challenge = getCaptchaChallenge(); String response = getCaptchaResponse();
} -- -------------------------------------------------------------- --
Les 2 valeurs sont récupérées, ont peut vérifier qu'elles sont non nulles et gérer un message d'erreur si c'est le cas, mais ce n'est pas le but de cet article donc passons. Pour continuer, nous avons besoin d'un service qui permette de valider que le challenge et la réponse correspondent bien, pour ça, on passe à l'étape suivante, le service GAE ...
Service / Server Side

Pour créer le service, rien de bien compliqué, on s'inspire du Servlet fait dans le tuto reCaptcha/GAE, et on adapte juste pour le mettre en RPC.

On crée donc les 3 classes nécéssaires aux services RPC pour GWT :
Côté client :
reCaptchaService reCaptchaServiceAsync
Côté Server :
reCaptchaServiceImpl

Une seule méthode pour notre service, qui va être de la forme :
boolean checkCaptcha(String challenge, String response) throws IllegalArgumentException;
Voila comment la déclarer pour chacune des 2 classes côté client :

reCaptchaService :
-- -------------------------------------------------------------- -- @RemoteServiceRelativePath("captchaService") public interface GreetingService extends RemoteService {
Boolean checkCaptach(String challenge, String response) throws IllegalArgumentException;
} -- -------------------------------------------------------------- --

reCaptchaServiceAsync :
-- -------------------------------------------------------------- -- public interface GreetingServiceAsync {
void checkCaptach(String challenge, String response, AsyncCallback
<boolean>
callback) throws IllegalArgumentException;
} -- -------------------------------------------------------------- --
On peut maitenant l'implémenter côté serveur :

reCaptchaServiceImpl :
-- -------------------------------------------------------------- -- @SuppressWarnings("serial") public class reCaptchaServiceImpl extends RemoteServiceServlet implements reCaptchaService {
private static final String PRIVATE_KEY = "YOUR_PRIVATE_KEY_HERE";
public Boolean checkCaptach(String challenge, String response) throws IllegalArgumentException{
// TODO
}
} -- -------------------------------------------------------------- --
Le corps de la méthode est simple, en voila les différentes étapes :
Vérification des paramêtres Appel du service reCaptcha (en utilisant la clé privé) permettant de comparer le challenge et la réponse Retour du résultat à la partie cliente
Le code parle de lui même :
-- -------------------------------------------------------------- -- public Boolean checkCaptach(String challenge, String response) throws IllegalArgumentException{

// Check arguments if ((challenge == null) || (response == null))
throw new IllegalArgumentException("Your words did not match. Please try submitting again.");
// Retrieve local URL
String remoteAddr = getThreadLocalRequest().getRemoteHost();
// Define Private Key
ReCaptchaImpl reCaptcha = new ReCaptchaImpl(); reCaptcha.setPrivateKey(PRIVATE_KEY);
// Check Captcha Validity
ReCaptchaResponse reCaptchaResponse = reCaptcha.checkAnswer(remoteAddr, challenge, response);
// Return result to client
if (!reCaptchaResponse.isValid())
return false;
return true;
} -- -------------------------------------------------------------- --

Notre service est presque prêt, il faut juste vérifier qu'il est bien déclaré dans notre fichier web.xml pour pouvoir être appelé par le client, encore une fois, la configuration est celle de base pour les services RPC :
-- -------------------------------------------------------------- --
<servlet>
<servlet-name>
captchaServlet
</servlet-name>
<servlet-class>
com.eryos.gwt.mailmodule.server.reCaptchaServiceImpl
</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>
captchaServlet
</servlet-name>
<url-pattern>
/mail_module/captchaService
</url-pattern>
</servlet-mapping>
-- -------------------------------------------------------------- --

Les différentes sources sont disponibles ici :
reCaptchaService <<http://code.google.com/p/eryos-mail-module/source/browse/src/com/eryos/gwt/mailmodule/client/GreetingService.java>> reCaptchaServiceAsync <<http://code.google.com/p/eryos-mail-module/source/browse/src/com/eryos/gwt/mailmodule/client/GreetingServiceAsync.java>> reCaptchaServiceImpl <<http://code.google.com/p/eryos-mail-module/source/browse/src/com/eryos/gwt/mailmodule/server/GreetingServiceImpl.java>> web.xml <<http://code.google.com/p/eryos-mail-module/source/browse/war/WEB-INF/web.xml>>
Dernière étape, appelé le service depuis le client, la aussi on reste sur du classique.
Le clickHandler est déjà définis dans l'entryPoint, et les valeurs du captcha sont récupérées, il ne reste donc qu'à appeler le service et gérer le résultat :

-- -------------------------------------------------------------- -- greetingService.checkCaptach(challenge, response, new AsyncCallback
<boolean>
(){
@Override public void onFailure(Throwable caught) {
logCaptcha("!!! Error while checking captcha : "+caught.getMessage()+" !!!");
}
@Override public void onSuccess(Boolean result) {
if ( result ){
// CAPTCHA OK
logCaptcha("Captcha OK ... congratulation ...");
}else {
// CAPTCHA NOK logCaptcha("Captcha ERROR ... try again ...");
}
}
}); -- -------------------------------------------------------------- --
La source de l'entrypoint est elle disponible ici : <<http://code.google.com/p/eryos-mail-module/source/browse/src/com/eryos/gwt/mailmodule/client/Mail_module.java>>
On peut maintenant exécuter notre projet, et normalement, on devrait retrouver sur notre page web quelque chose comme ça :
<
<printscreen>
>

Conclusion

Voila le captcha fonctionne au milieu de notre app GWT, une prochaine étape pourrait être de supprimer l'import du script JS dans la page HTML pour n'appeler que le code nécessaire, effectivement, si on développe une appli web, et que seul un formulaire de contact, ou la partie "création de compte" ne nécessite de captcha, il est un peu dommage de le télécharger systématiquement.

On doit pouvoir, à travers les Bunddles/Ressources GWT, ne faire appel à ce script que lorsqu'on en à vraiment besoin.

Vous pouvez retrouver toutes les sources de l'exemple ici : <<http://code.google.com/p/eryos-mail-module/source/checkout>>

Si vous avez des questions / remarques / commentaires, n'hésitez pas ...

Powered by Google Project Hosting