80

我正在使用如下代码:

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

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

13 回答 13

70

自 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);
}
于 2014-06-24T19:19:07.673 回答
51

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

没有像您使用的那样的参数 POSTedJSON.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值。

于 2010-05-25T17:10:15.077 回答
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 参数。

于 2012-04-04T15:37:59.677 回答
9

您可以设置$.ajaxtraditional属性并将其设置为true, 以将 json 数据作为 url 编码形式发送。确保设置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)
{
}
于 2013-10-11T19:03:34.570 回答
5

当您收到已发布的 JSON 时,您无需验证 AntiForgeryToken。

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

于 2011-09-29T20:43:26.067 回答
5

我将令牌保存在我的 JSON 对象中,最后修改了 ValidateAntiForgeryToken 类以在帖子为 json 时检查Request对象的InputStream 。我已经写了一篇关于它的博客文章,希望你会发现它有用。

于 2010-09-24T14:41:16.363 回答
5

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

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

于 2011-07-12T13:37:24.127 回答
5

我已经用 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 值。我已经编写并添加了这个将令牌从标头复制到 request.form 的 globalFilter,然后我可以使用默认的 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);
    }
}

这对我有用:)

于 2017-03-22T17:44:18.093 回答
2

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

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

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

        data = $.appendAntiForgeryToken(data,null);

        $.post(url, data, function() { refresh(); }, "json");
于 2010-07-09T04:41:28.197 回答
2

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

希望这可以帮助 :)

于 2018-10-23T16:56:00.557 回答
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 中传递,则内置验证认为它在 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),为了简洁起见,我省略了这些内容。如果需要,我可以添加它。

于 2013-07-15T23:09:25.773 回答
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-11-19T05:16:23.123 回答
-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]

我希望这有帮助。

于 2014-02-20T14:15:41.147 回答