Cómo usar cookies en portales web con Java y Spring

Las cookies nos permiten guardar información para mejorar la navegación y las usan también herramientas externas como Google Analytics. Vamos a ver cómo usarlas con Java y Spring Security y prevenir problemas legales.

Código para usar cookies

El código Java es sencillo y se puede usar en cualquier controlador. Para crearlas sólo hay que crear un objeto Cookie, indicarle el nombre que va a tener y su valor, y añadirlo a la respuesta que se envía al servidor. Es recomendable añadir también el tiempo máximo que estará activa. Este tiempo se indicará en segundos, por lo que si se quiere poner un mes sería 2592000 (30 días x 24 horas x 60 minutos x 60 segundos), y es recomendable ponerlo en un fichero de properties para que sea fácil cambiarlo.

public class CookieHelper{
  /**
  * Adds a cookie to the web browser
  * */
  public static void saveCookie(String cookieName, String value, int maxAge, HttpServletResponse response) {
    Cookie cookie = new Cookie(cookieName, value);
    cookie.setMaxAge(maxAge);
    response.addCookie(cookie);
  }

Para leer una cookie se acceden a las que tiene cargadas el navegador, se recorren hasta encontrar la que buscamos y se extrae su valor.

  /**
  * Reads a cookie from the web browser
  * */
  public static String getCookieValue(String cookieName, HttpServletRequest request) {
    String value = null;
    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
      int i = 0;
      boolean cookieExists = false;
      while (!cookieExists && i < cookies.length) {
        if (cookies[i].getName().equals(cookieName)) {
          cookieExists = true;
          value = cookies[i].getValue();
        } else {
          i++;
        }
      }
    }
    return value;
  }
}

Ver si se han creado con éxito

Cada navegador web tiene una forma distinta de mostrar y borrar las cookies guardadas. En Firefox se puede acceder en Opciones → Privacidad → Eliminar cookies de forma individual. Si se está probando en nuestro equipo saldrá dentro de localhost, y si está en un servidor aparecerá en la opción que tiene en su nombre su url.

Añadirlas al login de Spring Security

Lo habitual es hacer un formulario de login que tenga dos campos input, j_username y j_password, y enviarlos por POST a j_spring_security_check después de cifrar la contraseña con SHA1 con javascript. Si queremos podemos añadir otro campo como un select para elegir el idioma. La cuestión es, ¿cómo se lee el contenido de los nuevos campos?

Para ello se puede crear una clase que capture el evento de login con éxito o modificarla si ya hay una en el proyecto. Esta clase implementaría AuthenticationSuccessHandler y heredaría una clase de Spring que maneje los logins exitosos como SavedRequestAwareAuthenticationSuccessHandler.

Al leer los parámetros hay que tener cuidado porque el mapa devuelto no es un Map<String,String>, sino un Map<String,String[]> ya que cada parámetro puede tener varios valores como pasaría con un campo select múltiple. Es importante añadir la cookie ANTES de hacer el redirect o de llamar a super.onAuthenticationSuccess(…) porque en ese momento ya se habrían devuelto los datos y no funcionaría.

public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler implements
AuthenticationSuccessHandler {
  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    // saves a cookie with the selected language
    // this must be done BEFORE the sendRedirect
    Map<String, String[]> parameterMap = request.getParameterMap();
    if (parameterMap.containsKey("language")) {
      saveLanguageCookie(parameterMap.get("language")[0], response);
    }
    ...
    super.onAuthenticationSuccess(request, response, authentication);
  }
  private void saveLanguageCookie(String language, HttpServletResponse response) {
    // obtains the maximum time that a cookie will be available in the web browser
    String maxAgeValueText = messageService.getValue("cookieMaxAge").trim()
    int maxAge = Integer.parseInt(maxAgeValueText);

    // saves a cookie with the value of the language selected
    String cookieName = messageService.getValue("languageCookieName").trim();
    CookieHelper.saveCookie(cookieName, language, maxAge, response);
  }
}

Una vez creada habrá que añadirla al fichero de configuración de Spring Security creando el bean correspondiente e indicando que se use cuando el usuario rellene con éxito el formulario de login:

<security:http ...>
  <security:form-login ... authentication-success-handler-ref="customAuthenticationSuccessHandler"/>
  ...
</security:http>

<bean id="customAuthenticationSuccessHandler" class="com.rafaborrego.security.CustomAuthenticationSuccessHandler">
  ...
</bean>

Hacer un texto legal de cookies

La legislación de muchos países está prohibiendo que las páginas web usen cookies sin pedir antes permiso al usuario. Lo más sencillo para pedirlo es poner un div al principio o al final de cada página que lo avise y, cuando el usuario ha aceptado, guardar una cookie para que ya no se muestre el aviso. El formato general de este div es:

<c:if test="${!hasAcceptedCookies}">
  <div id="cookieArea">
    <!-- texto resumido cookies -->
    <!-- enlace al aviso legal -->
    <!-- botón aceptar cookies -->
  </div>
</c:if>

El botón dirigiría a un controller que guarde la cookie. Por ejemplo:

@Controller
public class CookieController extends GenericController {

  @RequestMapping(value = {"acceptsCookies.do", PRIVATE + "acceptsCookies.do"}, method = RequestMethod.GET)
  public String acceptsCookies(HttpServletResponse response) {
    //obtains the maximum time that a cookie will be available in the web browser
    String maxAgeText = messageService.getValue("cookieMaxAge").trim();
    int maxAge = Integer.parseInt(maxAgeText);

    //obtains the cookie name from the properties file and creates the cookie
    String cookieName = messageService.getValue("userAcceptsCookiesCookieName").trim();
    CookieHelper.saveCookie(cookieName, "YES", maxAge, response);

    return "redirect:...";
  }
}

Y en el resto de controllers habría que poner un ModelAttribute para ver si ha aceptado. Así se comprobaría para todas las urls:

@ModelAttribute("hasAcceptedCookies")
public boolean hasAcceptedCookies(HttpServletRequest request) {
  String cookieName = messageService.getValue("userAcceptsCookiesCookieName").trim();
  boolean hasAcceptedCookies = existsCookie(cookieName, request);
  return hasAcceptedCookies;
}

Uso con herramientas externas

Muchas herramientas añaden cookies sin darnos cuenta, como Google Analytics que nos ayuda a ver que páginas interesan más a los usuarios. Podemos tener problemas legales si no evitamos que se añadan antes de que el usuario autorice a que usemos cookies. Por ello debemos usar un código como el anterior para que no se guarden hasta que el usuario acepte e indicar que se usan en el aviso legal de la página.

Aviso: lo que he indicado de aviso legal es básico y puede no ser suficiente para tu proyecto web, por lo que no me hago responsable de posibles sanciones. Recomiendo consultar a un especialista legal o buscar información sobre la legislación de tu país como la LOPD.

Como siempre, podéis dejar vuestras dudas y comentarios, y suscribiros para obtener nuevos posts por email:[mc4wp_form]

Rafael Borrego

Ingeniero informático especializado en startups y en ayudarles a crecer

Facebook Twitter LinkedIn 

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

Puedes usar las siguientes etiquetas y atributos HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>