jQuery Ajax调用和Html.AntiForgeryToken()

Lor*_*nzo 198 ajax asp.net-mvc csrf antiforgerytoken asp.net-mvc-2

我已经在我的应用程序中实现了对我在互联网上的一些博客文章中阅读的信息后的CSRF攻击的缓解.特别是这些帖子是我实施的驱动力

基本上这些文章和建议说,为了防止CSRF攻击,任何人都应该实现以下代码:

1)添加[ValidateAntiForgeryToken]接受POST Http动词的每个动作

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SomeAction( SomeModel model ) {
}
Run Code Online (Sandbox Code Playgroud)

2)在<%= Html.AntiForgeryToken() %>向服务器提交数据的表单中添加帮助程序

<div style="text-align:right; padding: 8px;">
    <%= Html.AntiForgeryToken() %>
    <input type="submit" id="btnSave" value="Save" />
</div>
Run Code Online (Sandbox Code Playgroud)

无论如何,在我的应用程序的某些部分,我正在使用jQuery对服务器进行Ajax POST,而根本没有任何形式.例如,当我让用户点击图像来执行特定操作时,会发生这种情况.

假设我有一个包含活动列表的表.我在表的一列上有一个图像,上面写着"将活动标记为已完成",当用户点击该活动时,我正在进行Ajax POST,如下例所示:

$("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: {},
        success: function (response) {
            // ....
        }
    });
});
Run Code Online (Sandbox Code Playgroud)

我如何<%= Html.AntiForgeryToken() %>在这些情况下使用?我应该在Ajax调用的data参数中包含帮助器调用吗?

对不起,很长的帖子,非常感谢你的帮助

编辑:

根据jayrdub的回答,我使用了以下方式

$("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: {
            AddAntiForgeryToken({}),
            id: parseInt($(this).attr("title"))
        },
        success: function (response) {
            // ....
        }
    });
});
Run Code Online (Sandbox Code Playgroud)

Jer*_*eir 241

我使用像这样的简单js函数

AddAntiForgeryToken = function(data) {
    data.__RequestVerificationToken = $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val();
    return data;
};
Run Code Online (Sandbox Code Playgroud)

由于页面上的每个表单都具有相同的令牌值,因此只需将这样的内容放在最顶层的母版页中即可

<%-- used for ajax in AddAntiForgeryToken() --%>
<form id="__AjaxAntiForgeryForm" action="#" method="post"><%= Html.AntiForgeryToken()%></form>  
Run Code Online (Sandbox Code Playgroud)

然后在你的ajax调用do(编辑以匹配你的第二个例子)

