Este es uno de los problemas/bugs más raros con los que me he topado en años, y puede llegar a ser desesperante. Si has llegado aquí a través de una búsqueda de Google porque tienes el problema que indico en el título del post, estoy seguro de que me vas a dejar una nota de agradecimiento por el consejo :-)
Hace unos días hicimos una nueva plantilla estética para un cliente de nuestra plataforma de e-Learning, SELF. La plataforma tiene un sistema muy potente de plantillas estéticas que permite personalizar mucho la estética y parte de la funcionalidad de la interfaz. Se pueden asociar plantillas a los diferentes campus (entornos cerrados de formación) y también se pueden asignar muchas plantillas/fachadas diferentes a cada campus de forma que se pueda acceder con aspectos diferentes para cada cliente al mismo campus a través de diferentes URLs o directorios virtuales.
Una de estas plantillas, preciosa por cierto, provocaba un extraño efecto sobre todas y cada una de las páginas de la plataforma: se enviaban dos veces. Daba igual que pulsaras sobre un enñace, enviaras un formulario o simplemente llamaras a una página desde la barra de direcciones del navegador: TODAS las páginas se cargaban dos veces. Lo peor del asunto es que, cuando se enviaba un formlario, el primer envío del mismo era normal, pero el segundo y misterioso de ellos, se enviaba sin valor alguno, por lo que se producían excepciones constantemente debido a falta de los parámetros apropiados.
Esta doble petición de páginas no es muy evidente porque se suceden de forma rapidísima. De hecho no nos dimos cuenta de que esto era lo que estaba pasando hasta que analizamos el tráfico entre el navegador y el servidor usando Fiddler. Con esta herramienta detectamos que se estaba produciendo este doble envío de las páginas, lo que fué un avance, pero por más que revisábamos el código del servidor y las cabeceras no veíamos nada que pudiera estar provocándolo.
Cambiando la plantilla no pasaba, por lo que era evidente que había algo en ésta que lo estaba provocando. ¿Pero el qué? No había script con código de navegación, no había directivas de redirección, los archivos auxiliares (CSS, imágenes, etc...) existían todos por lo que tampoco era nuestra página de gestión de errores 404 la que podía estar interfiriendo...
?¿?¿?¿?
La solución
Tras analizar a fondo el código HTML de la página a ver qué encontrábamos vimos lo siguiente en una tabla:
<table border="0" cellpadding="5" cellspacing="0" background="#EFEFEF" width="100%">
¿Ves algo raro en ella?
Nosotros al principio tampoco. El fallo está en el atributo para establecer el color de fondo. El atributo background se usa para establecer la imagen de fondo de la tabla, pero no el color de fondo. Resulta que todos los navegadores importantes tienen un extraño comportamiento con esta etiqueta y es que cuando se introduce algo en ella que no es la ruta de una imagen provoca que se solicite nuevamente la página al servidor. Alucinante. Pasa tanto en Internet Explorer (cualquier versión, incluso la 8.0) y en Firefox (lo mismo), y no lo probamos en otros pero seguramente pasará también.
La solución fué fácil: utilizar el atributo correcto para el color de fondo, bgcolor.
Pero claro, para llegar a aislar el problema y llegar a esta conclusión tuvimos que perder varias horas ya que la plantilla tenía muchísimas líneas de código pues era bastante compleja.
Esta es una de esas cosas raras de narices que aprendes con la experiencia y que no puedes encontrar en un curso o un libro :-(
Espero que leer este post te ahorre horas de frustración :-)
Ya he hablado en otras ocasiones de aplicaciones Web de gran impacto y que están rematadamente mal hechas y sin pensar en el usuario. Ésta vez me toca hablar de la Oficina Virtual de Correos.es, el operador nacional de correo en España.
Hace unos días tuve que enviar un Burofax y me dije: "Ya que esta gente tiene una oficina on-line que incluso ha llevado premios, vamos a hacerlo directamente por Internet, que será coser y cantar". ¡Qué error cometí!.
El problema más grave fue sin duda el que se refiere a los pagos on-line. Resulta que estableces los parámetros del servicio que quieres utilizar y cuando ya lo tienes en el carrito de la compra te da la opción depagarlo on-line inmediatamente. Para ello debes introducir el número de tu tarjeta de crédito y, en teoría, se procesa el pago y el servicio queda listo para ser gestionado or un atento funcionario (perdón, ahora desde que no son monopolio del Estado son "personal laboral fijo",aunque algún funcionario queda). El problema es que tras introducir los datos de la tarjeta y continuar te sale un mensaje diciendo que la tarjeta tiene un problema y no se ha podido cargar el importe en la misma. "¡Qué raro!, pero si yo pago muchísimas cosas con esta tarjeta sin problema", pensé. Así que lo que hice fué intentarlo con otra diferente. Mismo resultado. Y finalmente con una tercera y mismo resultado. El problema es que de repente me entran los SMS avisándome de los cargos en las tarjetas. "¿Cómo?, pero si no me lo podían cargar y aquí sigue pendiente el pago". Llamo al teléfono del banco de Correos (que amablemente facilitaban en el mensaje de error), y me confirman que los cargos sí que han tenido éxito y que en realidad aunque el servicio figuraba como "no pagado" ¡me lo habían cobrado tres veces!.
Increible. Lo peor es que según la operadora que me atendió recibían muchas llamadas como la mía porque le pasaba a todo el mundo y que Correos lo sabía desde al menos hacía una semana. Para denunciarlos :-(((
Al final llamé a soporte técnico de Correos y tuve la suerte de que me tocó (a la segunda, porque la primera vez me dijeron que igual "era un virus o algo" o "que estaba entrando mucha gente") una chica muy espabilada y atenta que sabía de lo que estaba hablando anulé el pedido y a los pocos días me devolvieron el dinero en las tres tarjetas. Por supuesto, tuve que ir a una oficia física a poner el Burofax, con el correspondiente tiempo perdido sumado a la más de una hora que perdí intentándolo por Internet. Y luego quieren acercar la administración al ciudadano :-(((
De todos modos aproveché y saqué algunas conclusiones sobre la aplicación Web de Correos.es que merece la pena compartir.
Primeramente: la aplicación está hecha con ASP.NET 1.1 (al menos le tienen aplicado el Service Pack 1, menos da una piedra):

¿Y cómo lo sé? Pues como se puede observar por la captura, la aplicación rompe bastante, y cuando lo hace muestra todos los detalles de los errores. Esta es la primera mala práctica de seguridad que todo programador mínimamente decente de .NET tiene que saber: No se pueden mostrar los errores detallados a los usuarios. De hecho para hacerlo hay que cambiar a propósito el web.config, así que para mi aún tiene más delito que otros errores. Es más, el hecho de que no haya una página especialmente creada para gestionar estos problemas de manera elegante denota una gran falta de esmero a la hora de hacer la aplicación. Y por supuesto revela que la aplicación no está instrumentada y que probablemente nadie se entera de los errores, y si te dedicas toda la tarde a echar abajo la aplicaicón o a saltarte la seguridad, nadie hará nada por evitarlo.
Deben de mezclar alguna aplicación antigua con las páginas de esta porque de vez en cuando te saltan errores (por supuesto también visibles) que hacen referencia a VBScript, o sea, a ASP 3.0 Clásico:

Lo peor es que estos errores a veces te dan pistas sobre la estructura de disco del servidor y facilitan mucho al que quiera entrar como Pedro por su casa:

De vez en cuando la aplicación pierde la sesión, y en lugar de comprobarlo y obligarte a autenticarte de nuevo, pasan cosas como esta:

O sea, te salen las plantillas de los controles vacíos. Mola ¿no? ;-)
Por fin el peor de los errores (aparte del de mostrar los mensajes de error detallados) que vi en el rato que estuve: deben de tener un Web Farm o un Web Garden para atender a las peticiones y lo tienen mal configurado, por lo que cada dos por tres al pulsar un enlace para ir a otra página te sale esta bonita página de error:

O sea, ¿el que montó esto no sabía que cuando varios servidores atienden las peticiones de una aplicación web deben tener las claves de máquina sincronizadas para evitar que falle la validación del ViewState, las cookies de autenticación, etc?. Una coña marinera.
Cuanto daño hace el "body shopping" de algunas consultoras :-(((
¿Y sabeis lo peor del caso? Que como se ve que no están muy contentos con la aplicación, hace ya más de un año sacaron a concurso el hacer una nueva en Java/J2EE. Claro, seguro que algún iluminado dentro de la empresa dijo "Esto no vale para nada. Vamos a tirar a la basura la millonada que hemos gastado en la anterior y vamos a hacer una nueva. Eso sí, la próxima la quiero en J2EE y con Oracle, que esto de .NET es una mierda, ya lo veis". Claro, probablemente no habrán pensado que la tecnología es lo de menos si la gente que la utiliza no sabe cómo utilizarla bien. De momento no hay nada en J2EE así que ya veremos cuando la sustituyan. O ¿se habrán echado atrás en el último momento?.
En fin, siento la longitud y la acritud que refleja de este post, pero es que hay cosas con las que no puedo. A una empresa pequeña no le pasan ni la mitad de lo que le pasan a estas empresas grandes pseudo-monopolistas :-(
Otro día os hablaré de otras, que ejemplos hay muchísimos.
La semana pasada os contaba en un post la forma de eliminar la caché de salida de una página a voluntad, antes de que caduque de manera "natural", usando el método RemoveOutputCacheItem. Este método tiene el problema de que debemos hacerlo desde una página diferente a la que está cacheada, porque en ésta los eventos de servidor (incluyendo el click de un botón) no saltan. Además dejé planteado un caso relativamente común que se puede dar y en el que resultaría útil disponer de otra forma de controlar si se hace caché o no.
Imagina esta situación: tienes una página en caché que los usuarios normales pueden ver pero no editar, pero que los administradores pueden cambiar usando los botones apropiados. Si simplemente activas la caché para que los usuarios vean la versión cacheada de la página, como hemos visto, ninguno de los eventos normales saltará, así que los administradores tampoco podrán editar nada, ni tampoco se verán los cambios que han hecho hasta que la caché se refresque. ¿Cómo podemos conseguir que la caché salte únicamente para los usuarios normales, pero que para los editores o administradores la página funcione con normalidad?
La primera tentación es pensar en hacer caché de la página con un VaryByCustom, que guarde en caché una versión diferente de la salida página según quien sea el usuario actualmente autenticado en el sistema. Esto está bien excepto por un pequeño problema: que aunque sí serviría para crear una versión diferente en caché de la página por cada tipo de usuario (o el criterio que elijamos), no nos permite no hacer caché. Es decir, no podemos anularla por ejemplo para los administradores y hacerla para el resto de perfiles, que es lo que buscábamos. Así que este camino es un punto muerto.
La técnica para conseguirlo pasa por el uso de un evento especial de la caché que es llamado automáticamente una vez en cada petición. Para ello disponemos del método AddValidationCallback. Éste toma como argumentos la referencia a un delegado del tipo HttpCacheValidateHandler, y un objeto cualquiera que sirve para pasar los datos que necesitemos a la llamada (normalmente lo usaremos con una referencia nula).
Así, en el evento de carga de la página pondremos este código:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load lblHora.Text = DateTime.Now.ToLongTimeString() If User.Identity.IsAuthenticated Then Response.Cache.AddValidationCallback(New HttpCacheValidateHandler(AddressOf ValidateCache), Nothing) End If End Sub
El evento Load saltará siempre que la página no esté en Caché, es decir, la primera vez que llamemos a la página. En este caso lo que hacemos es que si el usuario actual está autenticado, asignamos la función ValidateCache como la responsable de decidir en cada petición la validez de la caché actual de la página.
Ahora sólo resta definir el manejador del evento, que será como este:
Public Shared Sub ValidateCache(ByVal Currentcontext As HttpContext, ByVal data As Object, _ ByRef status As HttpValidationStatus)
If Currentcontext.User.IsInRole("Administradores") Then status = HttpValidationStatus.IgnoreThisRequest Else status = HttpValidationStatus.Valid End If
End Sub
Lo que se hace es comprobar si el usuario actual está dentro del rol de "Administradores", en cuyo casi devolvemos como estátus de validación de caché el valor IgnoreThisRequest de la enumeración HttpValidationStatus. Al hacer esto lo que conseguimos es que se obvie la caché para la página, y por lo tanto ésta se ejecute normalmente.
Si el usuario es cualquier otro tipo de usuario validamos la caché (con el valor Valid de la misma enumeración) de forma que ésta actuará de la manera habitual.
Con esto conseguimos el efecto buscado que es que la caché se realice para todos los usuarios excepto para los administradores, de foram que puedan trabajar en ella normalmente.
En un programa real iríamos un paso más allá e invalidaríamos la caché de la página con la técnica aprendida en el post anterior siempre que un administrador cambiara algún dato en la página, dando acceso inmediato a la nueva versión a todos los usuarios.
Aunque antes he dicho que cuando la página está en caché los eventos de ésta no saltan, éste evento de validación saltará siempre (en todas las peticiones) porque no es se trata de un evento de la página, sino un evento de la propia caché, y por lo tanto ajeno al ciclo de vido de la página (pero no al ciclo de vida de una petición). No te dejes engañar por el hecho de que el código del evento esté en la propia página.
Una sutileza más
Antes de dar por terminado el post. La enumeración HttpValidationStatus aún tiene un valor más aparte de los dos que acabamos de ver. Se trata del valor HttpValidationStatus.Invalid. Si utilizamos este valor en lugar de HttpValidationStatus.IgnoreThisRequest, como en el ejemplo, aparentemente la técnica funcionará igual, pues se saltará la caché para los administradores y permitirá que estos trabajen en la página con todos sus eventos.
Sin embargo hay una diferencia importante: cuando usamos HttpValidationStatus.IgnoreThisRequest lo que conseguimos es saltarnos el uso de la caché, de forma que la página se ejecuta normalmente y la caché existente no se ve afectada, quedando intacta para el resto de los usuarios. Sin embargo cuando usamos el valor HttpValidationStatus.Invalid lo que indicamos es que la caché actual ya no vale (lo que técnicamente se conoce como "fallo de caché" o "cahé miss" e inglés), y por lo tanto el efecto conseguido es que se elimina la caché que existiera en ese momento para la página, se ejecuta ésta y se genera una nueva versión cacheada de la misma.
La diferencia es tremenda. En el primer caso nos la saltamos sin afectarla, pero en el segundo lo que hacemos es generar una versión nueva para todos los usuarios, con las implicaciones de rendimiento y posible visualización de información consistente que ello implica. Así que hay que tenerlo en cuenta.
Obviamente podemo usar HttpValidationStatus.Invalid para borrar la caché en casos que realmente así lo requieran, pero hay que tener en cuenta los posibles efectos.
Este tema está realmente pobremente documentado y es muy poco conocido, así que espero que te resulte de utilidad :-)
Sí, el sistema operativo que nunca has querido usar cumple 40 años ;-)
Como seguro que sabes, UNIX es la base de todas las distribuciones de Linux, pero ¿sabías que Mac OS X está basado también en este sistema operativo?. Obviamente también es la base de otros sistemas como Solaris, Irix, AIX, o BSD. Y ¿sabías que el UNIX con mayor implantación era de Microsoft?. Se llamaba Xenix, y antes de que llegara Linux fue el que tuvo un mayor número de usuarios.
Puedes saberlo todo sobre la historia de UNIX y sus creadores, los míticos Ken Thomson y Dennis Ritchie en la entrada de UNIX de la Wikipedia.

En realidad se podría decir que, a pesar de la aparente variedad, el mundo de los sistemas operativos se divide en dos: Microsoft Windows y los derivados de UNIX.
El famosísimo gurú Joel Spolsky ha afirmado lo siguiente:
"¿Cuáles son las diferencias culturales entre los programadores de Unix y de Windows?. Existen muchos detalles y sutilezas, pero en su mayor parte se resumen en una sola cosa: la cultura Unix valora el código que es útil para otros programadores, mientras que la cultura Windows valora el código que es útil para los que no son programadores. Esto es, por supuesto, una gran simplificación pero, en realidad, esta es la gran diferencia: ¿Estamos programando para otros programadores o para los usuarios finales?. Todo lo demás son comentarios."
Parece que últimamente los chicos de Linux, al menos en algunas de sus distribuciones, están haciendo un buen trabajo para cambiar esta cultura. Eso es bueno: la competencia fomenta la innovación y la mejora, y es bueno para los usuarios.
¡Felicidades de corazón por este aniversario!
Esta noticia es de hace un mes y con la vida ajetreada que llevo últimamente casi se me pasa ponerla en el blog (parece mentira) :-)
Ha pasado una buena temporada, pero al fin tenemos aquí un nuevo libro de Krasis Press. Se trata de "Workflows and Sharepoint: Going with the Flow".
No, no es que se nos haya "ido la pinza" usando anglicismos en el título, es que se trata de nuestro primer libro en inglés.
Lo ha escrito Gustavo Vélez, uno de los mayores expertos mundiales en Sharepoint. Gustavo es ingeniero mecánico y electrónico, director de soluciones en Avanade en Holanda y ha trabajado con Sharepoint desde antes incluso de que se llamase así. Podemos leer artículos suyos en varias de las revistas más importantes del sector, publicadas en inglés, alemán y español. Como ves todo muy internacional :-) Es webmaster de SkunkWorks, el único sitio en español íntegramente dedicado a Sharepoint, y le han concedido el premio MVP en tecnologías Sharepoint MOSS.
El libro es una referencia accesible para guiar paso a paso a los usuarios a través del proceso de creación de workflows en Sharepoint. Se basa en ejemplos claros para describir y poner de manifiesto sus posibilidades y aplicaciones. Se ha dividido en cinco áreas para una fácil consulta:
· Introducción a la arquitectura y al uso de workflows · Uso de SharePoint Designer para crear flujos sin tener que programar · Creación de flujos “Sequential” y “State-Machine” utilizando Visual Studio · Creación y desarrollo de formularios · Activities, su uso en Visual Studio y en SharePoint Designer, otros aspectos avanzados de la programación de workflows en SharePoint.
Si estás en España cómpralo en nuestra tienda con gastos de envío por mensajería urgente incluídos en el precio.
Si estás fuera de España puedes comprarlo directamente en:
Los que me conocéis o habéis ido a alguna de mis charlas sabéis que soy un gran defensor de las técnicas de Caché de salida en las páginas para mejorar el rendimiento y la respuesta de las aplicaciones. De hecho en este blog he hablado ya bastantes veces sobre la Caché en sus diversas variantes. Así que quizá resulte chocante que hoy vaya a hablar sobre justo lo contrario: cómo eliminar la caché de salida (OutputCache) de una página.
Como es sabido, para habilitar la caché de salida de una página o control lo único que tenemos que hacer es añadir una directiva similar a esta al principio de la página, justo tras la directiva de página:
<%@ OutputCache Duration="60" VaryByParam="None" %>
Los parámetros que se pueden usar son variados y no voy a entrar aquí en detalles pues todo programador de ASP.NET debe conocerlos (si alguien necesita formación sobre desarrollo web, ya sabe ;-)). Es posible hacer caché de la página completa, sólo de algunas partes, de todo menos algunos puntos concretos, que la caché dependa de cualquier parámetro o control, de una variable de QueryString, etc... Incluso definir nuestra propia política de cacheado en función de lo que queramos. En fin, una maravilla, fácil de usar y con resultados espectaculares cuando se aplica juiciosamente.
Pero, como toda herramienta, se puede volver en nuestra contra en un momento dado. ¿Qué pasa si algo ha cambiado y necesitamos que una determinada página -cacheada por un buen rato- se actualice inmediatamente con nueva visualización? En este caso necesitaremos que la caché de la página se elimine de inmediato. Es posible crear una dependencia de caché para que ésta dependa de un archivo, una consulta a una base de datos o casi cualquier otro recurso, pero en ocasiones querremos hacer algo más sencillo.
Luego veremos un caso mucho más claro de porqué queremos borrar este tipo de caché, pero primero quiero ver un caso más simple y su solución, más fácil también.
En el caso de simplemente querer eliminar la caché para una página la solución es bastante sencilla, si bien poco conocida por parte de los programadores de ASP.NET. La clase HttpResponse ofrece un método que sirve precisamente para eso: RemoveOutputCacheItem.
El único parámetro que necesita este método es la ruta absoluta virtual (es decir, empezando por "/") de la página cuya caché queremos liberar. Así, por ejemlo, para liberar la caché de la página actual haríamos lo siguiente:
HttpResponse.RemoveOutputCacheItem(Request.CurrentExecutionFilePath)
Esto en teoría, claro, porque lo primero que hará la mayoría es correar a una página de pruebas, habilitar la caché y poner un botón que haga lo de la línea anterior. El resultado: ¡no le va a funcionar!
El motivo es que, una vez que cacheamos la página completa, al llamarla de nuevo, incluso al pulsar un botón, ésta se saca directamente de la caché y su ciclo de vida normal no se ejecuta, por lo tanto no le funcionarán los eventos. Para conseguirlo con este método es necesario hacer la llamada desde otra página (luego explicaré cómo conseguirlo en la misma página con otro método).
En este archivo (3,95Kb) he incluido una página que, cuando es accedida desde cualquier otra, invalida la caché de ésta primera (en caso de haberla). Así, para eliminar la caché de cualquier página sólo es necesario colocar un enlace a BorraCache.aspx en ella y listo, con este código: Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
'Obtengo la página desde la que he llegado aquí
Dim uriAnterior As Uri = Request.UrlReferrer
If Not uriAnterior Is Nothing Then
Dim sRuta As String = uriAnterior.AbsolutePath
HttpResponse.RemoveOutputCacheItem(sRuta)
lblInfo.Text = "Se ha borrado la caché de la página " + sRuta
Else
lblInfo.Text = "Esta página no puede ser llamada directamente. Llámala desde una página cacheada para eliminarla de la caché de salida."
End If
End Sub
Obviamente es una prueba de concepto, y en una aplicación real no deberíamos incluirla de esta manera pues podría llamarla cualquiera para eliminar nuestra aplicación, pero sirve para captar la idea y poder usarla, por ejemplo, desde una página de administración protegida.
En el próximo post voy a continuar con este tema y a explicar algo mucho más interesante: Cómo eliminar selectivamente la caché de la página actual.
Imagina esta situación: tienes una página en caché que los usuarios normales pueden ver pero no editar, pero que los administradores puedes cambiar usando los botones apropiados. Si simplemente activas la caché, como hemos visto, ninguno de los eventos normales saltará, así que los administradores tampoco podrán editar nada, ni tampoco se verán los cambios que han hecho hasta que la caché se refresque.
En el próximo post veremos cómo conseguir esto.
En los últimos posts he estado hablando sobre la seguridad de ASP.NET basada en Forms. Esto ha traído algunas preguntas por parte de los visitantes. Una de ellas, bastante común, es la de cómo almacenar información adicional atada a los usuarios que hemos autenticado. La API de Membership que viene con ASP.NET, y en concreto el proveedor de Membership para trabajo contra SQL Server ( SqlMembershipProvider) nos provee de los medios suficientes para almacenar la información básica sobre los usuarios, esto es, el nombre, su login y su clave, que es lo mínimo necesario para trabajar. En este gráfico puedes ver la estructura completa creada por ASP.NET (más bien por aspnet_regsql.exe) en nuestra base de datos para dar soporte a SqlMembershipProvider y relacionados. Obviamente casi siempre necesitaremos almacenar mucha más información sobre ellos, relacionándola de manera directa y sencilla para poder extraerla. Así, por ejemplo, podemos necesitar almacenar sus datos de contacto, infromación sobre documentos que genere, estadísticas de aceso, y mil informaciones más. Lo que haríamos normalmente en cualquier aplicación creada íntegramente por nosotros, es crear una(s) nueva(s) tabla(s) para almacenar esta información adicional sobre el usuario, y relacionarla creando claves externas en la base de datos, usando el identificador único del usuario para ello. Y con Membership exactamente lo mismo. Podemos crear tablas extra y relacionarlas con la tabla aspnet_Users mediante la clave primaria de ésta, UserId. Nota: El nombre de usuario, campo LoweredUserName) es también único para cada aplicación (es decir combinado con el campo ApplicationId), pero es mejor utilizar la clave primaria de la tabla.
La única pega es que, posteriormente, a la hora de hacer consultas y extraer los datos necesitaremos saber este Id único para poder filtrar. ¿Y eso como lo hago? Muy sencillo. La clase MembershipUser dispone de una propiedad llamada ProviderUserKey que proporciona precisamente ese valor único para cada proveedor. En el caso del proveedor para SQL proporciona el valor del campo UserId que hemos visto. Para el del usuario actualmente autenticado sería, en VB, así: Dim usuario As MembershipUser = Membership.GetUser
Dim usuarioID As String = usuario.ProviderUserKey.ToString
Para un nombre de usuario cualquiera, aunque no esté autenticado, sería: Dim usuario As MembershipUser = Membership.GetUser("usuario") Dim usuarioID As String = usuario.ProviderUserKey.ToStringOJO: hay que tener en cuenta que con el SqlMembershipProvider este UserId es un GUID, por eso lo transformamos en una cadena (aunque se podría transformar directamente en una clase Guid). En el caso de otros proveedores podría ser cualquier otra cosa, por ejemplo, un entero. Con esto no tienes problma de relacionar cualquier información extra en la BD con los usuarios de ASP.NET. Sencillo, pero mucha gente lo desconoce. Espero que te sea útil :-)
En mi anterior post hablaba sobre el funcionamiento de las cookies en los navegadores y de cómo podíamos usar una extensión estándar (HttpOnly) para intentar impedir que las cookies sean accesibles desde el lado cliente y sólo se puedan manejar desde el servidor.
No obstante se trata de una medida que, de funcionar, complica un poco a los posibles piratas, pero no es una verdadera barrera de seguridad. Para empezar ni siquiera está soportada por todos los navegadores. Las cookies residen en el disco duro del usuario por lo que son fáciles de manipular por cualquiera. Además se envían al servidor en cada petición, por lo que cualquiera con un proxy estilo Fiddler puede leerlas y manipularlas antes de enviarlas al servidor.
Es decir, las cookies son elementos realmente inseguros.
A raíz de estas disquisiciones alguien me escribió preguntándome por la seguridad de la autenticación Forms de ASP.NET (la más habitual). Ésta se basa precisamente en la existencia de una cookie que identifica al usuario y que, por defecto, tiene una validez de 30 minutos, con renovación automática de este periodo en cada petición.
Un momento, ¿cómo?, ¿que la seguridad se basa en que hay una cookie en mi equipo? ¡Qué miedo!
Bueno esto es lo que podría pensarse si las cosas fueran tan sencillas como aparentan. Quiero decir que, en efecto, los datos sobre la sesión del usuario se almacenan en una cookie, pero no de cualquier manera, sino de manera segura.
El funcionamiento de este sistema es, básicamente, el siguiente:
1.- el usuario introduce sus credenciales y se validan (contra una base de datos o contra cualquier otro almacén marcado por el proveedor). 2.- Una vez validado positivamente se genera un ticket o testigo de la sesión actual con una validez de 30 minutos. 3.- Éste se encripta y se almacena en una cookie enviada al cliente y persistida durante la sesión del usuario. 4.- En cada nueva petición al servidor, la cookie se envía y un módulo especializado (FormsAuthenticationModule dentro del espacio de nombres System.Web.Security) desencripta la cookie y se encarga de re-autenticar al usuario en el sistema (estableciendo el objeto Principal actual y renovando la validez de la cookie si así está especificado).
Opcionalmente se puede almacenar el ticket en una cookie persistente que se guaradará en el disco duro del usuario durante por defecto 30 minutos (si bien se puede poner un valor algo más largo que generalmente será más apropiado). Eso significa que aunque el usuario cierre el navegador, al volver a abirlo se mantendrá su estado de autenticación intacto más allá de los 30 minutos por defecto y aunque cierre el navegador.
¿Qué se almacena en la cookie?
La cookie contiene el ticket de la autenticación Forms. Este ticket, representado por la clase FormsAuthenticationTicket, contiene los siguientes datos/miembros:
· Version: la versión del formato del ticket que se está usando. Actualmente la versión 2. · Name: el nombre del usuario actual, que es único para todo el sistema y que es la pieza clave para luego restituir la sesión autenticada, así como ligarlo con otras API de ASP.NET como Roles o Profile. · Expiration: cuándo caduca el ticket (y por lo tanto la cookie). · IssueDate: fecha en la que se generó · IsPersistent: si la cookie se guardará a disco o no. · UserData: datos extra sobre el usuario. Normalmente esto es una cadena vacía ya que se escribe desde el proveedor de Membership, y las implementaciones por defecto no escriben nada aquí. · CookiePath: la ruta relativa a partir la cual se almacena la cookie. Por defecto es "/".
Esta información es la que se serializa y se encripta, estableciendo una cookie para almacenarla en el cliente. Existe un método privado de la clase FormsAuthentication llamado MakeTicketIntoBinaryBlob que se encarga de serializar estos datos, que es llamado desde otro método privado, Encrypt, que se encarga del cifrado.
¿Cómo se encripta la cookie?
Dentro de la configuración de las cookies de autenticación en web.config, en el nodo <forms> podemos establecer algunas propiedades para controlar el comportamiento de este tipo de autenticación. Una de estas propiedades es protection. Puede tomar los valores siguientes:
· Encryption: con este valor el ticket se encripta antes de meterlo en la cookie. · Validation: fuerza la validación de la cookie, pero no la encripta. · All: Es el valor predeterminado y también el recomendado. Fuerza tanto el cifrado como la validación de la cookie que contiene el ticket de autenticación. · None: hace que la cookie no se cifre ni se valide. Está para casos extremos pero se desaconseja totalmente su uso, ya que entonces sí que los temores que llevaron a crear este post serían ciertos, por que no hay protección alguna de la cookie. Eso sí, el rendimiento es mejor porque no tiene que hacer nada.
El cifrado se realiza usando la información especificada en la sección <machineKey> de web.config. A partir de la versión 2.0 de .NET por defecto se utiliza el algoritmo AES (Advanced Encryption Standard, el famoso Rijndael, estándar de la máxima seguridad en cifrado simétrico), pero también están soportados otros algoritmos menos seguros como DES y 3DES.
Para la validación de la cookie se emplea un algortimo de resumen digital (hash). Se concatenan los datos del ticket con una clave de validación, y se obtiene el hash del total usando los algoritmos SHA1 o MD5. POr defecto se usa SHA1, que es el más seguro y rápido de los dos. Dado que se le concatena una clave que sólo es conocida en el servidor, este esquema asegura que no se pueda añadir el código de validación de la cookie desde fuera.
La clave de cifrado y descifrado así como la de validación usadas por defecto es autogenerada en el servidor. Si pretendemos usar la autenticación (así como la validación del Viewstate y otros servicios dependientes) en una granja de servidores, deberemos generar estas claves manualmente y colocarlas en todos los servidores de la granja. Puedes leer todos los detalles aquí.
Como vemos se trata de un sistema muy seguro y que nos protege de intercepciones de la información de autenticación.
De hecho la mayor parte de los sistemas de autenticación de sitios con millones de usuarios como Facebook o Live ID de Microsoft, se basan en técnicas similares.
Analizando el código de las clases de seguridad Web de ASP.NET usando .NET Reflector se pueden ver con todo detalle los entresijos del funcionamiento interno de este sistema, algo que te recomiendo si estás interesado en todas estas cuestiones.
Existe un artículo de la Knowledge Base de Microsoft orientado a resolver problemas con la autenticación Forms que, en su último apartado, trae el código fuente de un módulo que se puede instalar en cualquiera de nuestras aplicaciones para descifrar en el servidor la información de cookies de autenticación que llega y poder hacer logging de la misma. Es un interesante código para analizar y aprender con él sobre el funcionamiento del sistema. Lo tienes aquí.
¡Espero que te sea útil! :-)
Una de las técnicas de hacking de páginas web más conocidas es la de Cross-Site-Scripting o XSS. Mediante esta técnica, las aplicaciones Web vulnerables pueden ser atacadas para multitud de propósitos: servir de zombies para atacar a otras Webs inundándolas con peticiones, ejecutar código en zonas de seguridad menos restrictivas (como la local) o, sobre todo, robar la identidad de los usuarios del sitio vulnerable. Ha sido utilizado tradicionalmente en combinación con correos electrónicos maliciosos para entrar en sitios Web protegidos suplantando la identidad de usuarios autenticados. En mi curso de Desarrollo de aplicaciones seguras y seguridad de .NET monto un ejemplo completo de cómo utilizarlo en un supuesto sitio de banca electrónica (muy mal hecha y vulnerable), para robar la sesión del usuario y entrar en su cuenta.
Este robo de sesiones se basa en la obtención mediante XSS de la cookie de sesión de un usuario, enviándola a un servidor bajo el control del atacante. Éste al recibirla puede ir inmediatamente al sitio web atacado y entrar como si fuese el usuario cuya identidad ha robado. En general el robo de cookies es un problema, si no tan grave como el descrito, al menos con consecuencias sobre la privacidad de los sitios Web.
No nos pensemos que este problema de seguridad es algo esotérico que no nos pasará en nuestras aplicaciones. Le pasa a cualquiera. Lo han sufrido empresas como Google, Facebook o incluso hace unos días aparecieron estos problemas en las web de las principales empresas de seguridad). Hay informes que revelan que 9 de cada 10 aplicaciones Web tienen algún problema aunque sea leve de XSS. Así que no lo subestimes.
Manejo de cookies: cliente y servidor
Generalmente en nuestras aplicaciones las cookies las escribimos con código de servidor. Éstas se envían al navegador en una cabecera HTTP y luego se reenvían, en cada nueva petición, de nuevo al servidor, donde podemos leerlas o modificarlas si así es necesario. Estas cookies sólo pueden ser accedidas desde el mismo dominio desde el que han sido creadas, por lo que un código malicioso que esté en un marco o en otra página abierta en el navegador (en otra venta o e una pestaña) no tiene acceso a las mismas. Es, en teoría, información privada y accesible sólo para nuestro dominio.
Sin embargo, por defecto, hay una forma adicional de leer las cookies enviadas por un servidor y es mediante JavaScript. El objeto document del DOM de una página web dispone de una propiedad cookie que muestra todas las cookies del dominio actual así como sus contenidos en una cadena de parejas nombre=valor separadas por punto y coma. Y es ahí precisamente en donde se rompe la seguridad. Si alguien logra inyectar código malicioso de Script en una página (por ejemplo, metiéndolo camuflado en un comentario), este código puede leer las cookies del servidor actual y enviarlas fácilmente a donde quiera. Entre estas cookies habrá datos del usuario que pueden ser privados (de ahí que no se recomiende guardar datos importantes en una cookie) y también cookies de sesión, que es en lo que se basan la mayor parte de las tecnologías Web para mantener el estado entre llamadas HTTP.
Desde hace ya muchos años existe un añadido a la cabecera HTTP para manejo de cookies que planta una restricción en cuanto a quien puede leerlas o no. Se trata de la extensión HttpOnly. Esta cadena añadida al final de la cabecera indica a los navegadores que no deben permitir la lectura de las cookies desde el lado cliente, sólo desde el servidor. Así, el código JavaScript no puede leer las cookies que lleven esta extensión a la cabecera y por lo tanto las protegemos de los ataques de XSS.
El aspecto de una cabecera de cookie normal es este:
Set-Cookie: miCookie=Dato=XXXXYYYYZZZZZ; path=/
Para evitar que las cookies puedan ser leídas desde el cliente sólo hay que añadir la extensión HttpOnly al final, así:
Set-Cookie: miCookie=Dato=XXXXYYYYZZZZZ; path=/; HttpOnly
Cómo establecer esta opción en cookies con ASP.NET
En ASP.NET es muy sencillo conseguirlo mediante código ya que la clase HttpCookie dispone de una propiedad a tal efecto. Considera el siguiente código sencillo en el que una página ASPX escribe un par de cookies que serán enviadas al navegador:

