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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2906754/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 17:30:30  来源:igfitidea点击:

How can I supply an AntiForgeryToken when posting JSON data using $.ajax?

asp.net-mvcajaxjsonantiforgerytoken

提问by HerbalMart

I am using the code as below of this post:

我正在使用这篇文章下面的代码:

First I will fill an array variable with the correct values for the controller action.

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

Using the code below I think it should be very straightforward by just adding the following line to the JavaScript code:

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

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

The <%= Html.AntiForgeryToken() %>is at its right place, and the action has a [ValidateAntiForgeryToken]

<%= Html.AntiForgeryToken() %>是在其正确的位置,动作有[ValidateAntiForgeryToken]

But my controller action keeps saying: "Invalid forgery token"

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

What am I doing wrong here?

我在这里做错了什么?

Code

代码

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

回答by Ken Q

You don't need the ValidationHttpRequestWrapper solution since MVC 4. According to this link.

自 MVC 4 起,您不需要 ValidationHttpRequestWrapper 解决方案。根据此链接

  1. Put the token in the headers.
  2. Create a filter.
  3. Put the attribute on your method.
  1. 将令牌放在标题中。
  2. 创建过滤器。
  3. 将属性放在您的方法上。

Here is my solution:

这是我的解决方案:

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

回答by Darin Dimitrov

What is wrong is that the controller action that is supposed to handle this request and which is marked with the [ValidateAntiForgeryToken]expects a parameter called __RequestVerificationTokento be POSTed along with the request.

错误的是应该处理此请求并标有[ValidateAntiForgeryToken]期望参数的控制器操作__RequestVerificationToken与请求一起发布。

There's no such parameter POSTed as you are using JSON.stringify(data)which converts your form to its JSON representation and so the exception is thrown.

没有像您使用的那样 POSTed 参数JSON.stringify(data)会将您的表单转换为其 JSON 表示形式,因此会引发异常。

So I can see two possible solutions here:

所以我可以在这里看到两种可能的解决方案:

Number 1: Use x-www-form-urlencodedinstead of JSONfor sending your request parameters:

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

Number 2: Separate the request into two parameters:

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

So in all cases you need to POST the __RequestVerificationTokenvalue.

因此,在所有情况下,您都需要 POST__RequestVerificationToken值。

回答by 360Airwalk

I was just implementing this actual problem in my current project. I did it for all Ajax POSTs that needed an authenticated user.

我只是在我当前的项目中实施这个实际问题。我为所有需要经过身份验证的用户的 Ajax POST 都这样做了。

First off, I decided to hook my jQuery Ajax calls so I do not to repeat myself too often. This JavaScript snippet ensures all ajax (post) calls will add my request validation token to the request. Note: the name __RequestVerificationToken is used by the .NET framework so I can use the standard Anti-CSRF features as shown below.

首先,我决定钩住我的 jQuery Ajax 调用,这样我就不会经常重复自己。此 JavaScript 代码段确保所有 ajax(发布)调用都将我的请求验证令牌添加到请求中。注意:名称 __RequestVerificationToken 由 .NET 框架使用,因此我可以使用标准的 Anti-CSRF 功能,如下所示。

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

In your Views where you need the token to be available to the above JavaScript code, just use the common HTML-Helper. You can basically add this code wherever you want. I placed it within a if(Request.IsAuthenticated) statement:

在您需要令牌可用于上述 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

In your controller simply use the standard ASP.NET MVC anti-CSRF mechanism. I did it like this (though I actually used a salt).

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

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

With Firebug or a similar tool you can easily see how your POST requests now have a __RequestVerificationToken parameter appended.

使用 Firebug 或类似工具,您可以轻松查看您的 POST 请求现在如何附加了 __RequestVerificationToken 参数。

回答by N30

You can set $.ajax's traditionalattribute and set it to true, to send json data as url encoded form. Make sure to set type:'POST'. With this method you can even send arrays and you do not have to use JSON.stringyfy or any changes on server side (e.g. creating custom attributes to sniff header )

您可以设置$.ajaxtraditional属性并将其设置为true, 以将 json 数据作为 url 编码形式发送。确保设置type:'POST'. 使用这种方法,您甚至可以发送数组,而不必使用 JSON.stringyfy 或服务器端的任何更改(例如,创建自定义属性来嗅探 header )

