Servelts y JSP

Manejar Cookies


  1. Introducción a los Cookies
  2. El API Servlet Cookie
  3. Algunas utilidades menores de Cookies
  4. Ejemplo: Un Interface de Motor de Búsqueda Personalizado

1. Introducción a los Cookies

Los Cookies son pequeños trozos de información textual que el servidor Web envía al navegador y que el navegador devuelve sin modificar cuando visita más tarde la misma site o dominio. Dejando que el servidor lea la información envíada previamente al cliente, la site puede proporcionar a los visitantes un número de conveniencias: Ahora, añadir conveniencias al usuario y añadir valor al proporietario de la site es el propósito que hay detrás de las cookies. Y no te creas todas las informaciones, las cookies no son un serio problema de seguridad. Las cookies nunca son interpretadas o ejecutadas de ninguna forma, y no pueden usarse para insertar virus o atacar nuestro sistema. Además, como los navegadores sólo aceptan 20 cookies por site y 300 cookies en total, cada cookies está limita a 4Kb, las cookies no se pueden usar para llenar el disco duro o lanzar otros ataques de denegación de servicio.

Sin embargo, aunque no presentan un serio problema de seguridad, si que presentan un significante problema de privacidad. Primero, algunas personas no les gusta que los motores de búsqueda puedan recordar que ellos son las personas que usualmente buscan por uno u otro tópico. Incluso peor, dos motores de búsqueda pueden compartir datos sobre usuarios cargando pequeñas imágenes de una tercera parte que usa los cookies y comparte los datos con los dos motores de búsqueda. (Sin embargo, Netscape tiene una bonita caracterísitca que permite rechazar las cookies de otros sites distintos del que nos conectamos, pero sin desactivar las cookies totalmente). Este truco peude incluso ser explotado mediante email si usamos un programa de correo conpatible HTML que soporte Cookies, como lo hace Outlook Express. Así, la gente podría enviar un email que carge imágenes, adjuntar cookies a esas imágenes, y luego identificar nuesta dirección email y todo cuando vamos a su site.

O, una site que podría tener un nivel de seguridad muy superior al estándard podría permitir a los usuarios saltarse el nombre y la password mediante cookies. Por ejemplo, algunas de las grandes librerías on-line usan cookies para recordar a sus usuarios, y nos permite hacer pedidos sin tener que reintroducir nuestra información personal. Sin embargo, no muestran realmente el número completo de nuestra tarjeta de crédito, y sólo permiten envíar libros a la dirección que fue introducida cuando introducimos el número completo de la tarjeta de crédito o el nombre de usuario y la password. Y como resultado, alguien que use nuestro ordenador (o que robe el fichero cookie) lo único que podría hacer sería un enorme pedido de libros con nuestra tarjeta de crédito que nos llegaría a casa, donde podrían ser rechazados. Sin embargo, las pequeñas compañias no suelen ser tan cuidadosas, y el acceso de alguien a nuestro ordenador o al fichero de cookies puede resultar en una pérdida de información personal importante. Incluso peor, las sites incompetentes podrían embeber el propio número de tarjeta de crédito y otra información sensible directamente dentro de cookies, en vez de usar identificadores inocuos que sólo se enlazan con los usuarios reales en el servidor.

El punto de todo esto es doble. Primero, debido a los problemas reales de privacidad, algunos usuarios desactivan las cookies. Por eso, incluso anque usemos cookies para dar valor añadido a nuestra site, nuestra site no debería depender de ellas. Segundo, como autor de servelts que podemos usar cookies, no deberíamos confiar a los cookies información particularmente sensible, ya que el usuario podría correr el riesgo de que alguien accediera a su ordenador o a sus ficheros de cookies.

2. El API Servlet Cookie

Para envíar cookies al cliente, un servlet debería crear uno o más cookies con los nombres y valores apropiados mediante new Cookie(name, value) seleccionar cualquier atributo opcional mediante cookie.setXxx, y añadir los cookies a la cabecera de respuesta mediante response.addCookie(cookie). Para leer cookies entrantes, llamamos request.getCookies(), que devuelve un array de objetos Cookie. En la mayoría de los casos, recorremos el array hasta encontrar aquella cuyo nombre (getName) corresponda con el nombre que tenemos en mente. luego llamamos a getValue sobre ese Cookie para ver el valor asociado con ese nombre.

2.1 Crear Cookies

Un objeto Cookie se crea llamando al constructor Cookie, que toma dos strings: el nombre y el valor del cookie. Ni el nombre ni el valor deberían contener espacios en blanco y ninguno de estos caracteres:
  [ ] ( ) = , " / ? @ : ;

2.2 Leer y Especificar Atributos de Cookie

Antes de añadir el cookie a la cabecera saliente, podemos buscar o seleccionar atributos del cookie. Aquí hay un sumario:
getComment/setComment
Obtiene/Selecciona un comentario asociado con este cookie.

