Los temporizadores (Timer) de las extensiones de AJAX para ASP.NET son muy útiles. Nos permiten ejecutar una determinada tarea cada cierto tiempo, provocando postbacks de la página (tanto completos, como parciales) en intervalos regulares. Un único Timer colocado en la página puede conseguir que se refresquen todos los UpdatePanels disponibles o cada uno de manera individual.
Lo habitual es colocarlos en la página y olvidarnos de ellos. Pero ¿qué pasa si queremos poder pararlos y activarlos a voluntad?
La cosa tiene más complicación de la que parece a simple vista. Lo primero que se nos ocurre a cualquiera es que, dado que tiene una propiedad Enabled para activarlo y desactivarlo bastará con establecerla en False para conseguir el efecto deseado. Si lo hacemos desde un postback asíncrono enviado desde dentro de un UpdatePanel, ni se notará en la página, ¿no?.
Lo malo de esta idea es que, simplemente, no funciona. El motivo es que en un refresco parcial de página, aunque se establezca la propiedad del temporizador, al retornar desde el servidor el cambio no se ve reflejado en el código JavaScript que controla el funcionamiento del mismo (un temporizador no es más que un simple setInterval() de JavaScript de los de toda la vida que provoca por código un refresco parcial de la página).
Entonces, para conseguirlo no nos queda más remedio que controlarlo desde el lado cliente, con JavaScript, lo cual tiene la ventaja añadida de que no es necesario un viaje al servidor para una cosa tan prosaica como esta, así que mejor.
Si observamos el código fuente del control Timer en lado cliente (basta con pulsar F12 en Internet Explorer y ver el código desde la pestaña Script) verás que tiene las siguientes definiciones en su prototipo:
Sys.UI._Timer.prototype = {
get_enabled: Sys$UI$_Timer$get_enabled,
set_enabled: Sys$UI$_Timer$set_enabled,
get_interval: Sys$UI$_Timer$get_interval,
set_interval: Sys$UI$_Timer$set_interval,
get_uniqueID: Sys$UI$_Timer$get_uniqueID,
set_uniqueID: Sys$UI$_Timer$set_uniqueID,
dispose: Sys$UI$_Timer$dispose,
_doPostback: Sys$UI$_Timer$_doPostback,
_handleEndRequest: Sys$UI$_Timer$_handleEndRequest,
initialize: Sys$UI$_Timer$initialize,
_raiseTick: Sys$UI$_Timer$_raiseTick,
_startTimer: Sys$UI$_Timer$_startTimer,
_stopTimer: Sys$UI$_Timer$_stopTimer,
_update: Sys$UI$_Timer$_update
}
Como podemos ver es posible hacer muchas cosas desde lado cliente, en concreto las interesantes son: ver/establecer si está habilitado o no, ver/establecer su intervalo, pararlo y ponerlo de nuevo en marcha.
Sabiendo esto tomar control sobre el timer es muy sencillo, basta con incluir este código JavaScript:
<script language="javascript" type="text/javascript">
var tmr = null;
function ReferenciaTimer() {
if (tmr == null)
tmr = $find("<%= Timer1.ClientID %>");
return tmr;
}
function PararTimer() {
ReferenciaTimer()._stopTimer();
}
function LanzarTimer() {
ReferenciaTimer()._startTimer();
}
</script>
Lo único que hago es obtener una referencia al control Timer usando su identificador de cliente (que se "inyecta" desde el servidor al generar la página gracias a una expresión <%= %> de ASP.NET) y la función especial de AJAX $find que sirve para obtener referencias a los componentes en el lado cliente. Luego para pararlo o ponerlo en marcha llamo los método _stopTimer o _startTimer del control JavaScript, que hemos visto en su propotipo. Si colocamos dos botones HTML normales y corrientes que llamen a estas dos funciones que acabo de definir: <input id="Button1" type="button" value="Parar Timer" onclick="PararTimer();" />
<input id="Button2" type="button" value="Lanzar Timer" onclick="LanzarTimer();" />
¡Listo!
Con esto tenemos controlado el Timer y sin necesidad de ir al servidor para nada. Por supuesto podemo susar un código similar para cambiar o leer su intervalo de refresco o, incluso, para provocar manualmente un evento "tick" que lance el postback asociado al control.
He preparado un pequeño ejemplo práctico con todo lo explicado que puedes descargar desde aquí (3,11 KB)
¡Espero que te sea útil!
En mis tres anteriores post me centré en JSONP, la solución soportada actualmente por cualquier navegador para hacer llamadas a servicios JSON ubicados en dominios diferentes al actual (ver índice al final de este post). Ahora, tras quince dís sin tiempo para nada (con Codecamp y TTT para Microsoft por el medio), por fin saco un par de horitas para escribir esta últma parte de la serie, dedicada a los estándares.
Las limitaciones del objeto XmlHttpRequest a la hora de hacer peticiones desde el navegador a páginas y recursos ubicados en otros dominios son de sobra conocidas. Ello ha hecho que los programadores busquen atajos para conseguir esta funcionalidad y saltarse esta barrera. Hasta ahora hemos visto cómo JSONP nos permite conseguirlo gracias al uso de la etiqueta <script>, y cómo se le puede sacar partido en muchas ocasiones. No obstante esta técnica tiene bastantes limitaciones:
· Sólo sirve para peticiones GET · Los datos deben devolverse en formato JSON, o sea, JavaScript. · La información que se puede enviar es muy limitada (únicamente datos planos como parámetros de la URL) · El servidor debe colaborar explícitamente con el método para devolver el código envuelto en una llamada a un método JavaScript · No existe un método estándar para controlar el acceso a este tipo de recursos remotos por parte del servidor que los provee, es decir, decidir quién y cuándo puede acceder al recurso.
Conscientes de esta necesidad en el World Wide Web Consortium (W3C), el organismo encargado de regular los estándares para la Web, han estado trabajando en una modificación del modo de funcionamiento habitual del objeto XmlHttpRequest para dotarlo de capacidades de acceso a otros dominios, así como de las convenciones necesarias para que los servidores que ofrecen servicios puedan regular el acceso de los navegadores a los mismos. El último borrador en el momento de escribir esto es de marzo de este año, lo escribe una persona que trabaja para el navegador Opera, y se puede leer aquí: Cross-Origin Resource Sharing. A partir de ahora en este texto le llamaré CORS a este protocolo, para facilitar la lectura.
Funcionamiento básico de CORS
El comportamiento por defecto del objeto XmlHttpRequest impide que se puedan hacer llamadas a recursos en otros dominios, impidendo así la realización de ataques de XSS y de XSRF, como ya hemos visto en los artículos anteriores. Lo que pretende la W3C con CORS es definir un protocolo para que los servidores Web y los navegadores colaboren a la hora de definir políticas de seguridad de acceso entre dominios.
Así, en su forma más básica, CORS define que cuando se haga una llamada con XmlHttpRequest a un dominio diferente del actual, dicha llamada debe incluir automáticamente una cabecera llamada "Origin" que indicará al servidor de destino el protocolo, dominio y puerto desde el que se está originando la petición. Esta cabecera difiere de la cabecera "Referer" en que no se indica ningún tipo de ruta, por lo que las preocupaciones de privacidad de los posibles usuarios son menores.
Con esta cabecera el servidor debe decidir si permite o no el acceso al recurso, devolviendo en su respuesta una cabecera de tipo Access-Control-Allow-Origin en la que indica al navegador qué dominios son permitidos como origen de las peticiones. Por ejemplo, puede devolver:
Access-Control-Allow-Origin: http://www.tuservidor.com
o, en muchos casos:
Access-Control-Allow-Origin: *
que significa que admite peticiones desde cualquier dominio externo.
Al recibir esta cabecera el navegador sabe si debe permitir o no que se realice la llamada. De esta forma se protege a los usuarios de un uso indebido de JavaScript para traspasar u obtener información entre dominios debido a alguna vulnerabilidad en el código de la página que permita inyectar JavaScript para hacer XSS o XSRF.
Por defecto, si el servidor no incluye esta cabecera el navegador bloquea la llamada, con lo que el comportamiento es exactamente el mismo que el tradicional.
Lo mejor es que realmente los programadores del lado cliente (del JavaScript del navegador) no tienen que hacer nada especial para soportar estas llamadas entre dominios, ya que pueden usar el objeto XmlHttpRequest como ya están acostumbrados a hacerlo y sobre lo que ya he hablado en este blog anteriormente, sólo que en la URL de destino incluyen la ruta completa, con el nombre de otro dominio.
El protocolo define todavía más cabeceras para cuestiones algo más especializadas, como por ejemplo para cachear el resultado de una valoración de seguridad y cuánto tiempo permanece guardada. Otra cosa muy interesante es lo que el borrador de CORS denomina "preflight requests". Se trata de una cómo provocar una petición previa por parte del navegador que, antes de solicitar el verdadero recurso en el que el código está interesado, lanza una petición con el método HTTP OPTIONS en el que el servidor le devuelve las condiciones de seguridad de las llamadas desde ese origen. El navegador las cachea y reutiliza durante un tiempo determinado. El programador no tiene que hacer dos llamadas para consegui esto, sino que con una única llamada mediante XmlHttpRequest, construida de determinada manera, provoca la obtención de esta información. Si tienes interés en profundizar en el estos detalles puedes leerte el borrador de CORS o también el documento que tiene al respecto la gente de la fundación Mozilla (que merendilla :-P).
Pero ¿qué conseguimos con esto?
Puede que a estas alturas te estés diciendo: "Vale, estupendo, el servidor indica qué dominios tienen acceso pero ¿cómo impides que con esto alguien haga una llamada al servicio con cualquier otro método manual, sin pasar por el navegador?¿Qué modo de proteger mi aplicación del servidor es esta?".
La respuesta es que este protocolo no pretende proteger el acceso a tus aplicaciones de servidor. No es un método de seguridad para servicios en Internet. Lo que busca es proteger a los usuarios que navegan usando un navegador estándar de problemas en el código de las páginas que visitan, de modo que de forma inadvertida para ellos un pirata pueda usar técnicas de inyección de código para obtener acceso a su información. Revisa el tercer post de esta serie, más abajo, para recordar cómo son este tipo de ataques.
Esta es una distinción fundamental, ya que muchos programadores a los que se les habla de este protocolo piensan que les servirá para proteger sus recursos en el servidor, y es justo al contrario: es para proteger el uso indebido de código de cliente. Así que ojo con esto. La protección de los recursos del servidor no tiene nada que ver con esto y cada aplicación deberá usar el método, estándar o no, que considere oportuno (como el uso de Cookies cifradas, como ya he analizado en este blog).
Por otra parte el hecho de que podamos usar XmlHttpRequest para este tipo de llamadas nos quita de un plumazo todas las limitaciones que comentaba al principio del post, ya que podemos enviar peticiones GET y POST, cuaqluier tipo de información de cualquier longitud y en cualquier formato de texto (XML, JSON...), y tampoco necesitamos colaboración explícita por parte del servidor más que la inclusión de la cabecera, algo que no complica la programación en absoluto.
Así que si expones un servicio en cualquier formato y quieres darle un punto más de seguridad a tus usuarios incluye la cabecera Access-Control-Allow-Origin para indicar desde qué dominios podrá ser utilizado.
Soporte de navegadores
En la actualidad, y a pesar de que se trata todavía de un borrador, la especificación de este protocolo está soportada por todos los navegadores recientes, es decir, por Firefox 3.5, Safari 4 y Chrome 2.0.
"¿Eh? Un momento, ¿no acabas de decir todos los navegadores recientes?. ¿Y donde está Internet Explorer 8.0? ¿Y Opera?"
Curiosamente, aunque alguien de Opera es el responsable del borrador en la W3C, el fabricante europeo parece que no se ha decidido a implementarlo todavía.
Por otro lado, lamentablemente, Microsoft ha ido por su cuenta con este asunto, ofreciendo su propia implementación y permitiendo hacer lo mismo mediante un nuevo objeto especial llamado XDomainRequest. En realidad, como verás si te lees el enlace anterior, el uso es casi igual al que haríamos con el objeto XmlHttpRequest, pero tiene pequeñas diferencias. Pero por lo demás funciona prácticamente igual y acepta las mismas cabeceras desde el servidor.
Esto, como puedes imaginar, ha creado una nueva corriente anti-IE entre la comunidad más activa en el desarrollo Web y los estándares. Mi opinión personal es también la misma: que deberían haber seguido el borrador al pie de la letra, con el objeto XmlHttpRequest y no con un objeto nuevo que le complique la vida a los scripts multi-navegador. Y más si pensamos que con IE8 Microsoft ha dado un giro espectacular hacia el soporte estricto de los estándares Web, alejándose de la nefasta corriente con la que tontearon con IE6 (motivo principal del clamor popular para eliminar esta infame versión 6.0 del navegador de Microsoft)
¿Por qué decidió Microsoft crear un objeto propio y no hacer como los demás lo que indica el borrador del estándar?. El motivo oficial es que consideran que CORS en su forma actual no es totalmente seguro. En un post en el blog del equipo de Internet Explorer hacen referencia a un documento sobre seguridad en el que indican (en la sección 5) entre otras los posibles ataques que se pueden producir con este protocolo. Gran parte de la comunidad de desarrolladores no está de acuerdo y han contestado a estas reservas que tiene el equipo de IE, como se hace eco este post de ajaxian, blog muy activo dedicado al mundo del desarrollo de lado cliente.
En resumen
CORS es una gran iniciativa para librarnos a los programadores de las dificultades actuales para hacer llamdas entre dominios y, al mismo tiempo, conseguir seguridad para los usuarios. Los principales navegadores en sus últimas versiones soportan el último borrador del protocolo, con la notable excepción de IE8, y tampoco Opera, aunque en este caso dado su baja cuota de usuarios no supone demasiado problema.
Conviene que tratemos de usarlo en nuestros desarrollos de servicios para mejorar la seguridad de los usuarios y aunque no esté totalmente soportado aún.
Es de esperar que cuando el protocolo pase de la fase de borrador a versión definitiva todos los navegadores lo soporten (esperemos que también IE).
Con esto termino la serie de artículos sobre llamadas a servicios entre dominios. Espero que te haya resultado útil, y recuerda que si quieres formarte en desarrollo Web o en cualquier tecnología de Microsoft, en campusMVP encontrarás los mejores cursos on-line tutelados por conocidos MVP.
Las otras partes de esta serie:
· Parte 1: JSONP: llamadas AJAX a servidores remotos · Parte 2: JSONP (II): Soporte desde ASP.NET AJAX 4.0 · Parte 3: JSONP (yIII): Cuestiones de seguridad y ASP.NET rompiendo la compatibilidad en 3.5
En mis dos anteriores post (busca los enlaces al final de este post y leetelos antes si no lo has hecho) he estado hablando sobre la técnica de JSONP para consumo remoto de datos JSON entre dominios, algo a priori prohibido por el modelo de seguridad del navegador. Anteriormente comentaba que JSONP podría tener ciertos problemas de seguridad, y que para evitarlos en ASP.NET 3.5 se habían introducido cambios para paliarlos que rompían la compatibilidad con versiones anteriores. Voy a aclararlo ahora.
El problema de seguridad tiene que ver con la obtención de acceso no autorizado a información privada que se comparte con JSON.
Una de las técnicas habituales de securizar el acceso a los servicios, consiste en crear cookies encriptadas con una validez temporal limitada y asociadas a una sesión concreta. Esta técnica se utiliza de manera mayoritaria en los sitios Web actuales (como Facebook, Windows Live, o, como ya hemos visto, en la propia seguridad de ASP.NET). El problema de esta técnica es que la mayor parte de los navegadores actuales conservan las sesiones entre las diferentes pestañas abiertas (e incluso entre las diferentes ventanas), lo cual puede ser aprovechado por los atacantes para realizar ataques de phising y acceder a esa información privada en ciertos casos. Esto de las sesiones no tiene remedio fácil en navegadores como Firefox (al menos que yo sepa), pero en Internet Explorer 8.0 sí, como ya he explicado en su día.
Bien, supongamos que tenemos un servicio Web que devuelve información privada en formato JSON y que comprueba el acceso del usuario actual basándose en una cookie encriptada. El usuario tiene abierto el navegador con nuestra aplicación y recibe un correo electrónico de un atacante que lo "fuerza" a pulsar en un enlace aparentemente legal, pero que lo lleva a otra página controlada por el atacante. En esta página el pirata intenta una llamada a nuestro servicio JSON usando técnicas de JSONP, es decir, empleando etiquetas de tipo <script> para hacer la llamada. Al hacerlo, como las sesiones se conservan entre pestañas y ventanas, el servicio "se fía" y envía la información.
Lo que ocurre es que esta información generalmente será una matriz de datos JSON del estilo de la que vimos en el anterior post que devolvía Delicio.us. Al recibir en la página esta matriz, dentro de un <script>, aunque es válida realmente el atacante no puede hacer nada porque se recibe y al ser pura información, no asignada enningún sitio, simplemente se recibe y se descarta ¿no?
El problema es que en JavaScript es perfectamente lícito redefinir el constructor de una matriz, de modo que cuando se cree una de ellas se lleven a cabo algunas operaciones adicionales, por lo que el pirata puede escribir un código tan sencillo como este:
<script type="text/javascript">
var datos;
Array = function() {
datos = this;
};
</script>
Lo que se hace es redefinir el constructor de las matrices en JavaScript, de modo que se asigna a una variable el contenido de ésta al ser creada. Este sencillo truco hace que al recibir el JSON desde el otro dominio, los datos se encuentren listos para ser usados por el pirata en el momento que lo desee, sin necesidad de estar de acuerdo con el otro servidor para envolver el JSON en una llamada a una función JavaScript, que sería lo normal y es lo que tendríamos que hacer normalmente. El pirata podría enviar a un tercer dominio los datos o hacer con ellos lo que quisiera.
El impacto de estas técnicas
¿Te parece un ataque muy rebuscado? Quizá lo sea, pero debes saber que GMail fue crackeado hace no tanto tiempo con una técnica similar para obtener acceso a los contactos de cualquier usuario. Además es, en definitiva, una técnica habitual de Cross Site Request Forgery (CSRF), las cuales tienen bastantes casos de éxito en todo tipo de Webs.
No obstante sí que hay algo que minimiza el posible impacto de esta técnica en la actualidad, y es que las últimas versiones de los navegadores están protegidas "de serie" contra ello. Así, ni Internet Explorer 7 u 8, ni Firefox 3 ni Chrome o Safari son vulnerables a la redifinición del constructor de la matriz o de la clase genérica Object. Firefox 2.x síque lo es, por ejemplo.
¿Quiere decir esto que estmao slibres de todo peligro?. En absoluto. para empezar tus usuarios pueden estar usando navegadores viejos. Además, lo cierto es que tus datos son accesibles desde otros dominios sin que esa fuera tu intención inicial, y hay otras posibles técnicas que se pueden utilizar (muchas de las cuales ni siquiera son conocidas hoy, pero pueden surgir). Por ejemplo, Twitter fue crackeado no hace mucho usando técnicas avanzadas de JavaScript y el acceso a los datos JSON remotos usando técnicas JSONP. Y otras similares pueden surgir en cualquier momento.
¿Qué se puede hacer para evitar este problema y otros del estilo que pueden aparecer?
Hay muchas recomendaciones que podemos seguir para evitar este problema.
· Primeramente podemos exigir que el acceso a nuestro servicio privado se haga siempre mediante POST y no usando el más habitual GET. El motivo es que, obviamente, la etiqueta <script> de HTML hace siempre la llamada usando un método GET, y por lo tanto este tipo de ataque no funcionaría. · Otra técnica sencillña y efectiva es comprobar las cabeceras de la petición. Cuando la petición se hace con el objeto XmlHttpRequest generalmente las diferentes bibliotecas AJAX marcan la petición con una cabecera como esta: "X-Requested-With: XMLHttpRequest", para indicar que es una petición AJAX, y distinguirla de una normal. jQuery y ASP.NET AJAX lo hacen. Y si lo hacemos nosotros mismos es muy fácil incluirla también. Además generalmente si se hace una petición que espera JSON se suele indicar también el tipo de contenido esperado con una cabecera "Content-Type: application/json", cosa que tampoco hace la etiqueta <script>.
Otra cosa que hicieron los programadores de .NET para evitar el ataque descrito es incluir el famoso parámetro "d:" en todas las respuestas JSON de los servicios Web, tanto .asmx como WCF, expuesto como JSON.
En AJAX 1.0 para ASP.NET 2.0, cuando se devolvía un dato o colección de datos como JSON a través de un servicio Web, se obtenía un dato JSON normal y corriente, como el que acabamos de ver para Delicio.us, por ejemplo:
{"__type:": "Usuario", "Login":"usuario1", "Clave": "miclave"}
Sin embargo en la versión 3.5 de .NET (es decir, en realidad la versión de AJAX 1.0 específica para ser usada con Visual Studio 2008) y posteriores (ASP.NET 4.0), todas las respuestas JSON de servicios incluyen un parámetro adicional de nombre "d:", lo que convierte las respuestas en algo como esto:
{"d": {"__type:": "Usuario", "Login":"usuario1", "Clave": "miclave"} }
La diferencia en este caso es que siempre nos aseguramos de que lo que se devuelve es un objeto con un miembro de nombre "d" que es el que contiene los datos. En este ejemplo no se ve tanto la ventaja porque ya se devolvía un objeto, pero si devolviésemos una matriz de objetos (algo muy habitual) o un dato simple (como una cadena o un número) la cosa sí está más clara, ya que al devolverlo con forma de objeto y este miembro "d", conseguimos:
1.- Evitar que, aunque se redefina el constructor de la matriz, ello no tenga efecto sobre el acceso al JSON devuelto. 2.- El código no se pueda evaluar directamente en destino.
No tiene porqué ser una medida infalible porque pueden aparecer nuevos tipos de ataques que se salten la seguridad también, pero ayuda. Además, sigue siendo recomendable comprobar las cabeceras o permitir sólo POST para las peticiones de ciertos métodos de servicios, como ya he comentado.
Incompatibilidad
La principal consecuencia de añadir la "d" en las repsuestas JSON es que si tenemos código manual para leer estas respuestas y procesarlas, si cambiamos de versión de .NET (de la 2.0 a una superior) nos dejará de funcionar el código de JavaScript, debiendo, en tal caso hacer una de dos cosas:
1.- Leer las verdaderas propiedades de nuestros datos desde la propiedad "d" del objeto devuelto. 2.- Procesar previamente la respuesta antes de evaluarla para quitarle esa "d"
Si usamos ASP.NET AJAX no tendremos que preocuparnos pues ya hace esto último por nosotros,pero si el código es manual o usamos otras bibliotecas como jQuery, entonces tendremos que tenerlo en cuenta.
La mejor forma de verificarlo si tenemos que hacerlo de forma manual es usando la propiedad hasOwnProperty de la clase Object de JavaScript, soportado en todos los navegadores modernos (incluso IE6 y Firefox 2.0 lo soportan). Por ejemplo, si tenemos un servicio propio con las medidas de seguridad propuestas (sólo admite POST y comprueba que el tipo de datos solicitado sea JSON), si queremos llamarlo desde jQuery, con su función Ajax para llamadas a servicios, podríamos hacer esto y que funcionara con cualquier versión de .NET:
$.ajax({
type: "POST",
url: "miservicio.asmx/mimetodo",
data: "{}",
contentType: "application/json; charset=utf-8",
dataType:"json",
success: function(datos) {
if (datos.hasOwnProperty("d"))
ProcesarResultado(datos.d);
else
ProcesarResultado(datos);
}
});
function ProcesarResultado(datos) {
// aquí procesamos lo devuelto con la confianza de que nunca tendrá la "d"
}
Lo único que se hace es pre-procesar el resultado comprobando si tiene la propiedad "d" característica de ASP.NET 3.5+ o no, y si la tiene usar la propiedad para obtener los datos necesarios, o usar directamente lo devuelto en caso contrario.
Con cualquier otra biblioteca de AJAX o con código propio haríamos lo mismo o muy similar.
En resumen
La seguridad de los servicios basados en JSON puede estar comprometida entre dominios si no tenemos un poco de cuidado. ASP.NET AJAX a partir de .NET 3.5 incluye algunas medidas de protección, las cuales pueden producir algunas incompatibilidades. Si tenemos un poco de cuidado es fácil proteger a los usuarios de un uso indebido de su inforamción privada.
En un próximo post quiero comentar qué está preparando la W3C en las próximas versiones de HTML para facilitar nativamente las llamadas entre dominios y cómo incluso algunos navegadores hoy en día soportan estas características más seguras y estándar.
Las otras partes de este post:
· Parte 1: JSONP: llamadas AJAX a servidores remotos · Parte 2: JSONP (II): Soporte desde ASP.NET AJAX 4.0
En mi post de la semana pasada presenté el concepto de JSONP (o JSON with Pading) viendo lo útil que éste podía ser para acercarnos a la visión de servicios Web verdaderamente integrados entre dominios diferentes, y desde el lado de cliente.
En esta ocasión voy a comentar cómo sacarle partido desde nuestras aplicaciones creadas con ASP.NET AJAX y el soporte nativo que se ha incluido en la próxima versión 4.0 del framework.
El soporte de JSONP desde ASP.NET 4.0 y Visual Studio 2010 es transparente para el programador, ya que basta con incluir dentro de un ScriptManager una referencia a un servicio ubicado en otro dominio para que éste asuma automáticamente que debe hacer la llamda con JSONP. Por ejemplo:
<asp:ScriptManager ID="ScriptManager1" runat="server"> <Services> <asp:ServiceReference Path="http://www.miotrodominio.com/Servicios/Inventario.svc" /> </Services> </asp:ScriptManager>
Es así de fácil. Simplemente no se quejará y asumirá que en el otro extremo existe un servicio al que se le puede pasar un parámetro "callback" para indicarle el nombre de la función de retorno para envolver la respuesta JSON.
Otro lugar en el que se ha incluido este soporte para JSONP es en la clase Sys.Net.WebServiceProxy de las bibliotecas JavaScript (de lado cliente, vamos) de ASP.NET AJAX. Esta clase es la que se utiliza por debajo para hacer llamadas a servicios Web, generalmente con XMLHttpRequest. En la nueva versión de ASP.NET esta clase posee dos nuevas propiedades creadas ex profeso para este propósito:
-
enableJsonp: indica al proxy si debe usar JSONP para realiar la llamada o no. Si es true se añadirá un parámetro de callback a la llamada y se realizará con una referencia a Script en lugar de usar el objeto especializado XmlHttpRequest.
-
jsonpCallbackParameter: establece o lee el nombre del parámetro de callback para la llamada JSONP. Es el nombre de nuestra función de JavaScript encargada de procesar la respuesta y que por lo tanto el servicio usará para envolver los datos JSON que nos envía.
Además, el método invoke de la clase añade dos parámetros opcionales al final con el mismo nombre que estas dos propiedades y su mismo nombre, para dar el soporte directamente en la llamada, sin necesidad de establecer las propiedades antes.
Por supuesto, podemos usar código propio que genere dinámicamente etiquetas de Script para realizar las llamadas JSONP, lo cual es muy sencillo y funciona en cualquier página HTML, sin necesidad de usar una biblioteca especial como ASP.NET AJAX, y en muchos casos será lo que hagamos. Es lo que veremos en un ejemplo algo más abajo.
Crear nuestros propios servicios JSONP
Existen multitud de servicios en Internet que ya utilizan JSONP como formato de retorno, de forma que se fomenta su uso desde cualquier página HTML. Luego veremos un ejemplo. Pero, ¿y si queremos crear nuestro propio servicio habilitado para JSONP?
En MSDN hay un ejemplo concreto de cómo hacerlo mediante la inclusión de codificador (MessageEncoder) y un comportamiento de operación (OperationBehavior) propios. Básicamente se trata de detectar la existencia del parámetro adicional en la petición y actuar en consecuencia para envolver en la llamada JavaScript los datos resultantes. No voy a entrar en detalles, pero si tienes interés puedes leer cómo hacerlo aquí.
Ejemplo práctico con Delicio.us
Delicio.us es el servicio de Social Bookmarking más popular del mundo. Gracias a él podemos descubrir interesantes enlaces que otra gente ha almacenado y puesto a disposición de los demás. Podemos navegar y ver los enlaces más recientes, los más populares, los de un usuario concreto, o investigar enlaces buscando por "tags" o etiquetas que la gente usa para clasificarlos.
En la documentación de la API de Delicio.us se ofrece soporte para JSONP, de forma que nos resultará muy sencillo obtener estos mismos resultados de manera automatizada y usarlos en nuestras propias páginas.
Así, por ejemplo, para obtener en formato RSS los enlaces que la gente ha clasificado bajo la etiqueta "ASP.NET" escribiríamos:
http://feeds.delicious.com/v2/RSS/popular/ASP.NET
Y en nuestro caso, para obtener lo mismo en formato JSON necesitaríamos una llamada similar a esta:
http://feeds.delicious.com/v2/JSON/popular/ASP.NET?callback=Procesar
Aunque en la documentación de delicio.us no aparecen documentados los campos del JSON devuelto, podemos interceptarlos fácilmente con Fiddler o una herramienta similar, y luego usar un visor de JSON (este del enlace es alucinante) para ver fácilmente su estructura de datos:

