viernes, 14 de septiembre de 2012

Cross-Origin Resource Sharing


Guia Cross Origin Resource Sharing


La generalización de AJAX ha supuesto un impulso gigantesco para el desarrollo de aplicaciones web cada vez más importantes. Avanzamos claramente hacia aplicaciones que integran, mediante AJAX, servicios o módulos desarrollados por terceros (Google, Yahoo, Facebook, etc). Sin embargo esta tendencia se ha topado con un problema importante: la política del mismo origen (Same Origin Policy), que impide que se puedan realizar peticiones HTTP desde JavaScript a un dominio diferente.
Además de diferentes hacks que se han ido desarrollando con el tiempopara rodear el problema, existe una especificación del W3C parasolucionarlo: Cross-Origin Resource Sharing (CORS), que podriamostraducir como “Compartición de Recursos entre Origenes”.

Política del Mismo Origen

Same Origin Policy es una importante medida de seguridad queimplementan actualmente todos los navegadores. Significa que una web sólo puede utilizar el objeto XMLHttpRequest parahacer peticiones HTTP AJAX al mismo domino desde el que se cargó lapágina original. Las peticiones a dominios diferentes serán descartadas.
sameOriginPolicy.png
Same Origin Policy impide comunicación AJAX entre origenes diferentes
¿Qué se entiende por un dominio diferente? además del evidente nombredel dominio, tampoco se permite comunicación si cambia el protocolo (porejemplo de http a https) o el puerto.
url.png
Origen: protocolo, dominio y puerto
Esto quiere decir que tampoco nos funcionará una petición desde unapágina cargada de http:www.miweb.com a https:www.miweb.com. Aunque en estecaso lo único que cambia es que utilizamos el protocolo seguro https,pero no es posible.
La necesidad de poder hacer estas peticiones como parte de las aplicaciones actuales ha llevado a buscar varias soluciones:
  • JSONP
  • Servidor Proxy
  • uso de iFrames
  • CORS

Las tres primeras son en realidad diferentes formas de ‘rodear’ el problema en vez de solucionarlo, sólo CORS trata de definir un estándar para crear una forma de comunicación por AJAX entre diferentes dominios.

¿En qué consiste el estándar Cross-Origin Resource Sharing?

El estándar propone incluir nuevas cabeceras HTTP en la comunicacióncliente-servidor para saber si se debe enviar (servidor) o mostrar(navegador) un recurso concreto, en función del origen de la petición.
Laidea es que mediante la información que se intercambia en las cabecerasHTTP el servidor y el navegador puedan decidir si esa petición, desdeese origen, puede permitirse.
EL navegador, cuando se va arealizar una petición asíncrona a un dominio diferente, debe incluirautomáticamente la cabecera ORIGIN en la petición:Quote:
Origin: http://www.sitio1.com

Esta cabecera indicará al servidor el dominio desde el que se está haciendo la petición (desde el que se recibió la página original). El servidor tendrá una lista de dominios permitidos y, si este está en la lista, devolverá el recurso solicitado incluyendo en la respuesta la nueva cabecera Access-Control-Allow-Origin:Quote:
Access-Control-Allow-Origin: http://www.sitio1.com

Con esta cabecera el servidor indica el origen al que le permite leer este contenido. El navegador siempre comprobará esta cabecera. Si no se recibe o no indica el dominio correcto, bloqueará la respuesta para no permitir acceso al DOM a ningún script procedente de un dominio ‘extraño’.
Este es el funcionamiento básico. El estándar define también otro tipo de petición más compleja, que requiere un intercambio previo de cabeceras para decidir si puede enviarse. Vamos a ver a continuación los dos casos en detalle.
Petición simple
Una petición se considera simple cuando:
  • Utilizamos el método GET
  • Utilizamos POST para enviar datos pero el tipo de contenido (Content-Type) es: application/x-www-form-urlencoded, multipart/form-data, o text/plain
  • No incluimos cabeceras HTTP personalizadas en la petición