La primera de las cookies es una cookie "normal y corriente". Se establece un nombre ("Numero") y un valor (1), se añade a una cookie, se le pone un tiempo de caducidad y se manda al cliente. Por defecto no se incluye la extensión HttpOnly por lo que esta cookie va a ser accesible a código JavaScript en las páginas de nuestra aplicación.
La segunda cookie es idéntica a la anterior, pero en este caso establecemos su propiedad HttpOnly a True, de modo que se añade en la cabecera la extensión que hemos visto, y el navegador restringirá su acceso únicamente al servidor.
Podemos comprobar que está funcionando de manera muy fácil, basta con añadir un:
alert(document.cookie);
dentro de un bloque JavaScript en alguna página del sitio para ver como se abre una ventana mostrando los contenidos de las cookies a las que nuestro código JavaScript tiene acceso. Comprobaremos que la segunda cookie no aparece por ningún lado, sin embargo sigue accesible en el código de servidor. Además podemos verla en las cabeceras que se mandan al servidor, usando una herramienta de inspección como Fiddler (comentada en mi anterior post referenciado arriba)
Activación de HttpOnly para toda la aplicación
La propiedad que acabamos de ver está muy bien cuando queremos establecer esta opción sólo para algunas cookies, pero ¿y si queremos que todas las cookies sean de acceso exclusivo desde el servidor? Y especialmente si queremos que esta opción esté activa para todas las cookies de sesión, como la que mantiene el estado HTTP y que genera la propia infraestructura de ASP.NET.
La solución es establecer una opción en web.config. Dentro del nodo <system.web> podemos escribir esto:

