jQuery的Ajax调用和Html.AntiForgeryToken()

我已经在我的应用程序中实施了CSRF攻击的缓解措施,以下是我在互联网上的一些博客文章中阅读的信息。 特别是这些职位一直是我执行的驱动程序

  • ASP.NET和Web工具开发人员内容小组的ASP.NET MVC最佳实践
  • Phil Haack博客对跨站请求伪造攻击的剖析
  • ASP.NET MVC框架中的AntiForgeryToken -来自David Hayden博客的Html.AntiForgeryToken和ValidateAntiForgeryToken属性

基本上那些文章和build议说为了防止CSRF攻击,任何人都应该执行下面的代码:

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

 [HttpPost] [ValidateAntiForgeryToken] public ActionResult SomeAction( SomeModel model ) { } 

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

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

无论如何,在我的应用程序的一些部分,我正在使用jQuery的Ajax POST到服务器,根本没有任何forms。 例如,我让用户点击图片来执行特定操作。

假设我有一个包含活动列表的表格。 我在表格上的一列中有一个图像,标记为“已完成活动”,当用户单击该活动时,我正在执行Ajax POST,如以下示例所示:

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

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

对不起,很长的职位,非常感谢帮助

编辑

按照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) { // .... } }); }); 

我使用这样一个简单的js函数

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

由于页面上的每个表单都将具有相同的标记值,因此请在最顶层的母版页中放置这样的内容

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

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

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

我喜欢360Airwalk提供的解决scheme,但可能会有所改进。

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

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

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

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

我知道还有很多其他的答案,但是这篇文章很好,很简洁,强制你检查所有的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); } } } } 

客户

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

我只是在当前的项目中实现这个实际的问题。 我做了所有需要authentication用户的ajax-POST。

首先,我决定挂钩我的jQuery Ajax电话,所以我不要经常重复自己。 这个JavaScript片段确保所有Ajax(后)调用将添加我的请求validation令牌的请求。 注意:.Net框架使用名称__RequestVerificationToken,所以我可以使用如下所示的标准Anti-CSRFfunction。

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

在你的视图中,你需要令牌可用于上面的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 Anti-CSRF机制。 我这样做(虽然我实际上使用盐)。

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

使用Firebug或类似的工具,你可以很容易地看到你的POST请求现在有一个__RequestVerificationToken参数。

我觉得自己像一个先进的死灵巫师,但这在4年后的MVC5中仍然是一个问题。

要正确处理ajax请求,需要将防伪标记传递给服务器上的ajax调用。 将它集成到你的发布数据和模型是混乱和不必要的。 将标记添加为自定义标题是干净且可重复使用的 – 您可以对其进行configuration,因此您不必每次都记住这一点。

有一个例外 – 不显眼的ajax并不需要特殊的处理ajax调用。 令牌像往常一样在常规的隐藏input字段中传递。 和普通的POST完全一样。

_Layout.cshtml

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

 <script type="text/javascript"> $(document).ready(function () { var isAbsoluteURI = new RegExp('^(?:[az]+:)?//', '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> 

请注意在beforeSend函数中使用单引号 – 呈现的input元素使用双引号,这将会破坏JavaScript文字。

客户端JavaScript

当执行上面的beforeSend函数时,AntiForgeryToken会自动添加到请求标头中。

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

服务器库

需要自定义属性来处理非标准令牌。 这build立在@ viggity的解决scheme,但正确地处理不显眼的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); } } } } 

服务器/控制器

现在,您只需将该属性应用于您的操作。 更好的是,你可以将这个属性应用到你的控制器,所有的请求都将被validation。

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