getDomain/setDomain
Obtiene/Selecciona el dominio al que se aplica el cookie. Podemos usar este método para instruir al navegador a que las devuelva a otros host en el mismo dominio. Observa que el dominio debe empezar por un punto (por ejemplo .prenhall.com), y debe contener dos puntos para dominoos que no sean de paises como .com, .edu, y .gov, y tres puntos para dominios de país como .co.uk y .edu.es.

getMaxAge/setMaxAge
Obtiene/Selecciona el tiempo (en segundos) que debe pasar hasta que expire el cookie. Si no lo seleccionamos, el cookie sólo vivirá durante la sesión actual (es decir, hasta que el usuario salga del navegador), y no será almacenado en disco.

getName/setName
Obtiene/Selecciona el nombre del cookie. El nombre y el valor son dos piezas con las que siempre tenemos que tener cuidado. Como el método getCookies de HttpServletRequest devuelve un array de objetos Cookie, es muy común recorrer el array hasta que tengamos un nombre particular, luego chequeamos el valor con getValue.

getPath/setPath
Obtiene/Selecciona el path al que se aplica este cookie. Si no especificamos esto el cookie se devuelve para todas las URLs del mismo directorio que la página actual, así como para todos sus subdirectorios. Este método puede usarse para especificar algo más general. Por ejemplo someCookie.setPath("/") especifica que todas las páginas del servidor deberían recibir el cookie. Observa que el path especificado debe incluir el directorio actual.

getSecure/setSecure
Obtiene/Selecciona el valor boolean indicando si el código sólo debería enviarse sobre conexiones encriptada (SSL).

getValue/setValue
Obtiene/Selecciona el valor asociado con el cookie. En algunos casos el nombre se usa como una bandera booleana, y su valor es ignorado. (es decir, la existencia del nombre significa True).

getVersion/setVersion
Obtiene/Selecciona la versión del protocolo del cookie. La versión 0, por defecto, se adhiere a la especificación original Netscape. La versión 1, todavía no soportada muy ampliamente, se adhiere a la RFC 2109.

2.3 Situar los Cookies en las cabeceras de respuesta

El cookie se añade a la cabecera de respuesta Set-Cookie por medio del método addCookie de HttpServletResponse. Aquí hay un ejemplo:
  Cookie userCookie = new Cookie("user", "uid1234");
  response.addCookie(userCookie);

2.4 Leer las Cookies desde el cliente

Para envíar el cookie al cliente, creamos un Cookie luego usamos addCookie para enviar una cabecera de respuesta HTTP Set-Cookie. Para leer los cookies desde el cliente. llamamos a getCookies sobre el HttpServletRequest. Esto devuelve un array de objetos Cookie correspondiendo con los valores que vinieron de la cebcera de petición HTTP Cookie. Una vez que tengamos este array, lo recorremos, llamando al método getName sobre cada Cookie hasta encontrar uno que corresponda con el nombre que buscamos. Luego llamamos a getValue sobre la cookie correspondiente, haciendo algún precesamiento específico con el valor resultante.

3. Algunas utilidades menores de Cookies

Aquí tenemos algunas sencillas pero útiles utilidades para tratar con cookies.

3.1 Obtener el Valor de un Cookie con un Nombre Específico

Aquí tenemos una sección de ServletUtilities.java que simplifica ligeramente la recuperación del valor de una cookie dando un nombre de cookie, recorriendo el array de objetos Cookie disponible, devolviendo el valor de cualquier Cookie cuyo nombre corresponda con la entrada. Si no hay ningún nombre que corresponda, se devolverá el valor por defecto.
  public static String getCookieValue(Cookie[] cookies,
                                      String cookieName,
                                      String defaultValue) {
    for(int i=0; i<cookies.length; i++) {
      Cookie cookie = cookies[i];
      if (cookieName.equals(cookie.getName()))
        return(cookie.getValue());
    }
    return(defaultValue);
  }

3.2 LongLivedCookie.java (Descarga el código fuente)

Aquí tenemos una pequeña clase que podemos usar en lugar de Cookie si queremos que la cookie persista automáticamente cuando el cliente salga de su navegador:
package hall;

import javax.servlet.http.*;

public class LongLivedCookie extends Cookie {
  public static final int SECONDS_PER_YEAR = 60*60*24*365;
  
  public LongLivedCookie(String name, String value) {
    super(name, value);
    setMaxAge(SECONDS_PER_YEAR);
  }
}

4. Ejemplo: Un Interface de Motor de Búsqueda Personalizado

Aquí tenemos una variante del ejemplo SearchEngines mostrado en la página anterior. En esta versión, el inicio es generado dinámicamente en lugar de venir de un fichero HTML estático. Luego, el servlet que lee los parámetros y los reenvía al motor de búsqueda apropiado también devuelve cookies al cliente que lista estos valores. La siguiente vez que el cliente visita la site, se usan los valores del cookie para precargar los campos del formulario con las entradas usadas más recientemente.

4.1 SearchEnginesFrontEnd.java

Este servlet construye la página del formulario para el servlet del motor de búsqueda. A primera vista, se parece a la página vista en la página HTML estática presentada en la página anterior. Sin embargo, aquí, se recuerdan los valor seleccionados en cookies (seleccionado por el servlet CustomizedSearchEngines a los que está página envía los datos), por eso si el usuario vuelve a la misma página después de algún tiempo (incluso después de apagar el navegador), la página se inicializa con los valores de la búsqueda anterior.

