Cómo usar reCAPTCHAv3 en Liferay

Con códigos de ejemplo en GitHub

Publicado por Miguel Ángel Júlvez el 01 de noviembre de 2018

Esta semana Google ha publicado la nueva versión de su captcha, llamado reCAPTCHAv3.

Los captcha son utilizados para garantizar que los usuarios que están interactuando con las aplicaciones son personas de verdad y no bots que se dedican a introducir spam en las bases de datos.

Tradicionalmente, son imágenes con un conjunto de caracteres difíciles de leer (algunos casi imposibles) que, teóricamente, impiden que sean reconocidos por programas automáticos.

Hay variaciones de los captchas como son operaciones matemáticas, selección de imágenes de una temática dada, etc incluso captchas que aprovechan y te solicitan reconocer más palabras para de paso hacer negocio haciéndote actuar de OCR humano

El principal problema de los captcha, es la fricción con el usuario. Debido a su complejidad a la hora de identificarlos, muchos usuarios no los reconocen y tienen que introducir varias veces los caracteres para poder ser verificados. Esto hace que muchos usuarios abandonen el proceso que estaban realizando por lo que podemos perder muchas conversiones si usamos un captcha poco amigable.

Google fue consciente de esto, y ya sacó hace un tiempo reCAPTCHAv2 con casilla de verificación “No soy un robot” que intentaba reconocer la “humanidad” del usuario con un solo checkbox - si no estaba seguro mostraba unas imágenes a seleccionar - y a comienzos del año pasado sacó el captcha invisibile que detectaba si un usuario era una persona siguiendo sus acciones en la web (google lo sabe todo) evitando completamente de esta manera la fricción con el usuario.

El nuevo reCAPTCHAv3, mantiene el espíritu del reCAPTCHAv2 invisible, pero además añade unas action que permiten discernir las acciones del usuario y un score que cuantifica qué probabilidad tiene de ser humano el usuario (0.0 casi seguro que es un robot y un 1.0 casi seguro que es un humano).

Lo interesante de este nuevo recaptcha es que ahora somo nosotros los desarrolladores los que podemos definir el umbral de probabilidad para las acciones de nuestra web y en caso de que la puntuación del usuario esté por debajo de nuestras expectativas, habilitar otro tipo de sistema de identificación como la doble autenticación o incluso cambiar a reCAPTCHAv2 en esos casos en los que el nuevo sistema de google duda de la “humanidad” de un usuario.

De hecho, Google no considera que reCAPTCHAv3 sea un reemplazo de la v2 sino que lo considera, de momento, como una posible mejora en la usabilidad de algunos procesos de nuestro sistema. Todavía sigue recomendando usar reCAPTCHAv2 por ejemplo en formularios de introducción de datos que no afecten al negocio directamente (por ejemplo, un registro a una newsletter) ya que reCAPTCHAv3 aprende según el uso que nuestros usuarios tienen en nuestra aplicación y usar reCAPTCHAv2 ayuda a su aprendizaje

Google incluso recomienda poner reCAPTCHAv3 en todas las páginas ya que eso ayuda a su aprendizaje. Esto, para los conspiro-paranoicos como yo, parece más bien que es para registrar todas las acciones de las aplicaciones y seguir construyendo SkyNet pero bueno… vamos a continuar que si me meto en este tema el blog se me queda pequeño…

Ahora que ya sabemos lo que es reCAPTCHAv3, vamos a ver cómo usarlo en Liferay 7.1

Nuevo componente OSGI para usar reCAPTCHAv3

Lo primero que vamos a hacer es crear un componente de configuración, que sustituirá la configuración original de Captcha de Liferay (que solo soporta recaptcha v2 y simpleCaptcha)

Crear un componente para la nueva configuración

Como base, cogemos el fichero com.liferay.captcha.configuration.CaptchaConfiguration.java del código fuente de Liferay.

El único cambio que hay que hacer es añadir un nuevo captchaEngine

@Meta.AD(
  deflt = "com.liferay.captcha.simplecaptcha.SimpleCaptchaImpl",
  description = "captcha-engine-help", name = "captcha-engine",
  optionLabels = {"SimpleCaptcha", "reCAPTCHA", "reCAPTCHAv3"},
  optionValues = {
     "com.liferay.captcha.simplecaptcha.SimpleCaptchaImpl",
     "com.liferay.captcha.recaptcha.ReCaptchaImpl",
     "com.miguelangeljulvez.recaptcha.ReCaptchaV3Impl"
  },
  required = false
)
public String captchaEngine();

A continuación, vamos a crear el componente osgi que se encargará de validar recaptcha v3. Para ello habría que crear una clase que implemente la interfaz com.liferay.portal.kernel.captcha.Captcha

Crear un componente para procesar los datos de reCAPTCHAv3

Yo, por comodidad, voy a heredar de la clase SimpleCaptchaImpl que ya la implementa (podría haber usado la clase RecaptchaImpl sin ningún problema) y voy a modificar el método validateChallenge(HttpServletRequest request) para que recoja el nuevo parámetro que contendrá la acción indicada que veremos más adelante y además indicaremos que si el score es menor o igual a 0.1 consideramos que el captcha ha fallado, es decir, es un robot.

String reCaptchaResponse = ParamUtil.getString(request, "g-recaptcha-response");

String reCaptchaResponseAction = ParamUtil.getString(request, "g-recaptcha-response-action");

...

if (!StringUtil.equalsIgnoreCase(action, reCaptchaResponseAction)) {
  _log.info("El action no coincide");
  throw new CaptchaTextException();
}