I have tried this on ASP.NET MVC3 and jquery 1.7 setup and it's working

我已经在 ASP.NET MVC3 和 jquery 1.7 设置上尝试过这个,它正在工作

following is the code snippet.

以下是代码片段。

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

This will match with MVC action with following signature

这将与具有以下签名的 MVC 操作匹配

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

回答by Vasioky

You can't validate an content of type contentType: 'application/json; charset=utf-8' because your date will be uploaded not in the Formproperty of the request, but in the InputStream property, and you will never have this Request.Form["__RequestVerificationToken"].

您无法验证 contentType 类型的内容:'application/json; charset=utf-8' 因为您的日期将不会上传到请求的Form属性中,而是上传到 InputStream 属性中,并且您永远不会拥有这个 Request.Form["__RequestVerificationToken"]。

This will be always empty and validation will fail.

这将始终为空,验证将失败。

回答by Antoine Leclair

You won't ever have to validate an AntiForgeryToken when you receive posted JSON.

当您收到发布的 JSON 时,您将永远不必验证 AntiForgeryToken。

The reason is that AntiForgeryToken has been created to prevent CSRF. Since you can't post AJAX data to another host and HTML forms can't submit JSON as the request body, you don't have to protect your app against posted JSON.

原因是创建了 AntiForgeryToken 来防止 CSRF。由于您无法将 AJAX 数据发布到另一个主机,并且 HTML 表单无法提交 JSON 作为请求正文,因此您不必保护您的应用程序免受发布的 JSON 的影响。

回答by TWith2Sugars

I hold the token in my JSON object and I ended up modifying the ValidateAntiForgeryToken class to check the InputStreamof the Requestobject when the post is json. I've written a blog postabout it, hopefully you might find it useful.

我持有令牌在我的JSON对象,我结束了修改ValidateAntiForgeryToken类检查的InputStream中的请求对象时的职位是JSON。我写了一篇关于它的博客文章,希望你会发现它有用。

回答by Davide Ciarmiello

I have resolved it globally with RequestHeader.

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

where the requestVerificationTokenVariable is an variable string that contains the token value. Then all ajax call send the token to the server, but the default ValidateAntiForgeryTokenAttribute get the Request.Form value. I have writed and added this globalFilter that copy token from header to request.form, than i can use the default ValidateAntiForgeryTokenAttribute:

其中 requestVerificationTokenVariable 是一个包含令牌值的变量字符串。然后所有 ajax 调用将令牌发送到服务器,但默认的 ValidateAntiForgeryTokenAttribute 获取 Request.Form 值。我已经编写并添加了这个 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);
    }
}

This work for me :)

这对我有用:)

回答by AlDev

Check out Dixin's Blogfor a great post on doing exactly that.

查看Dixin 的博客,了解如何做到这一点。

Also, why not use $.post instead of $.ajax?

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

Along with the jQuery plugin on that page, you can then do something as simple as:

连同该页面上的 jQuery 插件,您可以做一些简单的事情:

        data = $.appendAntiForgeryToken(data,null);

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

回答by DrShaffopolis

I had to be a little shady to validate anti-forgery tokens when posting JSON, but it worked.

在发布 JSON 时,我不得不有点阴暗地验证防伪令牌,但它有效。

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

But, as a few people already mentioned, the validation only checks the form - not JSON, and not the query string. So, we overrode the attribute's behavior. Re-implementing all of the validation would have been terrible (and probably not secure), so I just overrode the Form property to, if the token were passed in the QueryString, have the built-in validation THINK it was in the Form.

但是,正如一些人已经提到的,验证只检查表单——而不是 JSON,而不是查询字符串。因此,我们覆盖了属性的行为。重新实现所有的验证会很糟糕(并且可能不安全),所以我只是覆盖了 Form 属性,如果令牌在 QueryString 中传递,那么内置验证认为它在表单中。

That's a little tricky because the form is read-only, but doable.

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

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

There's some other stuff that's different about our solution (specifically, we're using an HttpModule so we don't have to add the attribute to every single POST) that I left out in favor of brevity. I can add it if necessary.

我们的解决方案还有一些不同的东西(具体来说,我们使用的是 HttpModule,因此我们不必将属性添加到每个单独的 POST),为了简洁起见,我省略了这些内容。如果需要,我可以添加它。