También puedes descargar el código fuente.

package hall;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.*;

public class SearchEnginesFrontEnd extends HttpServlet {
  public void doGet(HttpServletRequest request,
                    HttpServletResponse response)
      throws ServletException, IOException {
    Cookie[] cookies = request.getCookies();
    String searchString =
      ServletUtilities.getCookieValue(cookies,
                                      "searchString",
                                      "Java Programming");
    String numResults =
      ServletUtilities.getCookieValue(cookies,
                                      "numResults",
                                      "10");
    String searchEngine =
      ServletUtilities.getCookieValue(cookies,
                                      "searchEngine",
                                      "google");
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    String title = "Searching the Web";
    out.println(ServletUtilities.headWithTitle(title) +
                "<BODY BGCOLOR=\"#FDF5E6\">\n" +
                "<H1 ALIGN=\"CENTER\">Searching the Web</H1>\n" +
                "\n" +
                "<FORM ACTION=\"/servlet/hall.CustomizedSearchEngines\">\n" +
                "<CENTER>\n" +
                "Search String:\n" +
                "<INPUT TYPE=\"TEXT\" NAME=\"searchString\"\n" +
                "       VALUE=\"" + searchString + "\"><BR>\n" +
                "Results to Show Per Page:\n" +
                "<INPUT TYPE=\"TEXT\" NAME=\"numResults\"\n" + 
                "       VALUE=" + numResults + " SIZE=3><BR>\n" +
                "<INPUT TYPE=\"RADIO\" NAME=\"searchEngine\"\n" +
                "       VALUE=\"google\"" +
                checked("google", searchEngine) + ">\n" +
                "Google |\n" +
                "<INPUT TYPE=\"RADIO\" NAME=\"searchEngine\"\n" +
                "       VALUE=\"infoseek\"" +
                checked("infoseek", searchEngine) + ">\n" +
                "Infoseek |\n" +
                "<INPUT TYPE=\"RADIO\" NAME=\"searchEngine\"\n" +
                "       VALUE=\"lycos\"" +
                checked("lycos", searchEngine) + ">\n" +
                "Lycos |\n" +
                "<INPUT TYPE=\"RADIO\" NAME=\"searchEngine\"\n" +
                "       VALUE=\"hotbot\"" +
                checked("hotbot", searchEngine) + ">\n" +
                "HotBot\n" +
                "<BR>\n" +
                "<INPUT TYPE=\"SUBMIT\" VALUE=\"Search\">\n" +
                "</CENTER>\n" +
                "</FORM>\n" +
                "\n" +
                "</BODY>\n" +
                "</HTML>\n");
  }

  private String checked(String name1, String name2) {
    if (name1.equals(name2))
      return(" CHECKED");
    else
      return("");
  }
}

4.2 CustomizedSearchEngines.java

El servlet anterior SearchEnginesFrontEnd envía sus datos al servlet CustomizedSearchEngines. En muchos aspectos, es igual que el servlet SearchEngines mostrado en la página anterior. Sin embargo, además de construir una URL para un motor de búsqueda y envíar una redirección de respuesta al cliente, el servlet también envía cookies recordando los datos del usuario. Estos cookies, serán utilizados por el servelt para construir la página para inicializar las entradas del formulario HTML.
package hall;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.*;

/** A varition of the SearchEngine servlet that uses
 *  cookies to remember users choices. These values
 *  are then used by the SearchEngineFrontEnd servlet
 *  to create the form-based front end with these
 *  choices preset.
 *  

* Part of tutorial on servlets and JSP that appears at * http://www.apl.jhu.edu/~hall/java/Servlet-Tutorial/ * 1999 Marty Hall; may be freely used or adapted. */ public class CustomizedSearchEngines extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String searchString = request.getParameter("searchString"); Cookie searchStringCookie = new LongLivedCookie("searchString", searchString); response.addCookie(searchStringCookie); searchString = URLEncoder.encode(searchString); String numResults = request.getParameter("numResults"); Cookie numResultsCookie = new LongLivedCookie("numResults", numResults); response.addCookie(numResultsCookie); String searchEngine = request.getParameter("searchEngine"); Cookie searchEngineCookie = new LongLivedCookie("searchEngine", searchEngine); response.addCookie(searchEngineCookie); SearchSpec[] commonSpecs = SearchSpec.getCommonSpecs(); for(int i=0; i<commonSpecs.length; i++) { SearchSpec searchSpec = commonSpecs[i]; if (searchSpec.getName().equals(searchEngine)) { String url = searchSpec.makeURL(searchString, numResults); response.sendRedirect(url); return; } } response.sendError(response.SC_NOT_FOUND, "No recognized search engine specified."); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }

4.3 Salidas de SearchEnginesFrontEnd

Aquí tenemos la vista de la página de nuestro buscador después de que el usuario haya vuelto a la misma página después de hacer una consulta:

4.4 CustomizedSearchEngines Output

height=743

Ozito