2011-10-17 18 views
8

ASP.NET MVC AntiForgeryToken mecanismo se basa en el actual HttpContext.User. Utiliza ese valor para construir el token cuando llamas al Html.AntiForgeryToken(). Básicamente está bien (ver una explicación en el last paragraph here), pero surge un problema cuando inicias sesión a través de una llamada Ajax .MVC3 AntiForgeryToken se rompe en Ajax login

En mi código, cuando un usuario inicia una sesión, las credenciales se envían como un objeto JSON en Ajax (el valor del campo oculto AntiForgeryToken también se envía dentro del JSON), el servidor autentica al usuario, se aplica FormsAuthentication.SetAuthCookie() y devuelve un resultado Json que contiene algunos datos específicos del usuario. De esa manera, puedo evitar la actualización completa de la página al iniciar sesión.

El problema es que cada solicitud subsiguiente de Ajax al servidor ahora falla al ValidateAntiForgeryTokenAttribute, porque ahora espera un token antifalsificación que no es compatible con la cookie antifalsificación.

¿Cómo puedo obtener un token antifalsificación válido para poner en el campo oculto del cliente para que todas las solicitudes Json después de iniciar sesión tengan éxito?

He intentado conseguir un nuevo campo oculto símbolo manualmente (usando AntiForgery.GetHtml() en la acción, la extracción de la cadena de símbolo en sí, devolviéndolo al cliente en JSON y colocándolo en el campo oculto anti-falsificación manualmente en JavaScript) pero no funciona - una llamada posterior de Ajax falla en el ValidateAntiForgeryTokenAttribute en el servidor. De hecho, cada llamada a AntiForgery.GetHtml() (que es esencialmente lo que hace la ayudante Html.AntiForgeryToken()) produce un token diferente, lo que invalida el anterior.

También traté de establecer HttpContext.User = new GenericPrincipal(new GenericIdentity(email), null); como se detalla here, pero no funciona.

Nota:This solution no funciona para mí, debido a mi situación específica: Un Ajax de inicio de sesión que cambia la identidad del usuario en el servidor y, por tanto, cada token que se generó antes del inicio de sesión no es válido; this solution tampoco se aplica porque aborda un problema diferente.

+1

¿Por qué está utilizando un AntiForgeryToken en su página de inicio de sesión, cuando el usuario no está autenticado? ¿Qué estás protegiendo? –

+2

La función de inicio de sesión no es una página, es un fragmento dentro de la plantilla del sitio. De hecho, no es necesario en la función de inicio de sesión, pero el problema surge después: después del método de inicio de sesión en el lado del servidor establece el usuario actual (HttpContext.User) y lo devuelve. En esta etapa, la página ya debe tener algún campo oculto de token anti falsificación para atender más llamadas Ajax. –

+0

Phil Haack publicó este artículo hace unos días. ¿Esto de alguna manera está relacionado con tu problema? http://haacked.com/archive/2011/10/10/preventing-csrf-with-ajax.aspx ... "El problema radica en el hecho de que debajo del capó, en lo profundo de la pila de llamadas, el atributo se asoma en la colección Request.Form para tomar el token anti falsificación, pero cuando publica datos codificados en JSON, no hay una colección de formularios para hablar ". – JasperLamarCrabb

Respuesta

6

Deberá borrar y volver a hacer cualquier token de formulario existente que tenga al iniciar sesión. Esto significa que su código de inicio de sesión tendrá que actualizar la página actual (algo así como la porción AJAX eh), su propia implementación de token, o tendrá que actualizar su token. Es posible solicitar una vista parcial, extraer el token y actualizar su formulario. En realidad, puede tener una url relajante que devuelve nada más que un token a un usuario autenticado. Se puede argumentar que se trata de un problema de seguridad, pero no lo creo porque es simplemente una manera más fácil de obtener un token en lugar de solicitar una vista parcial o de otro tipo.