Como podemos observar la estructura es muy sencilla y devuelve una matriz de objetos con los siguientes campos de datos:
-
u: la URL del favorito
-
d: la descripción del mismo
-
t: una matriz de tags asociadas con la URL
-
dt: la fecha en la que se guardo el favorito, con este formato: "yyyy-MM-ddTHH:mm:ssZ".
Por lo tanto sacarle partido directo desde una página HTML es realmente sencillo usando las nuevas capacidades de Live Binding de datos con JavaScript que vienen con ASP.NET AJAX 4.0.
Puedes descargarte la versión más reciente del ASP.NET AJAX Preview (lo que viene en el futuro pero ya está disponible en pruebas, que ahora va por su release 5) desde CodePlex. Dentro de la carpeta de ejemplos se incluye uno hecho con JSONP. Una versión ligeramente retocada del mismo la he colgado en este Blog para que puedas jugar con ella y es la que vamos a analizar brevemente. En el siguiente marco interna puedes verla en funcionamiento y probarla, cargando por defecto los enlaces populares para ASP.NET:
Se trata de una página HTML pura y dura. No hay código de servidor alguno. Puedes descargarte el código en este archivo ZIP. No obstante lo tienes, como ya he dicho, entre los ejemplos de ASP.NET AJAX Preview.
¿Cómo funciona esto?
En este ejemplo, para hacer las llamadas, se crea un objeto para representar una etiqueta <script> que se hace apuntar a la URL apropiada de Delicio-us que devuelve el tag que nos interesa, tal y como ya hemos visto:
http://feeds.delicious.com/v2/JSON/popular/ASP.NET?callback=queryComplete
En este caso nuestra función de JavaScript para procesar los resultados se llama queryComplete.
Su código es simplemente este: function queryComplete(results) {
$find("dataView").set_data(results);
}
"dataView" es el nombre de un div de la página al que se le ha añadido el comportamiento de enlazado a datos gracias al control de lado cliente DataView de ASP.NET AJAX 4.0. El formato de la plantilla de enlazado a datos (Live Binding) de lado cliente es este: <div id="dataView" sys:attach="dv" class="sys-template">
<p class="linkItem">
<p class="linkTitle">
<span class="title">
<a sys:href="{{ u }}" target="delicious">{{ d }}</a>
</span>
<span class="href">
<a sys:href="{{ u }}" target="delicious">{{ u }}</a>
</span>
</p>
<!--Lista anidada de Tags-->
<span class="taglist sys-template"
sys:attach="dv"
dv:data="{{ t }}"
dv:oncommand="{{ onTaglistCommand }}"
>
<span class="tag" sys:command="viewtag" sys:commandargument="{{ $dataItem }}" >
{{ $dataItem }}
</span> |
</span>
<span class="date">
{{ Date.parseInvariant(dt, "yyyy-MM-ddTHH:mm:ssZ")
.localeFormat("@ dd/MM/yyyy HH:mm:ss") }}
</span>
</p>
</div>
Si necesitas conocer cómo funciona el Live Binding en ASP.NET 4.0 te recomiendo que lo leas en mi libro "Tecnologías ASP.NET 4.0 (saltando desde la versión 2.0)", que tienes disponible para lectura on-line en el enlace anterior y para compra en papel.
Básicamente se puede ver que lo que hace el marcado anterior es enlazar los campos "u" y "d" del JSON devuelto para formar dos enlaces, utiliza un DataView anidado para mostrar las diferentes tags, que se convierten en comandos para lanzar subsiguientes consultas de filtrado por tag. Finalmente usa la clase Date para dar un formato más apropiado a la fecha y enlazarla a un span informativo final.
Para realizar las llamadas se usa una función queryPopularLinksForTag que, en última instancia lo que hace es usar una función sendJsonRequest que tiene el siguiente aspecto: function sendJsonpRequest(url, callback) {
var head = document.getElementsByTagName("head")[0];
if (queryScript) {
head.removeChild(queryScript);
}
queryScript = document.createElement("script");
// JSONP URL has query parameter specifying name of callback function
queryScript.src = url + "?callback=" + callback;
head.appendChild(queryScript);
}
Lo que se hace es crear un nuevo objeto para representar una etiqueta <script> (document.createElement("script")) al cual se le asigna en su propiedad src la URL de delicio.us que nos devolverá el JSON envuelto en una llamada a la función de callback (en este caso queryComplete).
Como vemos es realmente sencillo y nos abre todo un mundo de posibilidades.
En un nuevo post explicaré los peligros de seguridad de JSONP y porqué, debido a ellos, los servicios Web expuestos como JSON en ASP.NET cambiaron la forma de devolver los datos (y son incompatibles con los de la versión anterior).
¡Hasta pronto!
Como todo el mundo sabe, una de las medidas de seguridad más acertadas de los navegadores es la del aislamiento de los dominios para proteger la privacidad. Este aislamiento impide que, por defecto, se puedan transferir cookies entre dominios, que fallen los scripts que tratan de afectar a marcos con páginas que están en dominios diferentes y, por supuesto, tampoco permite hacer peticiones desde código JavaScript a servidores que están en dominios diferentes a la actual. Y aún así existen cantidad de vulnerabilidades (Cross Site Scripting, Cross Site Request Forgery, Cross Zone Scripting, etc...) relacionadas con robo de información y ataques distribuidos que están basadas en explotar código de JavaScript en aplicaciones mal construidas. Al final, cómo esté escrito el código de una aplicación web desde el punto de vista de la seguridad es más importante que todos los cortafuegos y otras medidas "de protocolo" que puedas incluir.
Una de las implicaciones de este aislamiento entre dominios es que desde nuestro código JavaScript no podremos llamar a servicios Web que estén ubicados en dominios diferentes al nuestro (con XmlHttpRequest). Esto limita la capacidad de agregar información de diversas aplicaciones para explotarla directamente desde el navegador en nuestros desarrollos. Así que por defecto olvídate de llamar a código de Google, Flicker, eBay, YouTube, etc... y mostrar toda esa información agregada en tu página haciéndolo directamente desde JavaScript.
La solución habitual pasa por construir servicios propios que actúen como intermediarios de otros servicios y nos devuelvan desde nuestro propio servidor la información que necesitemos, lo cual no deja de ser una pesadez.
Una solución alternativa se llama JSONP (JSON with Padding). Fue propuesta en diciembre de 2005 por Bob Ippolito, creador de la biblioteca JavaScript Mochikit, y que desde entonces ha sido adoptada por muchas aplicaciones Web 2.0 como forma de permitir las llamadas a servicios cross-domain.
¿En qué consiste JSONP?
Es una forma de extender la sintaxis de JSON (JavaScript Object Notation, el estándar de facto de intercambio de datos con JavaScript para aplicaciones AJAX) para que soporte llamadas desde otros dominios. Necesita la colaboración del servidor que expone los servicios para generar el código con una determinada sintaxis y se aprovecha de que las etiquetas <script> permiten establecer el origen de scripts externos (atributo SRC) en dominios diferentes al actual, no estando sujetas por tanto a las restricciones impuestas por el aislamiento de dominio.
Pongamos un ejemplo.
Una estructura de datos JSON común podría ser por ejemplo esta:
{ Nombre : "José Manuel" }
La forma normal de procesar estos datos, devueltos por un servicio, por parte de un cliente JavaScript es evaluándolos para crear un objeto JavaScript asignado a una variable y luego hacer uso de ellos.
Si para llamar al servicio escribimos una etiqueta similar a esta (o un método JavaScript que, por debajo, genere algo similar, que es lo habitual):
<script type="text/javascript" language="javascript" src="http://www.otroservidor.com/servicio/dameDatos.asp"/>
indicando en el atributo src la URL del servicio que devuelve los datos, obtendremos sin problema los datos en el cliente. Pero, como JSON define objetos para crear una estructura de datos pero no es directamente código ejecutable, lo que ocurrirá es que el objeto se evaluará pero se descartará inmediatamente.
Este problema se solucionaría si pudiésemos evaluar de algúna manera los datos recibidos desde el servidor remoto. Es aquí donde entra en juego la colaboración de éste. Si definimos en nuestro código JavaScript una función especializada en tratar la información devuelta y conseguimos que el servidor remoto devuelva no sólo los datos "puros" en JSON, sino estos mismos datos envueltos en una llamada a dicha función, el problema estará solucionado.
Esta función puede ser una cuyo nombre esté previamente acordado o bien una que se indique de manera dinámica y que el servidor utilizará como si se tratara de un método de retrollamada o "callback".
Por ejemplo, si tenemos una función llamada "ProcesaDatos" que recibe el objeto JSON y trabaja con él y podemos indicar al servidor remoto que esta es la función en la que nos interesa recibir los datos tendríamos el problema solucionado.
En este ejemplo si pudiésemos escribir esta llamada:
<script type="text/javascript" language="javascript" src=http://www.otroservidor.com/servicio/dameDatos.asp?f=ProcesaDatos/>
para obtener como JavaScript devuelto esto otro:
ProcesaDatos( { Nombre : "José Manuel" } );
lo que tendríamos sería una llamada automática a nuestra función con datos obtenidos desde otro servidor. ¡Justo la llamada entre dominios que estábamos buscando!.
Esta técnica de JSONP tiene muchas aplicaciones y está soportado por la mayor parte de los frameworks de JavaScript que hay por ahí (como jQuery). Pero no está exento de problemas de seguridad. De hecho su existencia es uno de los motivos de que el equipo de ASP.NET incluyera un cambio importante en la forma de devolver los datos JSON en la versión 3.5 del framework quye hace que rompan las aplicaciones AJAX que usan servicios y que fueron creadas con la versión 2.0 de la plataforma.
En un próximo post explicaré esta vulnerabilidad de JSONP en aplicaciones descuidadas, y cuál fue este cambio. En un post posterior tambien me gustaría explicar qué está haciendo el W3C para incluir de manera estándar y segura las llamadas entre dominios y el soporte actual de algunos navegadores.
Mientras tanto quédate con el concepto de JSONP y úsalo con sentido común pues es muy útil en algunas circunstancias.
Page 1 of 7 in the AJAX category Next Page
|
|
Copyright © 2010 José Manuel Alarcón Aguín. All rights reserved.
|
|