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

时间:2010-05-25 17:04:59

标签: asp.net-mvc ajax json antiforgerytoken

我正在使用以下帖子的代码:

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

使用下面的代码我认为只需在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(); }
        });
    }

13 个答案:

答案 0 :(得分:56)

自MVC 4以来,您不需要ValidationHttpRequestWrapper解决方案。根据此link

  1. 将标记放入标题中。
  2. 创建过滤器。
  3. 将属性放在您的方法上。
  4. 这是我的解决方案:

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

答案 1 :(得分:46)

错误的是,应该处理此请求的控制器操作以及[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值。

答案 2 :(得分:11)

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

首先,我决定挂钩我的jQuery Ajax调用,所以我不要经常重复自己。此JavaScript代码段确保所有ajax(post)调用都将我的请求验证令牌添加到请求中。注意:.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参数。

答案 3 :(得分:7)

您可以设置$.ajaxtraditional属性并将其设置为true,以便以网址编码形式发送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)
{
}

答案 4 :(得分:5)

我在我的JSON对象中持有令牌,当帖子为json时,我最终修改了ValidateAntiForgeryToken类以检查InputStream对象的Request。我已经写了一篇关于它的blog post,希望你会觉得它很有用。

答案 5 :(得分:5)

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

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

答案 6 :(得分:5)

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

原因是已创建AntiForgeryToken以防止CSRF。由于您无法将AJAX数据发布到其他主机,并且HTML表单无法提交JSON作为请求正文,因此您无需保护您的应用程序免受已发布的JSON的攻击。<​​/ p>

答案 7 :(得分:3)

我已使用RequestHeader全局解决了这个问题。

  var DisciplinesCollection = Backbone.Collection.extend({
model: Discipline,
filterByCategory: function(cat){
  return _(this.filter(function(data) {
    return _.contains(data.get("category"), cat);        
  }));
}

其中requestVerificationTokenVariable是包含标记值的变量字符串。 然后所有ajax调用都将令牌发送到服务器,但默认的ValidateAntiForgeryTokenAttribute获取Request.Form值。 我已经写了并添加了这个globalFilter,它将令牌从header复制到request.form,而不是我可以使用默认的ValidateAntiForgeryTokenAttribute:

    <?php

//simulating your array

$x = new StdClass();
$x->task_id = 1;
$y = new StdClass();
$y->task_id = 28;
$z = new StdClass();
$z->task_id = 43;

$object = [

    [0 => $x],

    [

    1 => $y,
    2 => $z

    ]];

//converting the objects to array
$array = json_decode(json_encode($object), true);

$new_array = [];

//implode
foreach($array as $k1 => $a){

    $new_array[$k1] = implode(', ', array_column($a, 'task_id'));

}

var_dump($new_array);

这项工作对我来说:)

答案 8 :(得分:2)

查看Dixin's Blog以获取有关这方面的精彩帖子。

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

随着该页面上的jQuery插件,您可以执行以下简单的操作:

        data = $.appendAntiForgeryToken(data,null);

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

答案 9 :(得分: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它在表单中。 / p>

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

    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),而我为了简洁而省略了。如有必要,我可以添加它。

答案 10 :(得分:1)

使用Newtonsoft.JSON库可以使使用AntiForgerytoken进行基于AJAX的模型发布变得更加容易
以下方法对我有用:
保持这样的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);
    }

希望这会有所帮助:)

答案 11 :(得分: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);
    }
});

```

答案 12 :(得分:-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]

我希望这会有所帮助。