Usted debe ser capaz de obtener fácilmente los casos de fichas para reemplazar a través de:

 
var token = $('input[name=""__RequestVerificationToken""]'); 

EDITAR Después de volver a leer unas cuantas más veces - me pregunta

¿Por qué tener una ficha en la formulario si el usuario no está conectado. ¿Permites que se "opere" el mismo formulario sin iniciar sesión y sin iniciar sesión? La mayoría de los sitios en la red, incluso en este caso, redireccionarán para iniciar sesión. ¿Estoy entendiendo esto correctamente? Si es así, puede considerar omitir el token aquí o usar un segundo tipo de token para usuarios no autenticados.Creo que están diciendo que un usuario no autenticado ya puede enviar algo en la aplicación, nuevamente si entiendo esto correctamente, sin ser autenticado.

+0

En cuanto a su última pregunta, ¿por qué necesito un token para usuarios no autenticados? Consulte mi respuesta al comentario de Ben. Trataré de crear una vista parcial con un token y actualizarlo con los resultados. ¡Suena como una buena idea! –

+0

Aún no lo he hecho, pero aceptaré tu respuesta porque suena prometedor :-) –

3

Ok, lo que hice fue combinar la respuesta desde aquí: jQuery Ajax calls and the Html.AntiForgeryToken() con un parcial. Estoy usando knockout, pero para aquellos que no estén familiarizados con él, todavía deberían poder seguirlo bastante fácilmente.

Primero mi html:

<form id="__AjaxAntiForgeryForm" action="#" method="post">@{Html.RenderPartial("AntiForgeryToken");}</form> 
<div id="loginTestView"> 
    <button data-bind="visible: signedIn() == false,click: signIn">Sign In</button> 
    <button data-bind="visible: signedIn, click: signOut">Sign Out</button> 

    <form> 
     <button data-bind="click: testToken">Test Token</button> 
    </form> 
</div> 

La principal diferencia es que en lugar de @ Html.AntiForgeryToken() Tengo un AntiForgeryToken parcial que contiene @ Html.AntiForgeryToken().

Así que para aclarar realmente ahora tengo un archivo con sólo AntiForgeryToken.cshtml:

@Html.AntiForgeryToken() 

Ahora cuando se registra de entrada/salida tiene que actualizar el token por lo que el Javascript/jQuery parece:

$(document).ready(function() { 
    AddAntiForgeryToken = function (data) { 
     data.__RequestVerificationToken = $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val(); 
     return data; 
    }; 

    var viewmodel = function() { 
     var vm = this; 

     vm.signedIn = ko.observable(false); 

     vm.signIn = function() { 
      $.post('Home/SignIn', function() { 
       vm.signedIn(true); 
       $.get('Home/GetAuthToken', function (newToken) { 
        $('#__AjaxAntiForgeryForm').html(newToken); 
       }); 
      }); 

     }; 
     vm.signOut = function() { 
      $.post('Home/SignOut', function() { 
       vm.signedIn(false); 
       $.get('Home/GetAuthToken', function (newToken) { 
        $('#__AjaxAntiForgeryForm').html(newToken); 
       }); 
      }); 
     }; 
     vm.testToken = function() { 
      $.post('Home/TestToken', AddAntiForgeryToken({ stuff: 'stuff' })); 
     }; 
    }; 

    ko.applyBindings(new viewmodel(), $('#loginTestView')[0]); 
}); 

Lo más importante a tener en cuenta aquí es que el $ .get debe pasar después del $ .post para iniciar sesión/salir. Este código podría limpiarse un poco, pero esa es la principal ventaja. Si no lo hace, dado que las solicitudes son asincrónicas, $ .get podría (y probablemente lo hará) volver antes de que usted haya iniciado sesión.

Eso debería hacerlo. No me he encontrado con ningún otro momento cuando se actualiza el token, pero solo requeriría otra llamada para actualizar el parcial.

+0

Buena solución @rball, lo intentaré (aunque tomará un tiempo). –