$.ajax({
    type: "post",
    dataType: "html",
    url: $(this).attr("rel"),
    data: AddAntiForgeryToken({ id: parseInt($(this).attr("title")) }),
    success: function (response) {
        // ....
    }
});
Run Code Online (Sandbox Code Playgroud)

  • 很好,我喜欢令牌提取的封装. (6认同)
  • 使用[`ajaxSend`](http://api.jquery.com/ajaxSend/)或覆盖[`ajax`](http://stackoverflow.com/questions/4536788/override-)是多么糟糕的想法? jquery-functions)总是使用防伪令牌来增加`data`?也许添加一些检查以确保`url`发往您的服务器. (3认同)
  • @Lorenzo,把你的自定义数据放在调用`AddAntiForgeryToken`中,如下所示:`data:AddAntiForgeryToken({id:parseInt($(this).attr("title"))}),` (2认同)
  • @SouhaiebBesbes,验证令牌对于所有页面上的用户来说应该是相同的(它与设置并保持不变的 cookie 结合使用)。因此,每个页面是否有多个请求并不重要,如果基页面重新加载,结果将是相同的。 (2认同)

Bro*_*onx 29

我喜欢360Airwalk提供的解决方案,但可能会有所改进.

第一个问题是,如果$.post()使用空数据,jQuery不会添加Content-Type标头,在这种情况下,ASP.NET MVC无法接收和检查令牌.所以你必须确保标题始终存在.

另一个改进是支持所有带有内容的 HTTP动词:POST,PUT,DELETE等.虽然你的应用程序中只能使用POST,但最好有一个通用的解决方案并验证你用任何动词收到的所有数据都有防伪令牌.

$(document).ready(function () {
    var securityToken = $('[name=__RequestVerificationToken]').val();
    $(document).ajaxSend(function (event, request, opt) {
        if (opt.hasContent && securityToken) {   // handle all verbs with content
            var tokenParam = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
            opt.data = opt.data ? [opt.data, tokenParam].join("&") : tokenParam;
            // ensure Content-Type header is present!
            if (opt.contentType !== false || event.contentType) {
                request.setRequestHeader( "Content-Type", opt.contentType);
            }
        }
    });
});
Run Code Online (Sandbox Code Playgroud)

  • +1用于保存我不必将函数添加到所有jQuery.Ajax调用 (2认同)
  • +1正如后代的一个注释,`.ajaxSend()的jQuery文档`陈述"从jQuery 1.8开始,.ajaxSend()方法只应附加到文档." http://api.jquery.com/ajaxsend/ (2认同)

vig*_*ity 22

我知道还有很多其他的答案,但这篇文章很简洁,并且强迫你检查所有的HttpPosts,而不仅仅是其中一些:

http://richiban.wordpress.com/2013/02/06/validating-net-mvc-4-anti-forgery-tokens-in-ajax-requests/

它使用HTTP标头而不是尝试修改表单集合.

服务器

//make sure to add this to your global action filters
[AttributeUsage(AttributeTargets.Class)]
public class ValidateAntiForgeryTokenOnAllPosts : AuthorizeAttribute
{
    public override void OnAuthorization( AuthorizationContext filterContext )
    {
        var request = filterContext.HttpContext.Request;

        //  Only validate POSTs
        if (request.HttpMethod == WebRequestMethods.Http.Post)
        {
            //  Ajax POSTs and normal form posts have to be treated differently when it comes
            //  to validating the AntiForgeryToken
            if (request.IsAjaxRequest())
            {
                var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName];

                var cookieValue = antiForgeryCookie != null
                    ? antiForgeryCookie.Value 
                    : null;

                AntiForgery.Validate(cookieValue, request.Headers["__RequestVerificationToken"]);
            }
            else
            {
                new ValidateAntiForgeryTokenAttribute()
                    .OnAuthorization(filterContext);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

客户

var token = $('[name=__RequestVerificationToken]').val();
var headers = {};
headers["__RequestVerificationToken"] = token;

$.ajax({
    type: 'POST',
    url: '/Home/Ajax',
    cache: false,
    headers: headers,
    contentType: 'application/json; charset=utf-8',
    data: { title: "This is my title", contents: "These are my contents" },
    success: function () {
        ...
    },
    error: function () {
        ...
    }
});
Run Code Online (Sandbox Code Playgroud)

  • 您链接的文章中的属性也与[Bronx的响应](http://stackoverflow.com/a/12116344/550073)相结合,是此问题的最终DRY解决方案. (4认同)
  • 很棒的发现.我编辑了你的答案,包括代码片段,所以答案独立,但我希望人们也会阅读本文的其余部分.这似乎是一个非常干净的解决方案. (2认同)

Edw*_*rey 19

不要使用Html.AntiForgeryToken.而是使用Web API中的AntiForgery.GetTokensAntiForgery.Validate,如防止跨站请求伪造(CSRF)攻击中所述.

  • @Odys:Html.AntiForgeryToken没有任何内在错误,但它有缺点:需要一个表单,需要jQuery,并假定未记录的Html.AntiForgeryToken实现细节.不过,在许多情况下都很好.我的声明"不要使用Html.AntiForgeryToken"可能会过于强大.我的意思是它不打算与Web API一起使用,而AntiForgery.GetTokens则更灵活. (4认同)
  • 它当然不需要表格.您只需要按名称解析DOM.使用jquery,我可以通过数据{__RequestVerificationToken:$("input [name = __ RequestVerificationToken]")将其添加到我的数据对象中.val()} (3认同)

Wil*_*l D 19

我觉得这里是一名先进的死灵法师,但这仍然是4年后MVC5的一个问题.

要正确处理ajax请求,需要在ajax调用时将防伪令牌传递给服务器.将它集成到您​​的帖子数据和模型中是混乱和不必要的.将令牌添加为自定义标头是干净且可重复使用的 - 您可以对其进行配置,这样您就不必每次都记得这样做.

有一个例外 - Unobtrusive ajax不需要特殊处理ajax调用.令牌在常规隐藏输入字段中照常传递.与常规POST完全相同.

_Layout.cshtml

在_layout.cshtml中我有这个JavaScript块.它不会将令牌写入DOM,而是使用jQuery从MVC Helper生成的隐藏输入文字中提取它.作为标题名称的Magic字符串在属性类中定义为常量.

<script type="text/javascript">
    $(document).ready(function () {
        var isAbsoluteURI = new RegExp('^(?:[a-z]+:)?//', 'i');
        //http://stackoverflow.com/questions/10687099/how-to-test-if-a-url-string-is-absolute-or-relative

        $.ajaxSetup({
            beforeSend: function (xhr) {
                if (!isAbsoluteURI.test(this.url)) {
                    //only add header to relative URLs
                    xhr.setRequestHeader(
                       '@.ValidateAntiForgeryTokenOnAllPosts.HTTP_HEADER_NAME', 
                       $('@Html.AntiForgeryToken()').val()
                    );
                }
            }
        });
    });
</script>
Run Code Online (Sandbox Code Playgroud)

请注意在beforeSend函数中使用单引号 - 呈现的输入元素使用双引号来破坏JavaScript文字.

客户端JavaScript

执行此操作时,将调用上面的beforeSend函数,并自动将AntiForgeryToken添加到请求标头中.

$.ajax({
  type: "POST",
  url: "CSRFProtectedMethod",
  dataType: "json",
  contentType: "application/json; charset=utf-8",
  success: function (data) {
    //victory
  }
});
Run Code Online (Sandbox Code Playgroud)

服务器库

处理非标准令牌需要自定义属性.这是基于@ viggity的解决方案,但正确处理不引人注目的ajax.此代码可以隐藏在您的公共库中

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ValidateAntiForgeryTokenOnAllPosts : AuthorizeAttribute
{
    public const string HTTP_HEADER_NAME = "x-RequestVerificationToken";

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        var request = filterContext.HttpContext.Request;

        //  Only validate POSTs
        if (request.HttpMethod == WebRequestMethods.Http.Post)
        {

            var headerTokenValue = request.Headers[HTTP_HEADER_NAME];

            // Ajax POSTs using jquery have a header set that defines the token.
            // However using unobtrusive ajax the token is still submitted normally in the form.
            // if the header is present then use it, else fall back to processing the form like normal
            if (headerTokenValue != null)
            {
                var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName];

                var cookieValue = antiForgeryCookie != null
                    ? antiForgeryCookie.Value
                    : null;

                AntiForgery.Validate(cookieValue, headerTokenValue);
            }
            else
            {
                new ValidateAntiForgeryTokenAttribute()
                    .OnAuthorization(filterContext);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

服务器/控制器

现在,您只需将该属性应用于您的Action.更好的是,您可以将属性应用于控制器,并验证所有请求.

[HttpPost]
[ValidateAntiForgeryTokenOnAllPosts]
public virtual ActionResult CSRFProtectedMethod()
{
  return Json(true, JsonRequestBehavior.DenyGet);
}
Run Code Online (Sandbox Code Playgroud)


360*_*alk 16

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

首先,我决定挂钩我的jquery ajax调用,所以我不要经常重复自己.此javascript代码段确保所有ajax(post)调用都将我的请求验证令牌添加到请求中.注意:名称__RequestVerificationToken由.Net框架使用,因此我可以使用标准的Anti-CSRF功能,如下所示.

$(document).ready(function () {
    var 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);
            }
        }
    });
});
Run Code Online (Sandbox Code Playgroud)

在您的视图中,您需要令牌可用于上述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
Run Code Online (Sandbox Code Playgroud)

在您的控制器中,只需使用标准的ASP.Net MVC Anti-CSRF机制.我这样做了(虽然我实际上使用了Salt).

[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public JsonResult SomeMethod(string param)
{
    // do something
    return Json(true);
}
Run Code Online (Sandbox Code Playgroud)

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


jba*_*all 14

我认为您所要做的就是确保POST请求中包含"__RequestVerificationToken"输入.另一半信息(即用户cookie中的令牌)已经通过AJAX POST请求自动发送.

例如,

$("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: { 
            "__RequestVerificationToken":
            $("input[name=__RequestVerificationToken]").val() 
        },
        success: function (response) {
            // ....
        }
    });
});
Run Code Online (Sandbox Code Playgroud)


Leo*_*spo 6

你也可以这样做:

$("a.markAsDone").click(function (event) {
    event.preventDefault();

    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: $('<form>@Html.AntiForgeryToken()</form>').serialize(),
        success: function (response) {
        // ....
        }
    });
});
Run Code Online (Sandbox Code Playgroud)

这是使用Razor,但如果你使用WebForms语法,你也可以使用<%= %>标签