Servelts y JSP
Manejar Cookies
- Introducción a los Cookies
- El API Servlet Cookie
- Algunas utilidades menores de Cookies
- Ejemplo: Un Interface de Motor de Búsqueda Personalizado
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:
- Identificar a un usuario durante una sesión de comercio electrónico. Muchas tiendas on-line usan una "carta de compra" a la que se añaden los articulos que selecciona el usuario, luego continúa comprando. Como la conexión HTTP se cierra después de enviar cada página, cuando el usuario selecciona un nuevo item para su carta. ¿cómo sabe la tienda que él es el mismo usuario que añadió el ítem anterior a su carta? Los cookies son una buena forma de conseguir esto. De hecho, son tan útiles que los sevlets tienen un API especifico para esto, y los los autores de servlets no necesitan manipular las cookies directametne haciendo uso de él. Esto se explica en la página sigueinte .
- Evitar el nombre de usuario y la password. Muchas grandes sites requieren registrarse para poder usar sus servicios, pero es un inconveniente recordar el nombre de usuario y la password. Los cookies son una buena alternativa para sites de baja seguridad. Cuando un usuario se registra, se envía una cookie con un único ID de usuario. Cuando el cliente se reconecta más tarde, se devuelve el ID de usuario, el servidor los busca, determina que pertenece a un usuario registrado, y no requiere explícitamente el nombre de usuario y la password.
- Personalizar una Site. Muchos "portales" nos permiten personalizar el aspecto de la página principal. Usan cookies para recordar lo que queremos, para que obtengamos el mismo resultado inicial la próxima vez.
- Publicidad enfocada. Los motores de búsqueda cobran más a sus clientes por mostrar anuncios "directos" que por anuncios "aleatorios". Es decir, si hacemos una búsqueda sobre "Java Servlets", un motor de búsquea cobra más por un anuncio de un entorno de desarrollo de servelts que por el de una agencia de viajes on-line. El problema es que tienen que enseñarnos un anuncio aleatorio la primera vez que llegamos y no hemos realizado la búsqueda, así como cuando buscamos algo que no corresponde con las caegorías de anuncios. Los cookies les permiten recordar "Oh, esta es la persona que buscó por esto y esto enteriormente" y muestra un anunció apropiado (lease "caro") en vez de uno aleatorio (lease "barato").
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.
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.
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);
}
}
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
Ozito