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


77

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

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

使用下面的代码,我认为只需将以下行添加到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(); }
        });
    }

Answers:


67

你不需要因为MVC 4. ValidationHttpRequestWrapper解决方案根据这个链接

  1. 将令牌放在标题中。
  2. 创建一个过滤器。
  3. 将属性放在您的方法上。

这是我的解决方案:

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

你在哪里上公共课ValidateJsonAntiForgeryTokenAttribute
Turp 2014年

1
我直接在名为Filters的项目根目录中创建了一个文件夹,在其中创建了一个名为ValidateJsonAntiForgeryTokenAttribute.cs的类。
肯Q

那仍然对我不起作用。我在项目根目录下的文件夹中创建了新的.CS文件,并[ValidateJsonAntiForgeryToken]ActionResult,然后让JS正是因为你拥有它。Chrome开发者工具“网络>页面名称>页眉”显示:__RequestVerificationToken:egrd5Iun...8AH6_t8w2Request Headers。还有什么可能是错的!?
Turp 2014年

1
我现在正在工作;这很可能是我的缓存问题!谢谢!这个答案很棒!
Turp 2014年

2
优雅的解决方案,非常好地使用属性,结果代码更简洁。
气膝

49

出问题的是,应该处理此请求并用[ValidateAntiForgeryToken]期望标记的控制器动作期望__RequestVerificationToken与该请求一起被称为POST的参数。

使用时没有张贴这样的参数,该参数JSON.stringify(data)将您的表单转换为其JSON表示形式,因此引发了异常。

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

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

因此,在所有情况下,您都需要发布该__RequestVerificationToken值。


4
我喜欢这种方法,它的作用是...只要您不希望通过MVC 2 Futures / MVC 3类JsonValueProviderFactory对字符串化的json对象进行水化操作,并且您自己手动处理水化操作,那么您就可以忽略__RequestVerificationToken。如果我不告诉contentType期望$ .ajax为json,那么将处理验证令牌,但是json对象不会被水化。如果我确实告诉set json contentType,那么防伪验证将失败。因此,我将研究TWith2Sugars解决方案。但是以上确实确实有效!
kdawg

如果您已将参数传递给包含ajax调用的函数,则可以使用$ .extend'不加干扰地'添加令牌。`var data = $ .extend(parameters,{__RequestVerificationToken:令牌,jsonRequest:参数}); `:stackoverflow.com/questions/617036/appending-to-a-json-object
Greg Ogle,2012年

@kdawg您是如何使代码正常工作的?或如何定义data变量?
hardywang 2012年

@GregOgle仅在不发布JSON的较简单情况下,通过简单的分配就已经很好地涵盖了此答案。不需要$ .extend()的CPU滴答声。
克里斯·

我曾用“2号”由@Darin季米特洛夫提议,为了为它工作,我不得不删除下列参数我有$阿贾克斯: dataType: 'JSON'而且contentType: 'application/json; charset=utf-8'正是像达林季米特洛夫有它自己的岗位。
萨沙

10

我只是在当前项目中实现了这个实际问题。我对所有需要经过身份验证的用户的Ajax POST都执行了此操作。

首先,我决定挂断我的jQuery Ajax调用,这样我就不必再重复一遍了。这个JavaScript程式码片段可确保所有的ajax(发布)呼叫都会将我的要求验证凭证加入要求。注意:.NET框架使用名称__RequestVerificationToken,因此我可以使用如下所示的标准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);
            }
        }
    });
});

在需要令牌可用于以上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参数。


根据我的测试,该功能适用​​于MVC中大多数类型的ajax帖子,我已经放到Layout中,而无需任何其他代码即可使用。

3
在我看来,您错过了重点。您的代码仅应将请求数据有效application/x-www-form-urlencoded内容作为内容类型使用。OP希望将其请求数据有效载荷发送为application/json。附加&__Request...到JSON有效负载应失败。(他不是在索要JSON响应,这是您的代码示例,而是索要JSON请求。)
Frédéric

7

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

我已经在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)
{
}

