如何使用$ .ajax发布JSON数据时提供AntiForgeryToken?

我正在使用下面的这个职位的代码:

首先,我将使用控制器操作的正确值填充数组variables。

使用下面的代码,我认为它应该是非常简单的,只需将以下行添加到JavaScript代码:

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

<%= Html.AntiForgeryToken() %>位于正确的位置,并且该操作具有[ValidateAntiForgeryToken]

但是我的控制器操作一直在说:“伪造令牌无效”

我在这里做错了什么?

 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(); } }); } 

什么是错误的是,应该处理这个请求,并用[ValidateAntiForgeryToken]标记的控制器操作需要一个名为__RequestVerificationToken的参数与请求一起被POST。

没有像使用JSON.stringify(data)那样发送这样的参数,它将表单转换为其JSON表示,因此抛出exception。

所以我可以看到两个可能的解决scheme:

编号1:使用x-www-form-urlencoded代替JSON发送您的请求参数:

 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(); } }); 

编号2:将请求分成两个参数:

 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(); } }); 

所以在所有情况下,您都需要POST __RequestVerificationToken值。

自MVC 4以来,您不需要ValidationHttpRequestWrapper解决scheme。根据此链接 。

  1. 把标记放在标题中。
  2. 创build一个filter。
  3. 把属性放在你的方法上。

这是我的解决scheme:

 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<JsonResult> MyTestMethod(string Test) { return Json(true); } 

我只是在当前的项目中实现这个实际的问题。 我为所有需要authentication用户的Ajax POST做了这个。

首先,我决定挂钩我的jQuery Ajax调用,所以我不要经常重复自己。 这个JavaScript片段确保所有的ajax(post)调用都会将我的请求validation令牌添加到请求中。 注意:名称__RequestVerificationToken由.NET框架使用,因此我可以使用如下所示的标准Anti-CSRFfunction。

 $(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); } } }); }); 

在您的视图中,您需要令牌可用于上面的JavaScript代码,只需使用通用的HTML-Helper即可。 你可以基本上添加这个代码,无论你想要的。 我把它放在一个if(Request.IsAuthenticated)语句中:

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

在您的控制器中,只需使用标准的ASP.NET MVC反CSRF机制。 我这样做(虽然我实际上使用了盐)。

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

使用Firebug或类似的工具,你可以很容易地看到你的POST请求现在有一个__RequestVerificationToken参数。

您可以设置$ .ajax的traditional属性并将其设置为true ,以url编码forms发送json数据。 确保设置type:'POST' 。 有了这个方法,你甚至可以发送数组,你不必使用JSON.stringyfy或服务器端的任何变化(例如创build自定义属性来嗅探头)

我已经尝试了ASP.NET MVC3和jquery 1.7安装程序,它的工作

以下是代码片段。

 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 } }); 

这将与MVC行动与以下签名相匹配

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

我在我的JSON对象中持有令牌,并最终修改了ValidateAntiForgeryToken类,以检查该post是json时Request对象的InputStream 。 我已经写了一篇关于它的博客文章 ,希望你会发现它有用。

当您收到发布的JSON时,您永远不必validationAntiForgeryToken。

原因是AntiForgeryToken已经被创build来防止CSRF。 由于您无法将AJAX数据发布到其他主机,并且HTML表单无法将JSON作为请求正文提交,因此您无需保护应用免于发布的JSON。

你不能validationtypescontentType的内容:'application / json; charset = utf-8',因为你的date不会被上传到请求的Form属性中,而是在InputStream属性中,你永远不会有这个Request.Form [“__ RequestVerificationToken”]。

这将永远是空的,validation将失败。

查看Dixin的博客做一个伟大的职位,做到这一点。

另外,为什么不使用$ .post而不是$ .ajax?

随着页面上的jQuery插件,你可以做一些简单的事情:

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

发布JSON时,我不得不做一些validation防伪标记的工作。

 //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()); } } }); 

但是,正如一些人已经提到的,validation只检查表单 – 而不是JSON,而不是查询string。 所以,我们忽略了属性的行为。 重新实现所有的validation将是可怕的(可能不安全),所以我只是否定Form属性,如果令牌传递在QueryString中,有内置的validationTHINK它是在窗体中。

这有点棘手,因为表单是只读的,但可行。

  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; } } } 

还有一些与我们的解决scheme有所不同的东西(具体来说,我们使用的是HttpModule,所以我们不必为每个POST添加属性),为了简洁,我省略了这些。 如有必要,我可以添加它。

我用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); } }); 

requestVerificationTokenVariable是一个包含令牌值的variablesstring。 然后,所有ajax调用将令牌发送到服务器,但默认的ValidateAntiForgeryTokenAttribute获取Request.Form值。 我已经writed并添加了这个globalFilter复制令牌从头到request.form,比我可以使用默认的ValidateAntiForgeryTokenAttribute:

 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); } } 

这个工作对我来说:)

不幸的是,对我来说,其他的答案依赖于由jquery处理一些请求格式,并没有一个直接设置有效载荷时工作。 (公平地说,把它放在标题中可能会奏效,但我不想走这条路。)

为了在beforeSend函数中完成这个,下面的工作。 $.params()将对象转换为标准的表单/ url编码格式。

我已经尝试了各种各样的stringJSON与令牌的变化,他们都没有工作。

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

“`

您应该将AntiForgeryToken放置在表单标签中:

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

那么在javascript中修改下面的代码即可

 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(); } }); 

那么你应该能够使用ActionResult注解validation请求

 [ValidateAntiForgeryToken] [HttpPost] 

我希望这有帮助。