¿Cómo puedo suministrar un AntiForgeryToken cuando publico datos JSON usando $ .ajax?

Estoy usando el código como debajo de esta publicación:

Primero llenaré una variable de matriz con los valores correctos para la acción del controlador.

Usando el siguiente código creo que debería ser muy sencillo al agregar la siguiente línea al código JavaScript:

data["__RequestVerificationToken"] = $('[name=__RequestVerificationToken]').val(); 

El está en su lugar correcto, y la acción tiene un [ValidateAntiForgeryToken]

Pero mi acción de controlador sigue diciendo: “token de falsificación no válido”

¿Qué estoy haciendo mal aquí?

Código

 data["fiscalyear"] = fiscalyear; data["subgeography"] = $(list).parent().find('input[name=subGeography]').val(); data["territories"] = new Array(); $(items).each(function() { data["territories"].push($(this).find('input[name=territory]').val()); }); if (url != null) { $.ajax( { dataType: 'JSON', contentType: 'application/json; charset=utf-8', url: url, type: 'POST', context: document.body, data: JSON.stringify(data), success: function() { refresh(); } }); } 

No necesita la solución ValidationHttpRequestWrapper desde MVC 4. Según este enlace .

  1. Pon la ficha en los encabezados.
  2. Crea un filtro
  3. Pon el atributo en tu método.

Aquí está mi solución:

 var token = $('input[name="__RequestVerificationToken"]').val(); var headers = {}; headers['__RequestVerificationToken'] = token; $.ajax({ type: 'POST', url: '/MyTestMethod', contentType: 'application/json; charset=utf-8', headers: headers, data: JSON.stringify({ Test: 'test' }), dataType: "json", success: function () {}, error: function (xhr) {} }); [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public class ValidateJsonAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationContext filterContext) { if (filterContext == null) { throw new ArgumentNullException("filterContext"); } var httpContext = filterContext.HttpContext; var cookie = httpContext.Request.Cookies[AntiForgeryConfig.CookieName]; AntiForgery.Validate(cookie != null ? cookie.Value : null, httpContext.Request.Headers["__RequestVerificationToken"]); } } [HttpPost] [AllowAnonymous] [ValidateJsonAntiForgeryToken] public async Task MyTestMethod(string Test) { return Json(true); } 

Lo que está mal es que la acción del controlador que se supone que maneja esta solicitud y que está marcada con [ValidateAntiForgeryToken] espera que un parámetro llamado __RequestVerificationToken sea ​​POST junto con la solicitud.

No existe dicho parámetro POST puesto que está utilizando JSON.stringify(data) que convierte su formulario a su representación JSON y, por lo tanto, se lanza la excepción.

Entonces puedo ver dos posibles soluciones aquí:

Número 1: Use x-www-form-urlencoded lugar de JSON para enviar sus parámetros de solicitud:

 data["__RequestVerificationToken"] = $('[name=__RequestVerificationToken]').val(); data["fiscalyear"] = fiscalyear; // ... other data if necessary $.ajax({ url: url, type: 'POST', context: document.body, data: data, success: function() { refresh(); } }); 

Número 2: Separe la solicitud en dos parámetros:

 data["fiscalyear"] = fiscalyear; // ... other data if necessary var token = $('[name=__RequestVerificationToken]').val(); $.ajax({ url: url, type: 'POST', context: document.body, data: { __RequestVerificationToken: token, jsonRequest: JSON.stringify(data) }, success: function() { refresh(); } }); 

Entonces, en todos los casos, debe __RequestVerificationToken valor __RequestVerificationToken .

Solo estaba implementando este problema real en mi proyecto actual. Lo hice para todos los POST de Ajax que necesitaban un usuario autenticado.

En primer lugar, decidí conectar mis llamadas jQuery Ajax, así que no me repito demasiado a menudo. Este fragmento de JavaScript garantiza que todas las llamadas ajax (posteriores) agregarán mi token de validación de solicitud a la solicitud. Nota: el nombre __RequestVerificationToken es utilizado por .NET framework para que pueda usar las funciones estándar de Anti-CSRF como se muestra a continuación.

 $(document).ready(function () { securityToken = $('[name=__RequestVerificationToken]').val(); $('body').bind('ajaxSend', function (elm, xhr, s) { if (s.type == 'POST' && typeof securityToken != 'undefined') { if (s.data.length > 0) { s.data += "&__RequestVerificationToken=" + encodeURIComponent(securityToken); } else { s.data = "__RequestVerificationToken=" + encodeURIComponent(securityToken); } } }); }); 

En sus Vistas donde necesita que el token esté disponible para el código JavaScript anterior, simplemente use el HTML-Helper común. Básicamente puedes agregar este código donde quieras. Lo coloqué dentro de una statement if (Request.IsAuthenticated):

 @Html.AntiForgeryToken() // You can provide a string as salt when needed which needs to match the one on the controller 