当尝试发送包含C#值类型数组的数据时,此方法有效,但是使用用户定义的类时,我无法使其正常工作。您需要采取其他措施才能使它正常工作吗?
Liam Flanagan 2014年

6

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

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



5

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

原因是创建了AntiForgeryToken来防止CSRF。由于您无法将AJAX数据发布到其他主机,HTML表单也无法提交JSON作为请求正文,因此您不必针对发布的JSON保护应用程序。


3
这并不总是正确的。可以使用HTML表单伪造JSON帖子。如果查看AjaxRequestExtensions.IsAjaxRequest,它将在请求正文中检查“ X-Requested-With”,而不仅仅是标头。因此,您可以滚动自己的验证以确保使用AJAX发布数据,或者添加AntiForgeryToken。
Jeow Li Huan

但是请求的主体将不是JSON,而是表单URL编码的。
Antoine Leclair 2012年

3
谁说您不能将数据ajax到另一台主机? stackoverflow.com/questions/298745/...
亚当Tuliper - MSFT

1
同意,但我的发言是对“您不能将Ajax数据发布到另一台主机”的回应。您可以发布数据。如果您的意思有所不同,可能正在编辑,因为它看起来像您做不到。
Adam Tuliper-MSFT 2012年

2
如果您可能要求Action仅可用于AJAX请求,但不能,则为true。就像声明的那样。您唯一可以做的就是将Action锁定为仅接受application / json作为请求的主体。但是我对如何将MVC中的动作仅限制为特定的内容类型并不十分熟悉,我猜您将不得不做很多自定义工作。据我所知,这不是开箱即用的功能。
Nicholi 2013年

3

我已经使用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是包含令牌值的可变字符串。然后所有的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);
    }
}

这项工作对我来说:)


2

查阅Dixin的博客,获得有关此操作的精彩文章。

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

通过该页面上的jQuery插件,您可以执行以下操作:

        data = $.appendAntiForgeryToken(data,null);

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

2

与AntiForgerytoken AJAX基于模型的发布可以制成更容易一点与Newtonsoft.JSON库
下面为我工作的方法:
保持你的AJAX后是这样的:

$.ajax({
  dataType: 'JSON',
  url: url,
  type: 'POST',
  context: document.body,
  data: {
    '__RequestVerificationToken': token,
    'model_json': JSON.stringify(data)
  };,
  success: function() {
    refresh();
  }
});

然后在您的MVC动作中:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(FormCollection data) {
 var model = JsonConvert.DeserializeObject < Order > (data["model_json"]);
 return Json(1);
}

希望这可以帮助 :)


1

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

但是,正如少数人已经提到的那样,验证仅检查表单-不检查JSON,也不检查查询字符串。因此,我们覆盖了属性的行为。重新实现所有验证将是可怕的(并且可能不安全),因此我只是覆盖了Form属性,如果令牌在QueryString中传递,则将内置验证THINK包含在Form中。

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

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

为了简洁起见,我遗漏了一些其他的东西(具体来说,我们使用的是HttpModule,因此我们不必将属性添加到每个POST中)。如果需要,我可以添加它。


0

对我来说不幸的是,其他答案依赖于jquery处理的某些请求格式,并且在直接设置有效负载时它们都不起作用。(公平地说,将其放在标题中是可以的,但是我不想走那条路线。)

为了在beforeSend功能中完成此任务,需要进行以下工作。 $.params()将对象转换为标准格式/ URL编码格式。

我尝试了使用令牌对json进行字符串化的各种变体,但没有一个起作用。

$.ajax({
...other params...,
beforeSend: function(jqXHR, settings){

    var token = ''; //get token

    data = {
        '__RequestVerificationToken' : token,
        'otherData': 'value'
     }; 
    settings.data = $.param(data);
    }
});

```


请让我知道是否存在错误-我只是手动输入该错误,而不是复制并粘贴那是我真正的代码的混乱尝试:\
拼写2015年

-1

您应该将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批注来验证请求

[ValidateAntiForgeryToken]
        [HttpPost]

我希望这有帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.