if (score > 0.1) {
  return true;
}

Y ya por último, vamos a usar el punto de extensión “/html/common/themes/top_head.jsp#pre“ para inyectar el código javascript que cargará recaptcha v3 en todas las páginas del portal (date cuenta que una misma página puede tener múltiples acciones de recaptcha)

Añadir JS al punto de extensión

El código a inyectar es el mismo que aparece en la documentación de recaptchav3 pero obteniendo los datos de la configuración de Liferay.

<script src="https://www.google.com/recaptcha/api.js?render=reCAPTCHA_site_key"></script>
  <script>
  grecaptcha.ready(function() {
    grecaptcha.execute('reCAPTCHA_site_key', {action: 'homepage'}).then(function(token) {
         ...
      });
  });
  </script>

Para facilitarte la vida, he creado un proyecto en github con la implementación realizada para que simplemente la compiles y despliegues en tu instalación. Puedes verlo pulsando aquí

Ahora que ya tenemos reCAPTCHAv3, vamos a configurarlo y a añadir su validación en un formulario nuestro propio en un fichero .jsp

Configurar reCAPTCHAv3 en Liferay

Para configurar reCAPTCHAv3, necesitamos generar una claves. Para ello, accede a https://www.google.com/recaptcha/admin#list (tendrás que estar autenticado en los servicios de google) y registra un nuevo sitio.

Registrar nuevo sitio

Y obtén las claves (las claves de la siguiente imagen ya no son válidas, obviamente)

Obtención de claves

Esas claves, las vas a tener que copiar y pegar en la configuración de liferay.

Panel de control -> Configuraciones del sistema -> Seguridad -> Herramientas de seguridad

Configuración en Liferay

y seleccionas reCAPTCHAv3 e introduces las claves pública y privada generadas en el paso anterior

Ahora que tenemos todo configurado, lo único que tenemos que hacer es añadir un código javascript que obtendrá un token de google y a continuación, en el action de nuestro controlador en el que recogemos los datos del formulario, validar este token (que terminará llamando a la función de validación que modificamos en el componente osgi recaptchaV3Impl)

Configurar un formulario para usar reCAPTCHAv3

Lo primero vamos a añadir la dependencia que necesitamos en el build.gradle del nuevo module

compileOnly group: 'com.liferay', name: 'com.liferay.captcha.api', version: '2.0.3'

y creamos el formulario

<aui:form action="${myActionURL}" method="post" name="fm">

  <liferay-ui:error exception="<%= CaptchaTextException.class %>" message="text-verification-failed" />
  <liferay-ui:error exception="<%= CaptchaConfigurationException.class %>" message="a-captcha-error-occurred-please-contact-an-administrator" />
  <liferay-ui:error exception="<%= CaptchaException.class %>" message="a-captcha-error-occurred-please-contact-an-administrator" />

<!-- Resto de campos del formulario -->

</aui:form>

y añadimos al final del .jsp lo siguiente

<script data-senna-track="temporary">
  grecaptcha.ready(function() {
     grecaptcha.execute('<%= captchaConfiguration.reCaptchaPublicKey() %>', {action: 'contacto'}).then(function(token) {
        $('#<portlet:namespace />fm').prepend('<input type="hidden" name="g-recaptcha-response" value="' + token + '">');
        $('#<portlet:namespace />fm').prepend('<input type="hidden" name="g-recaptcha-response-action" value="contacto">');
     });
  });
</script>
Este último código estaría mejor en un taglib que lo hiciera todo por nosotros. Que recibiera la action (que puede ser cualquier nombre establecido por el usuario) y generará los input del formulario automáticamente. Pero bueno, eso es materia para otro post.

Para poder acceder a la configuración del captcha donde está la public key de recaptchaV3 que hay en el paso anterior, podemos usar el siguiente código

CaptchaConfiguration captchaConfiguration = ConfigurationProviderUtil.getSystemConfiguration(CaptchaConfiguration.class);

Y ya por último, lo único que nos queda es validar el captcha en el controlador antes de realizar la inserción de los datos en bbdd

try {
   CaptchaUtil.check(actionRequest); //Comprobamos el captcha
  _fooLocalService.addFoo(foo);
} catch (CaptchaConfigurationException e) {
   SessionErrors.add(actionRequest, CaptchaConfigurationException.class.getName());
} catch (CaptchaTextException e) {
   SessionErrors.add(actionRequest, CaptchaTextException.class.getName());
} catch (CaptchaException e) {
   SessionErrors.add(actionRequest, CaptchaException.class.getName());
}

He estado haciendo pruebas para intentar averiguar qué hace google para asignar el score a los usuarios pero todavía no lo tengo claro porque he visto resultados contradictorios.

Analíticas

En principio, logré obtener un 0.1 (casi robot) al acceder con Lynx (navegador modo texto) a la página de contacto directamente y sin cookies y un 0.7 con un firefox sin cookies pero pasando primero por la home de mi web y un 0.9 (casi super hombre de Nietzsche) con chrome logueado en google. Pero también me pareció ver un 0.1 en una prueba que hice desde el chrome con cookies y estando logueado. No se. Voy a estar unos días vigilando el comportamiento del formulario de contacto de mi web para ver realmente cómo está funcionando

Disclaimer: reCAPTCHAv3 es muy nuevo y todavía no hay mucha documentación así que es posible que haya algún error de concepto en esta implementación. A medida que vaya viendo su evolución en las pruebas que estoy haciendo actualizaré el blog si encontrara algún problema así que suscríbete al feed de este blog para estar al tanto de las novedades.