A partir de ese momento todas las cookies serán de manejo exclusivo en el servidor, por lo que podremos despreocuparnos de utilizar la propiedad que hemos visto.
Conviene utilizar esta opción en las aplicaciones en las que recibamos datos de los usuarios que puedan contener HTML y código de JavaScript, y desde luego aparte de esto, observar siempre las buenas prácticas de seguridad en aplicaciones Web, así como la regla número 1 de seguridad que todo programador debe seguir: "jamás confíes en nada que te envíe un usuario".
Soporte de HttpOnly en los diferentes navegadores
Internet Explorer 4.0, allá por el año 1997 (casi en la prehistoria de Internet) ya soportaba esta extensión de seguridad. Así que IE es el mejor candidato para estar seguros de que funciona esta extensión. IE8 además es el único navegador que incorpora un filtro anti-XSS que si bien no es efectivo a 100% (algo imposible seguramente), hace un gran trabajo y es más de lo que ofrece la competencia, lo que demuestra la seriedad con la que en Redmond se toman la seguridad.
Firefox (o sea Mozilla, es decir, Netscape y otros que se basaban en ese motor etc...) sin embargo no lo empezó a soportar hasta su versión 2.0.0.5, en el año 2007 (diez años despues de IE). Y encima tenía un bug, por que no las retiraba del objeto XMLHttpObject, algo que se solucionó en la versión 3.0.
Opera empezó a soportarla en su versión 9.5 (Junio de 2008), por lo que todas las versiones modernas funcionan como es debido en este aspecto.
El navegador de Google, Chrome, lo soporta en el sentido de que no permite la lectura por parte de código de Script de las cookies marcadas con HttpOnly. Sin embargo soporta su escritura, algo un tanto extraño pero que mitiga mucho el efecto de un atacante malicioso.
El navegador Safari, de Apple, no tiene en cuenta para nada esta extensión y permite a JavaScript hacer lo que quiera con las cookies, lo que lo convierte en un objetivo apetecible para ataques XSS.
Así que podemos utilizarla sin demasiado problema confiando en que funcione. De todos modos lo mejor es tener en cuenta la seguridad en nuestras aplicaciones y poner contramedidas para evitar los ataques XSS :-)
|
|
Copyright © 2010 José Manuel Alarcón Aguín. All rights reserved.
|
|