En este caso el procedimiento es tan simple como el que hemos descrito antes.
Supongamosque vamos a hacer una petición GET mediante AJAX, utilizando el objetoXMLHttpRequest, desde una página cargada de www.sitio1.com. El destinode la petición es www.sitio2.com/utilidad.php
El navegadorincluirá automáticamente la nueva cabecera HTTP ORIGIN al detectar unintento de comunicación a un dominio diferente.
La petición HTTP, entre otras cabeceras, enviará:Quote:
GET /utilidad.php HTTP/1.1Host: www.sitio2.com...Origin: http://www.sitio1.com

La última linea es la que incluyen los navegadores que soportan el nuevoestándar, indicando al servidor que esta petición llega desde contenidocargado desde otro dominio (www.sitio1.com).
El servidor comprobará que el dominio www.sitio1.com está en la lista de aceptados y enviará la respuesta, por ejemplo:Quote:
HTTP/1.1 200 OK...Access-Control-Allow-Origin: http://www.sitio1.com[Data]

La cabecera Access-Control-Allow-Origin se incluye como parte de CORS, para indicar al navegador que esta información puede ser leido por el origen indicado. Si esta cabecera no aparece o el origen indicado no es el de la página cargada, el navegador bloqueará la respuesta.
Este tipo de peticiones simples son soportadas por los navegadores mayoritarios (Internet Explorer 8+, Firefox 3.5+, Safari 4+, and Chrome) y pueden ser suficientes para utilizar servicios públicos de terceros.
Petición con verificación previa (preflighted)
El estándar propone que para peticiones más complejas, que incluyancabeceras HTTP personalizadas, métodos HTTP diferentes de GET o POST ocon Content-Type diferente de los tres listados anteriormente (porejemplo application/xml), se haga una verificación previa.
Laidea es utilizar este método para todas las peticiones que puedanimplicar cambios en datos almacenados en el servidor. Este tipo depeticiones se hacen en dos pasos:

  • Determinar, utilizando la cabecera OPTIONS, si la petición se puede mandar
  • Enviar la petición

  • En la primera parte del proceso, el navegador informará al servidor,mediante cabeceras, de los métodos y cabeceras personalizadas que va autilizar en la petición para ver si el servidor las acepta. Tambiénindicará si pretende enviar información de autenticación o cookies, pero esto lo veremos en otro apartado.
    Un ejemplo de petición podría ser este:Quote:
    OPTIONS /utilidad.php HTTP/1.1Host: www.sitio2.com...Origin: http://www.sitio1.comAccess-Control-Request-Method: POSTAccess-Control-Request-Headers: X-CUSTOM

    OPTIONS no es en realidad una cabecera nueva, forma parte del estándarHTTP 1.1 y se utiliza para obtener más información sobre lasopciones que soporta el servidor.
    Las tres últimas cabeceras síson particulares de CORS y, en este caso, indicarían que se pretendeenviar una petición desde el origen www.sitio1.com, utilizando el método POST eincluyendo la cabecera personalizada X-CUSTOM. El servidor responderáindicando las cabeceras y métodos que permite para este origen.
    La respuesta podría ser algo así:Quote:
    HTTP/1.1 200 OK...Access-Control-Allow-Origin: http://www.sitio1.comAccess-Control-Allow-Methods: POST, GET, OPTIONSAccess-Control-Allow-Headers: X-CUSTOMAccess-Control-Max-Age: 1500000Content-Type: text/plain

    Viendo estas cabeceras el navegador sabe que puede enviar la petición y que el servidor la acepta. Access-Control-Max-Age sirve para indicar el tiempo, en segundos, durante el que esta misma petición puede hacerse directamente, sin el proceso de preflight.
    A continuación se enviará la petición real, incluyendo la cabecera ORIGIN y el servidor enviará la respuesta incluyendo la cabecera Access-Control-Allow-Origin: http://www.sitio1.com como en una petición simple.

    El nuevo XMLHttpRequest de nivel 2

    Las peticiones AJAX se realizan a través del objeto XMLHttpRequest (XHR). Era necesario actualizarlo para permitir las comunicaciones inter-dominios en los casos y las condiciones que contempla el estándar Cross-Origin Resource Sharing. Estas nuevas capacidades, junto con algunas más, se definen en el estándar XMLHttpRequest Nivel 2.
    Microsoft ha decidido, una vez más, no seguir los estándares e implementar la funcionalidad de CORS mediante un objeto diferente llamado XDomainRequest (XDR), en vez de aumentar las capacidades de XMLHttpRequest. Mas adelante veremos las limitaciones que esto supone.
    Afortunadamente los métodos y propiedades que debemos utilizar son comunes por lo que sólo tenemos que decidir qué objeto instanciamos. El interface que podemos usar con cualquiera de los dos objetos es el siguiente:
    • abort() – detiene una petición en marcha
    • onerror – para asignar el callback que se dispara en caso de error
    • onload – para asignar el callback que se dispara en caso de éxito
    • open() – establece el método y URL para la petición HTTP
    • responseText – propiedad en la que se almacenan los datos de la respuesta
    • send() – Para enviar la petición

    Un ejemplo sencillo de cómo podriamos usar el objeto XHR en una petición a un dominio diferente sería:Quote:
    var xhrObject = new XMLHttpRequest();xhrObject.open("get", "http://www.sitio2.com/utilidad.php");xhrObject.onload = function(){alert(xhrObjet.responseText)};xhrObject.send(null);

    El objeto XHR se crea normalmente, y se utiliza open() y send() igual que para peticiones normales. La diferencia es que el evento que se dispara cuando la petición se ha recibido con éxito es load. Por eso definimos nuestra función callback para manejar el resultado en xhrObject.onload. Para una llamada AJAX normal, en el dominio propio, definiriamos el callback en xhrObject.onreadystatechange
    En este ejemplo, cuando la petición se completa, mostramos los datos que hemos recibido en xhrObjet.responseText con un alert().
    El objeto XDomainRequest del IE se utilizaría exactamente igual:Quote:
    var xdrObject = new XDomainRequest();xdrObject.open("get", "http://www.sitio2.com/utilidad.php");xdrObject.onload = function(){alert(xdrObjet.responseText)};xdrObject.send(null);

    En un caso real no es necesario duplicar el código, símplemente tenemos que detectar qué objeto debemos crear.
    Para saber si el navegador en el que estamos soporta el nuevo objeto XHR, con las capacidades CORS, comprobaremos si el objeto tiene definida la propiedad withCredentials, exclusiva del XHR nivel 2 y que más adelante veremos cómo se utiliza. Si el resultado es negativo debemos preguntar si existe el objeto XDomainRequest para saber si estamos en alguna versión del IE con soporte CORS. Si el resultado también es negativo, no tenemos soporte para peticiones AJAX inter-dominios.
    El ejemplo anterior, con un código único sería:Quote:
    /Función para encapsular la detección del objeto que tenemos que usar para AJAX inter-dominios, según el navegador en el que estemos.function createCorsObject(){/Inicialmente creamos XHRvar xhrObject = new XMLHttpRequest();/comprobamos si XHR tiene capacidades CORS o es el antiguoif ("withCredentials" in xhrObject){return xhrObject;}/si es el antiguo, comprobamos si el navegador soporta el objeto XDRelse if (typeof XDomainRequest != "undefined"){xhrObject = new XDomainRequest();} else {xhrObject = null;}return xhrObject;}/obtenemos un objeto para AJAX cross-dominiovar xhrObject = createCorsObject();/si tenemos un objeto válido...if (xhrObject){/definimos los parámetros de la petición HTTPxhrObject.open("get", "http://www.sitio2.com/utilidad.php");/definimos un callback para tratar los datos que recibamosxhrObject.onload = function(){alert(xhrObjet.responseText)};/definimos un callback para tratar el caso de errorxhrObject.onerror = function(){/ código para caso de error};/Enviamos la peticiónxhrObject.send(null);}

    Creo que los comentarios del código son suficiente para explicarlo. En este caso la novedad es que hemos definido también una función para tratar errores.

    Peticiones con cookies o autentificación

    Las cookies o la información de autentificación (certificados SSL ocabeceras HTTP de autenticación) no se envía nunca por defecto para estetipo de comunicación. Existe una propiedad del objeto XHR nivel 2 (elque implementan los navegadores que soportan CORS) que sirve paraindicar que queremos enviar y recibir este tipo de información:Quote:
    XHR.withCredentials = true;

    Si el servidor permite el envío de este tipo de información para peticiones desde este origen lo indicará en la respuesta incluyendo una cabecera HTTP especial:Quote:
    Access-Control-Allow-Origin:http://www.sitio1.comAccess-Control-Allow-Credentials: true

    Para estos casos, la cabecera Access-Control-Allow-Origin debe indicar el origen exacto, no puede indicarse un origen genérico mediante ‘*’.
    El Internet Explorer 8 no soporta esta funcionalidad. El objeto XDR, propietario del IE, ni siquiera incluye la propiedad withCredentials. Nunca se enviarán cookies ni información de autenticación para este tipo de comunicación.

    Las limitaciones de XDomainRequest (IE)

    El Internet Explorer sólamente implementa una parte del estándar Cross-Origin Resource Sharing. Puede utilizarse para peticiones sencillas (obtener datos RSS o widgets para presentar en nuestra página) pero para comunicaciones complejas presenta las siguientes limitaciones importantes:
    • En ningún caso permite comunicación entre HTTPS y HTTP o viceversa
    • En ningún caso permite enviar cookies o información de autentificación
    • No permite peticiones con verificación previa (preflighted)
    • Sólo soporta Content-Type: text/plain
    • No permite añadir cabeceras HTTP personalizadas

    Puedes obtener información más detallada en los documentos: XDomainRequest – Restrictions, Limitations and Workarounds y Client-side Cross-domain Security. En este último,Microsoft intenta explicar las razones por las que han elegido esta implementación y estas limitaciones.

    ¿Que seguridad aporta CORS?

    En principio puede parecer que CORS no aporta mucha seguridad. Se basaen una cabecera ORIGIN que el servidor comprueba y en otras cabeceras derespuesta que el comprueba navegador. Enviar una cabecera ORIGIN falsaes algo trivial, al alcance de cualquiera y utilizar un navegadormodificado que no bloquee las respuestas, también.
    Sin embargo,el propósito de este estándar, es proteger al usuario que está navegandoen su ordenador, con su navegador, de scripts maliciosos que intentaránacceder a páginas web sin que lo sepa, suplantando su identidad.Supongamos un caso con los siguientes actores:
    • Inocencio, el usuario que navega tranquilamente por internet desde su ordenador
    • Malone, un hacker con su propio sitio web de descargas www.pelisgratis.es
    • La tienda online www.tienda.com en la que Inocencio tiene una cuenta

    Inocencio visita la web www.pelisgratis.es que le ha recomendado unamigo. La página inicial de ‘Pelis Gratis’ presenta el listado depelículas e incluye también un script en JavaScript que, sin queInocencio lo sepa, intenta utilizar AJAX para conectarse a la conocidatienda online www.tienda.com.
    Si el hacker tiene suerte y elusuario ha abierto sesión en la tienda recientemente, aún tendrá lacookie de sesión válida y el servidor le reconoce como Inocencio. Malonepuede utilizar su script para realizar cualquier cosa o robar cualquierinformación de la cuenta de Inocencio. Esto es lo que trata deimpedirse mediante la Política del Origen Común y el estándarCompartición de Recursos entre Origenes
    El caso del hacker usando un navegador modificado no presenta los mismos problemas porqueno tendría la cookie del usuario para suplantarle. El acceso seprotegería mediante la comprobación de credenciales (usuario/password).
    La modificación de cabeceras en el trayecto de la información, entre el usuario y el destino, puede protegerse utilizando HTTPS.

    Fuente: Quimeraazul.com

    No hay comentarios:

    Publicar un comentario