sábado, 20 de febrero de 2016

Mantener el estado en una aplicación web

Doc template

Básico


Mantener el estado en una aplicación web

Sessions y Cookie


Navegador, Servidor, Cookies y HTTP

Una manera de mantener el estado de una conexion en el protocolo HTTP 1.1 se consigue mediante el envio de IDs de sesion entre el navegador y el servidor al que conecta.  De esta manera el navegador conserva dicho ID para futuras conexiones. Se trata de un mecanismo, mediante el que se envía reciben un conjunto arbitrario de datos en conexiones HTTP a través de la cabecera de respuesta "Set-cookie", y que el cliente almacena localmente y re-envía en cada nueva conexion al servidor a través de la cabecera "Cookie" de la request. Entre los atributos que contiene una Cookie, puede ser casi cualquier dato string, establecido por el servidor, solemos encontrar el nombre del dominio, una ruta dentro de dicho servidor,y un tiempo de expiración o duración máxima, un flag que indica si se trata de una cookie de seguridad y un flag para indicar si se trata de HTTP-only.

Cada vez que el navegador realiza una petición, busca en su almacen de cookies que se corresponden con el nombre de dominio y la ruta  y envía la cookie junto con los datos de la request. El atributo Expires nos da un valor absoluto de fecha de caducidad mientras que Max-Age, ambos mutuamente exclusivos, indica el numero de segundos que han de pasar hasta que la cookie expire.  Si el navegador al leer la Cookie comprueba que ha caducado
la borra automaticamente, por otro lado si dicha cookie no contiene alguno de estos atributos, se borra  cuando cerramos el navegador.

Una cookie puede contener el atributo Secure, entonces, el navegador la enviará unicamente  a través del protocolo HTTPS, lo cual asegura que se enviara cifrada.  El atributo HTTP-only restringe el envío a peticiones de navegador (solo a través de la request GET-POST), otros plugins no tendrian acceso a enviar dicha cookie. Servidores Web y de aplicaciones, usan las cookies para almacenar el estado de las conexiones cliente (session). En los servidores de aplicaciones JEE, el nombre por defecto es  JSESSIONID. Si analizamos las cabeceras de request-response
encontrariamos algo parecido a lo siguiente:

Request 1
GET /users/home HTTP/1.1
Host: www.servidor.com
Response 1
HTTP/1.1 302 Moved Temporarily
Location: https://www.servidor.com/access/login
Set-Cookie: JSESSIONID=NRxclGg2vG7MdlLn; Domain=.servidor.com; Path=/; HttpOnly

Request 2
GET /access/login HTTP/1.1
Host: www.servidor.com
Cookie: JSESSIONID=NRxclGg2vG7MdlLn
Response 2
HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Content-Length: 21765

Request 3
POST /access/login HTTP/1.1
Host: www.servidor.com
Cookie: JSESSIONID=NRxclGg2vG7MdlLn
Response 3
HTTP/1.1 302 Moved Temporarily
Location: http://www.servidor.com/users/home
Set-Cookie: username=Jose; Expires=Wed, 02-Jun-2021 12:15:47 GMT;
Domain=.servidor.com; Path=/; HttpOnly

Request 4
GET /users/home HTTP/1.1
Host: www.servidor.com
Cookie: JSESSIONID=NRxclGg2vG7MdlLn; username=Jose
Response 4
HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Content-Length: 2653380 

De la misma forma que las cabeceras Set-Cookie en la respuesta se usan para enviar cookies a los clientes en response, de manera que se almacenen en el cliente si este lo permite; las cabeceras Cookie en la peticion se usan para enviarlas de vuelta al servidor. En el caso imaginario descrito arriba, el usuario intenta acceder a la direccion /users/home y se le redirige a la pagina de login. Cuando accede a pagina el servidor  incluye un ID de session,
mediante Set-Cookie. Cada vez que el navegador conecta con el servidor, se incluye la cookie JSESSIONID.

Una vez que el usuario se autentifica, el servidor reenvia una cookie con la clave username.

Uno de los problemas que presenta el uso de cookies es que los usuarios pueden desactivar que el navegador las acepte, de esta manera
impedir que se envie el ID de sesion, la manera de pasar por alto esta situacione, es incluirla en la URL. Se trata de una forma comun
de transmitir la sesion. Una aplicación web o un servidor de aplicaciones, busca un patron reconocible en la uri y extrae de ahí los datos
de la sesion. Un ejemplo en PHP:

http://www.servidor.com/usuarios?PHPSESSID=NRxclGg2vG7kI4MdlLn&foo=bar&lang=es

Las aplicaciones JEE usan una tecnica distinta. El session ID se coloca en la última parte de la URI, lo que
no sobrecarga  query string, de esta manera no hay conflictos con otros parametros de la request.

http://www.servidor.com/usuarios;JSESSIONID=NRxclGg2vG7kI4MdlLn?foo=bar&lang=es

Cualquiera de las tecnicas usadas el resultado es el mísmo: Incluir el session ID en la URL para evitar la necesidad de almacenar Cookies,

Deberíamos preguntarnos, de que manera entonces como recoge el navegador el session ID la primera vez,
Una  request URL es unicamente para convenir el session ID desde el navegador al servidor al que conecta.
De donde se obtiene entonces la primera vez? Dicho  session ID  se incluye en cada URL que la aplicacion envia su response,
incluidos los enlaces, actions y redirects.

Para incluir session ID en cada URL,  HttpServletResponse  contiene dos metodos que incluyen el ID de session si es necesario: : encodeURL  y encodeRedirectURL . Para cada  salida de la response que escriba
una URL,  se usa el metodo encodeURL  el cual devuelve la URL correcta. De la misma manera una URL  para una response
de tipo sendRedirect, llamariamos al metodo  encodeRedirectURL, de manera que se incluya el  JSESSIONID, esto sucedera si se da alguno
 de los siguientes casos:

Una session activa en la request en curso. (Bien se realizo una peticion que contenía session ID o bien la
aplicacion creo uno nuevo).

 No  contenia la cookie  JSESSIONID.

La  URL es relativa a la aplicacion.

El descriptor de la aplicacion tiene configurado URL rewriting.

El caso segundo es el mas problematico. La unica forma de detectar si un navegador acepta cookies, es enviar una y comprobar
si es devuelta en la siguiente peticion. Sin embargo, es necesario crear una session para asociar una request con otra; sino, como
podemos saber si la request era la primera que envia otro usuario o es una segunda request del mismo? Asi que de esta manera,
se asume que cuando no exista JSESSIONID en la request, esto nos indica que el navegador no acepta cookies. Lo cual no tiene por que
ser siempre cierto.