我认为你所要做的就是确保“__RequestVerificationToken”input包含在POST请求中。 另一半的信息(即用户的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) { // .... } }); }); 

除了我对JBall的回答的评论之外,这是对我的最终答案。 我正在使用MVC和Razor,而我正在使用jQuery AJAX提交表单,所以我可以用一些新的结果更新一个局部视图,而我不想做一个完整的回发(和页面闪烁)。

像往常一样在表单中添加@Html.AntiForgeryToken()

我的AJAX提交button代码(即一个onclick事件)是:

 //User clicks the SUBMIT button $("#btnSubmit").click(function (event) { //prevent this button submitting the form as we will do that via AJAX event.preventDefault(); //Validate the form first if (!$('#searchForm').validate().form()) { alert("Please correct the errors"); return false; } //Get the entire form's data - including the antiforgerytoken var allFormData = $("#searchForm").serialize(); // The actual POST can now take place with a validated form $.ajax({ type: "POST", async: false, url: "/Home/SearchAjax", data: allFormData, dataType: "html", success: function (data) { $('#gridView').html(data); $('#TestGrid').jqGrid('setGridParam', { url: '@Url.Action("GetDetails", "Home", Model)', datatype: "json", page: 1 }).trigger('reloadGrid'); } }); 

我已经离开了“成功”的行动,因为它显示了如何更新包含MvcJqGrid的部分视图以及如何刷新(非常强大的jqGrid网格,这是一个辉煌的MVC包装)。

我的控制器方法如下所示:

  //Ajax SUBMIT method [ValidateAntiForgeryToken] public ActionResult SearchAjax(EstateOutlet_D model) { return View("_Grid", model); } 

我必须承认,不要把整个表单的数据作为一个模型来发布,但是如果你需要这样做的话,这是一个有效的方法。 MVC只是使数据绑定太容易了,而不是subits 16个人的价值观(或弱types的FormCollection)这是好的,我猜。 如果你知道更好,请让我知道,因为我想生产健全的MVC C#代码。

你也可以这样做:

 $("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) { // .... } }); }); 

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

1.定义从服务器获取令牌的function

 @function { public string TokenHeaderValue() { string cookieToken, formToken; AntiForgery.GetTokens(null, out cookieToken, out formToken); return cookieToken + ":" + formToken; } } 

获取令牌并设置标题,然后发送到服务器

 var token = '@TokenHeaderValue()'; $http({ method: "POST", url: './MainBackend/MessageDelete', data: dataSend, headers: { 'RequestVerificationToken': token } }).success(function (data) { alert(data) }); 

3. OnservervalidationHttpRequestBase上你处理Post / get的方法

  string cookieToken = ""; string formToken = ""; string[] tokens = Request.Headers["RequestVerificationToken"].Split(':'); if (tokens.Length == 2) { cookieToken = tokens[0].Trim(); formToken = tokens[1].Trim(); } AntiForgery.Validate(cookieToken, formToken); 

发现这个从https://gist.github.com/scottrippey/3428114这个非常聪明的想法,每$ .ajax调用它修改请求并添加令牌。

 // Setup CSRF safety for AJAX: $.ajaxPrefilter(function(options, originalOptions, jqXHR) { if (options.type.toUpperCase() === "POST") { // We need to add the verificationToken to all POSTs var token = $("input[name^=__RequestVerificationToken]").first(); if (!token.length) return; var tokenName = token.attr("name"); // If the data is JSON, then we need to put the token in the QueryString: if (options.contentType.indexOf('application/json') === 0) { // Add the token to the URL, because we can't add it to the JSON data: options.url += ((options.url.indexOf("?") === -1) ? "?" : "&") + token.serialize(); } else if (typeof options.data === 'string' && options.data.indexOf(tokenName) === -1) { // Append to the data string: options.data += (options.data ? "&" : "") + token.serialize(); } } }); 

我意识到这个问题已经发布了一段时间了,但是我发现了非常有用的资源,它讨论了AntiForgeryToken的使用,并且使用起来不那么麻烦。 它还提供了jQuery插件,用于在AJAX调用中轻松包含防伪令牌:

ASP.NET MVC和AJAX的防伪请求配方

我没有太多的贡献,但也许有人会觉得它有用。

略微改进360Airwalk解决scheme。 在javascript函数中embedded了Anti Forgery Token,所以@ Html.AntiForgeryToken()不再需要包含在每个视图中。

 $(document).ready(function () { var securityToken = $('@Html.AntiForgeryToken()').attr('value'); $('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); } } }); }); 
 function DeletePersonel(id) { var data = new FormData(); data.append("__RequestVerificationToken", "@HtmlHelper.GetAntiForgeryToken()"); $.ajax({ type: 'POST', url: '/Personel/Delete/' + id, data: data, cache: false, processData: false, contentType: false, success: function (result) { } }); } public static class HtmlHelper { public static string GetAntiForgeryToken() { System.Text.RegularExpressions.Match value = System.Text.RegularExpressions.Regex.Match(System.Web.Helpers.AntiForgery.GetHtml().ToString(), "(?:value=\")(.*)(?:\")"); if (value.Success) { return value.Groups[1].Value; } return ""; } } 

我正在使用ajax后运行一个删除方法(恰好是从visjs时间表,但不是相关的)。 这就是我的意思:

这是我的Index.cshtml

 @Scripts.Render("~/bundles/schedule") @Styles.Render("~/bundles/visjs") @Html.AntiForgeryToken() <!-- div to attach schedule to --> <div id='schedule'></div> <!-- div to attach popups to --> <div id='dialog-popup'></div> 

我在这里添加的是@Html.AntiForgeryToken()使令牌出现在页面中

然后在我使用的ajax文章中:

 $.ajax( { type: 'POST', url: '/ScheduleWorks/Delete/' + item.id, data: { '__RequestVerificationToken': $("input[name='__RequestVerificationToken']").val() } } ); 

它将令牌值添加到已发布的字段中

在此之前,我试图把值放在标题,但我得到了同样的错误

随意发表改进。 这当然似乎是一个我能理解的简单方法

AntiforgeryToken仍然是一个痛苦,上面的例子没有一个一个字为我工作。 那里太多了。 所以我把他们全部结合起来 需要一个挂在iirc附近的@ Html.AntiforgeryToken

解决如此:

 function Forgizzle(eggs) { eggs.__RequestVerificationToken = $($("input[name=__RequestVerificationToken]")[0]).val(); return eggs; } $.ajax({ url: url, type: 'post', data: Forgizzle({ id: id, sweets: milkway }), }); 

如有疑问,请添加更多$符号