¡La versión 2.0 de Play ya está lista! Ayúdanos a traducir la documentación de la útlima versión y sigue nuestro progreso.

Manuales, tutoriales & referencias

Consulte

Contenidos

Elija la versión

Buscar

Busque con google

Libros

Agregando un captcha

Dado que cualquier persona puede escribir comentarios en nuestro blog, deberíamos protegerlo un poco a fin de evitar mensajes spam. Una manera simple de proteger un formulario es agregando un captcha.

Generado la imagen del captcha

A continuación veremos cuán simple es generar una imagen captcha con Play. Básicamente lo que haremos será desarrollar un método de acción, pero en este caso retornaremos un stream de datos binarios en vez de una página HTML como lo veníamos haciendo hasta el momento.

Dado que Play es un stack completo, procuramos incluir herramientas para las necesidades más comunes en toda aplicación web; generar un captcha es una de ellas. Podemos recurrir a la librería play.libs.Images para generar un simple captcha, y luego enviarlo en la respuesta HTTP.

Como de costumbre, comenzaremos con una implementación lo más simple posible. Agregaremos la accion captcha al controlador Application:

public static void captcha() {
    Images.Captcha captcha = Images.captcha();
    renderBinary(captcha);
}

Note que podemos pasar el objeto captcha directamente al método renderBinary() dado que la clase Images.Captcha implementa java.io.InputStream.

Recuerde importar play.libs.*.

Ahora agregue una nueva ruta al archivo /yabe/conf/routes:

GET     /captcha                                Application.captcha

Y pruebe el nuevo captcha abriendo un explorador en http://localhost:9000/captcha.

Debería generar un texto diferente cada vez que refresca la página.

¿Cómo podemos manejar el estado de la página?

Hasta el momento todo ha resultado bastante fácil, pero la parte más complicada está por comenzar. Para validar el captcha necesitamos guardar el texto de la imagen, generado al azar, en algún lugar, para luego poder verificar que el usuario haya ingresado el texto correcto.

Una solución trivial, sería poner el texto en la session del usuario al momento de generar la imagen, para recuperarlo más tarde. Pero esta solución presenta dos inconvenientes:

Primero, la session de Play es almacenada en una cookie. Esta decisión resuelve muchos problemas en términos de arquitectura, pero tiene múltiples implicancias. La información guardada en la cookie de la session está firmada (de forma tal que el usuario no puede modificarla) pero no está encriptada. Si escribimos el código del captcha en la session, cualquiera podría fácilmente resolverlo leyendo la cookie.

Segundo, recuerde que Play es un framework sin estado o stateless. Nuestra intención es manejar las cosas sin guardar ningún tipo de estado. Típicamente, cuando un usuario abre simultáneamente dos páginas distintas de nuestro blog con dos captchas diferentes, tenemos que guardar el código de cada captcha.

Para resover este problema precisamos dos cosas. Necesitamos guardar el código secreto del captcha del lado del servidor. Dado que se trata de información transitoria, podemos fácilmente almacenarla en el Cache de Play. Más aún, dado que la información guardada en el cache tiene un tiempo limitado de vida, esto le agregará un nuevo mecanismo de seguridad (por ejemplo, la código del captcha podría estar disponible por tan sólo 10 minutos). Entonces para resolver el código tendremos que generar un ID único. Este identificador único será agregado a cada formulario como un campo oculto y hará referencia de manera implícita al código generado por el captcha.

De esta manera resolvemos con elegancia la cuestión de mantener el estado del formulario.

Modifiquemos la action captcha de la siguiente manera:

public static void captcha(String id) {
    Images.Captcha captcha = Images.captcha();
    String code = captcha.getText("#E4EAFD");
    Cache.set(id, code, "10mn");
    renderBinary(captcha);
}

Note que el método getText() acepta cualquier color como parámetro. Ése será el color utilizado para dibujar el texto.

Recuerde importar play.cache.*.

Agregar el captcha al formulario de comentarios

Ahora, antes de mostrar en pantalla el formulario para los comentarios, tendremos que generar el ID único. Entonces modificaremos el formulario HTML para integrar el captcha utilizando este ID, y guardaremos el ID en un campo oculto.

Modifiquemos la acción Application.show:

public static void show(Long id) {
    Post post = Post.findById(id);
    String randomID = Codec.UUID();
    render(post, randomID);
}

Y ahora el formulario en el template /yable/app/views/Application/show.html:

…
<p>
    <label for="content">Your message: </label>
    <textarea name="content" id="content">${params.content}</textarea>
</p>
<p>
    <label for="code">Please type the code below: </label>
    <img src="@{Application.captcha(randomID)}" />
    <br />
    <input type="text" name="code" id="code" size="18" value="" />
    <input type="hidden" name="randomID" value="${randomID}" />
</p>
<p>
    <input type="submit" value="Submit your comment" />
</p>
…

Todo marcha bien. El formulario de comentarios ahora tiene una imagen captcha.

Validando el captcha

Ahora tan sólo tenemos que validar el captch. Ya hemos agregado el randomID como un campo oculto, de manera que podemos recuperarlo en la acción postComment, luego traeremos del cache el código del captcha y finalmente lo compararemos con el código enviado por el usuario.

No es tan complicado. Modifiquemos la acción postComment:

public static void postComment(
        Long postId, 
        @Required(message="Author is required") String author, 
        @Required(message="A message is required") String content, 
        @Required(message="Please type the code") String code, 
        String randomID) 
{
    Post post = Post.findById(postId);
    validation.equals(
        code, Cache.get(randomID)
    ).message("Invalid code. Please type it again");
    if(validation.hasErrors()) {
        render("Application/show.html", post, randomID);
    }
    post.addComment(author, content);
    flash.success("Thanks for posting %s", author);
    Cache.delete(randomID);
    show(postId);
}

Dado que ahora tenemos más mensajes de error, modificaremos el template show.html para mostrarlos (sí, por el momento será suficiente con mostrar tan sólo el primer error)

.. 
#{ifErrors}
    <p class="error">
        ${errors[0]}
    </p>
#{/ifErrors}
…

Por lo común, para formularios complejos, los mensajes de error no son manejados de esta manera, sino mediante mensajes internacionalizados en el archivo messages, y cada error es desplegado junto con su correspondiente campo.

Verifique que el captcha funciona según lo esperado.

Bravo!

Vaya a Agregando soporte para tags.