En su controlador simplemente use el mecanismo estándar anti-CSRF de ASP.NET MVC. Lo hice así (aunque en realidad usé una sal).

 [HttpPost] [Authorize] [ValidateAntiForgeryToken] public JsonResult SomeMethod(string param) { // Do something return Json(true); } 

Con Firebug o una herramienta similar, puede ver fácilmente cómo sus solicitudes POST ahora tienen un parámetro __RequestVerificationToken adjunto.

Puede establecer el atributo traditional $ .ajax y establecerlo en true , para enviar datos json como formulario codificado de url. Asegúrese de establecer el type:'POST' . Con este método, incluso puedes enviar matrices y no tienes que usar JSON.stringyfy ni ningún cambio en el lado del servidor (por ejemplo, crear atributos personalizados para olfatear el encabezado)

He probado esto en la configuración de ASP.NET MVC3 y jquery 1.7 y está funcionando

siguiente es el fragmento de código.

 var data = { items: [1, 2, 3], someflag: true}; data.__RequestVerificationToken = $(':input[name="__RequestVerificationToken"]').val(); $.ajax({ url: 'Test/FakeAction' type: 'POST', data: data dataType: 'json', traditional: true, success: function (data, status, jqxhr) { // some code after succes }, error: function () { // alert the error } }); 

Esto coincidirá con la acción de MVC con la siguiente firma

 [HttpPost] [Authorize] [ValidateAntiForgeryToken] public ActionResult FakeAction(int[] items, bool someflag) { } 

Mantengo el token en mi objeto JSON y terminé modificando la clase ValidateAntiForgeryToken para verificar el InputStream del objeto Request cuando la publicación es json. He escrito una publicación en el blog sobre esto, con suerte, puede que le resulte útil.

Nunca tendrá que validar un AntiForgeryToken cuando reciba JSON publicado.

La razón es que AntiForgeryToken se ha creado para evitar CSRF. Como no puede publicar datos AJAX en otro host y los formularios HTML no pueden enviar JSON como el cuerpo de la solicitud, no tiene que proteger su aplicación contra JSON publicado.

No puede validar un contenido de tipo contentType: ‘application / json; charset = utf-8 ‘porque su fecha no se cargará en la propiedad Form de la solicitud, sino en la propiedad InputStream, y nunca tendrá este Request.Form [“__ RequestVerificationToken”].

Esto siempre estará vacío y la validación fallará.

Echa un vistazo al Blog de Dixin para ver una buena publicación sobre cómo hacer exactamente eso.

Además, ¿por qué no usar $ .post en lugar de $ .ajax?

Junto con el complemento jQuery en esa página, puede hacer algo tan simple como:

  data = $.appendAntiForgeryToken(data,null); $.post(url, data, function() { refresh(); }, "json"); 

Tuve que ser un poco confuso para validar los tokens antifalsificación cuando publicaba JSON, pero funcionó.

 //If it's not a GET, and the data they're sending is a string (since we already had a separate solution in place for form-encoded data), then add the verification token to the URL, if it's not already there. $.ajaxSetup({ beforeSend: function (xhr, options) { if (options.type && options.type.toLowerCase() !== 'get' && typeof (options.data) === 'string' && options.url.indexOf("?__RequestVerificationToken=") < 0 && options.url.indexOf("&__RequestVerificationToken=") < 0) { if (options.url.indexOf('?') < 0) { options.url += '?'; } else { options.url += '&'; } options.url += "__RequestVerificationToken=" + encodeURIComponent($('input[name=__RequestVerificationToken]').val()); } } }); 

Pero, como algunas personas ya mencionaron, la validación solo verifica el formulario, no JSON, y no la cadena de consulta. Entonces, anulamos el comportamiento del atributo. Volver a implementar toda la validación hubiera sido terrible (y probablemente no seguro), así que simplemente eliminé la propiedad del Formulario, si el token se pasó en QueryString, tengo la validación incorporada THINK en el Formulario.

Eso es un poco complicado porque el formulario es de solo lectura, pero factible.

  if (IsAuth(HttpContext.Current) && !IsGet(HttpContext.Current)) { //if the token is in the params but not the form, we sneak in our own HttpContext/HttpRequest if (HttpContext.Current.Request.Params != null && HttpContext.Current.Request.Form != null && HttpContext.Current.Request.Params["__RequestVerificationToken"] != null && HttpContext.Current.Request.Form["__RequestVerificationToken"] == null) { AntiForgery.Validate(new ValidationHttpContextWrapper(HttpContext.Current), null); } else { AntiForgery.Validate(new HttpContextWrapper(HttpContext.Current), null); } } //don't validate un-authenticated requests; anyone could do it, anyway private static bool IsAuth(HttpContext context) { return context.User != null && context.User.Identity != null && !string.IsNullOrEmpty(context.User.Identity.Name); } //only validate posts because that's what CSRF is for private static bool IsGet(HttpContext context) { return context.Request.HttpMethod.ToUpper() == "GET"; } 

...

 internal class ValidationHttpContextWrapper : HttpContextBase { private HttpContext _context; private ValidationHttpRequestWrapper _request; public ValidationHttpContextWrapper(HttpContext context) : base() { _context = context; _request = new ValidationHttpRequestWrapper(context.Request); } public override HttpRequestBase Request { get { return _request; } } public override IPrincipal User { get { return _context.User; } set { _context.User = value; } } } internal class ValidationHttpRequestWrapper : HttpRequestBase { private HttpRequest _request; private System.Collections.Specialized.NameValueCollection _form; public ValidationHttpRequestWrapper(HttpRequest request) : base() { _request = request; _form = new System.Collections.Specialized.NameValueCollection(request.Form); _form.Add("__RequestVerificationToken", request.Params["__RequestVerificationToken"]); } public override System.Collections.Specialized.NameValueCollection Form { get { return _form; } } public override string ApplicationPath { get { return _request.ApplicationPath; } } public override HttpCookieCollection Cookies { get { return _request.Cookies; } } } 

Hay algunas otras cosas que son diferentes acerca de nuestra solución (específicamente, estamos usando un HttpModule para que no tengamos que agregar el atributo a cada POST) que dejé en favor de la brevedad. Puedo agregarlo si es necesario.

Lo he resuelto globalmente con RequestHeader.

 $.ajaxPrefilter(function (options, originalOptions, jqXhr) { if (options.type.toUpperCase() === "POST") { // We need to add the verificationToken to all POSTs if (requestVerificationTokenVariable.length > 0) jqXhr.setRequestHeader("__RequestVerificationToken", requestVerificationTokenVariable); } }); 

donde RequestVerificationTokenVariable es una cadena variable que contiene el valor del token. Luego, todas las llamadas ajax envían el token al servidor, pero ValidateAntiForgeryTokenAttribute predeterminado obtiene el valor Request.Form. He escrito y agregado este filtro global que copia el token del encabezado a request.form, que puedo usar el ValidateAntiForgeryTokenAttribute predeterminado:

 public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new GlobalAntiForgeryTokenAttribute(false)); } public class GlobalAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter { private readonly bool autoValidateAllPost; public GlobalAntiForgeryTokenAttribute(bool autoValidateAllPost) { this.autoValidateAllPost = autoValidateAllPost; } private const string RequestVerificationTokenKey = "__RequestVerificationToken"; public void OnAuthorization(AuthorizationContext filterContext) { var req = filterContext.HttpContext.Request; if (req.HttpMethod.ToUpperInvariant() == "POST") { //gestione per ValidateAntiForgeryToken che gestisce solo il recupero da Request.Form (non disponibile per le chiamate ajax json) if (req.Form[RequestVerificationTokenKey] == null && req.IsAjaxRequest()) { var token = req.Headers[RequestVerificationTokenKey]; if (!string.IsNullOrEmpty(token)) { req.Form.SetReadOnly(false); req.Form[RequestVerificationTokenKey] = token; req.Form.SetReadOnly(true); } } if (autoValidateAllPost) AntiForgery.Validate(); } } } public static class NameValueCollectionExtensions { private static readonly PropertyInfo NameObjectCollectionBaseIsReadOnly = typeof(NameObjectCollectionBase).GetProperty("IsReadOnly", BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Instance); public static void SetReadOnly(this NameValueCollection source, bool readOnly) { NameObjectCollectionBaseIsReadOnly.SetValue(source, readOnly); } } 

Este trabajo para mí 🙂

Desafortunadamente para mí, las otras respuestas dependen de un formato de solicitud manejado por jquery, y ninguna de ellas funcionó al configurar la carga útil directamente. (Para ser justos, ponerlo en el encabezado habría funcionado, pero no quería seguir esa ruta).

Para lograr esto en la función beforeSend , funciona lo siguiente. $.params() transforma el objeto en el formato estándar / url-encoded.

Había intentado todo tipo de variaciones de stringifying json con la ficha y ninguno de ellos funcionó.

 $.ajax({ ...other params..., beforeSend: function(jqXHR, settings){ var token = ''; //get token data = { '__RequestVerificationToken' : token, 'otherData': 'value' }; settings.data = $.param(data); } }); 

“ `

Debes colocar AntiForgeryToken en una etiqueta de formulario:

 @using (Html.BeginForm(actionName:"", controllerName:"",routeValues:null, method: FormMethod.Get, htmlAttributes: new { @class="form-validator" })) { @Html.AntiForgeryToken(); } 

luego en javascript modifica el siguiente código para ser

 var DataToSend = []; DataToSend.push(JSON.stringify(data),$('form.form-validator').serialize()); $.ajax( { dataType: 'JSON', contentType: 'application/json; charset=utf-8', url: url, type: 'POST', context: document.body, data: DataToSend, success: function() { refresh(); } }); 

entonces deberías poder validar la solicitud usando anotaciones de ActionResult

 [ValidateAntiForgeryToken] [HttpPost] 

espero que esto ayude.