jQuery Ajax 调用和 Html.AntiForgeryToken()
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4074199/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
jQuery Ajax calls and the Html.AntiForgeryToken()
提问by Lorenzo
I have implemented in my app the mitigation to CSRF attacksfollowing the informations that I have read on some blog post around the internet. In particular these post have been the driver of my implementation
根据我在互联网上的一些博客文章中阅读的信息,我在我的应用程序中实现了对CSRF 攻击的缓解。特别是这些帖子一直是我实现的驱动力
- Best Practices for ASP.NET MVCfrom the ASP.NET and Web Tools Developer Content Team
- Anatomy of a Cross-site Request Forgery Attackfrom Phil Haack blog
- AntiForgeryToken in the ASP.NET MVC Framework - Html.AntiForgeryToken and ValidateAntiForgeryToken Attributefrom David Hayden blog
- 来自 ASP.NET 和 Web 工具开发人员内容团队的ASP.NET MVC 最佳实践
- Phil Haack 博客对跨站点请求伪造攻击的剖析
- ASP.NET MVC 框架中的 AntiForgeryToken -来自 David Hayden 博客的Html.AntiForgeryToken 和 ValidateAntiForgeryToken 属性
Basically those articles and recommendations says that to prevent the CSRF attack anybody should implement the following code:
基本上这些文章和建议说,为了防止 CSRF 攻击,任何人都应该实现以下代码:
1) Add the [ValidateAntiForgeryToken]on every action that accept the POST Http verb
1)[ValidateAntiForgeryToken]在接受 POST Http 动词的每个动作上添加
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SomeAction( SomeModel model ) {
}
2) Add the <%= Html.AntiForgeryToken() %>helper inside forms that submits data to the server
2)在<%= Html.AntiForgeryToken() %>向服务器提交数据的表单中添加助手
<div style="text-align:right; padding: 8px;">
<%= Html.AntiForgeryToken() %>
<input type="submit" id="btnSave" value="Save" />
</div>
Anyway in some parts of my app I am doing Ajax POSTs with jQuery to the server without having any form at all. This happens for example where I am letting the user to click on an image to do a specific action.
无论如何,在我的应用程序的某些部分,我正在使用 jQuery 向服务器执行 Ajax POST,根本没有任何形式。例如,我让用户单击图像以执行特定操作时会发生这种情况。
Suppose I have a table with a list of activities. I have an image on a column of the table that says "Mark activity as completed" and when the user click on that activity I am doing the Ajax POST as in the following sample:
假设我有一个包含活动列表的表格。我在表格的一列上有一张图片,上面写着“将活动标记为已完成”,当用户单击该活动时,我正在执行 Ajax POST,如下例所示:
$("a.markAsDone").click(function (event) {
event.preventDefault();
$.ajax({
type: "post",
dataType: "html",
url: $(this).attr("rel"),
data: {},
success: function (response) {
// ....
}
});
});
How can I use the <%= Html.AntiForgeryToken() %>in these cases? Should I include the helper call inside the data parameter of the Ajax call?
<%= Html.AntiForgeryToken() %>在这些情况下我该如何使用?我应该在 Ajax 调用的数据参数中包含辅助调用吗?
Sorry for the long post and thanks very much for helping out
很抱歉这篇很长的帖子,非常感谢您的帮助
EDIT:
编辑:
As per jayrdubanswer I have used in the following way
根据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) {
// ....
}
});
});
回答by JeremyWeir
I use a simple js function like this
我使用这样一个简单的 js 函数
AddAntiForgeryToken = function(data) {
data.__RequestVerificationToken = $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val();
return data;
};
Since every form on a page will have the same value for the token, just put something like this in your top-most master page
由于页面上的每个表单都具有相同的令牌值,因此只需在最顶层的母版页中放置类似的内容
<%-- used for ajax in AddAntiForgeryToken() --%>
<form id="__AjaxAntiForgeryForm" action="#" method="post"><%= Html.AntiForgeryToken()%></form>
Then in your ajax call do (edited to match your second example)
然后在您的 ajax 调用中执行(编辑以匹配您的第二个示例)
$.ajax({
type: "post",
dataType: "html",
url: $(this).attr("rel"),
data: AddAntiForgeryToken({ id: parseInt($(this).attr("title")) }),
success: function (response) {
// ....
}
});
回答by Bronx
I like the solution provided by 360Airwalk, but it may be improved a bit.
我喜欢360Airwalk提供的解决方案,但可能会有所改进。
The first problem is that if you make $.post()with empty data, jQuery doesn't add a Content-Typeheader, and in this case ASP.NET MVC fails to receive and check the token. So you have to ensure the header is always there.
第一个问题是,如果您$.post()使用空数据进行创建,jQuery 不会添加Content-Type标头,并且在这种情况下 ASP.NET MVC 无法接收和检查令牌。因此,您必须确保标题始终存在。
Another improvement is support of all HTTP verbs with content: POST, PUT, DELETE etc. Though you may use only POSTs in your application, it's better to have a generic solution and verify that all data you receive with any verb has an anti-forgery token.
另一个改进是支持所有带有内容的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);
}
}
});
});
回答by viggity
I know there are a lot of other answers, but this article is nice and concise and forces you to check all of your HttpPosts, not just some of them:
我知道还有很多其他的答案,但这篇文章简洁明了,并迫使您检查所有 HttpPost,而不仅仅是其中一些:
http://richiban.wordpress.com/2013/02/06/validating-net-mvc-4-anti-forgery-tokens-in-ajax-requests/
http://richiban.wordpress.com/2013/02/06/validating-net-mvc-4-anti-forgery-tokens-in-ajax-requests/
It uses HTTP headers instead of trying to modify the form collection.
它使用 HTTP 标头而不是尝试修改表单集合。
Server
服务器
//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);
}
}
}
}
Client
客户
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 () {
...
}
});
回答by Will D
I feel like an advanced necromancer here, but this is still an issue 4 years later in MVC5.
我在这里感觉自己是个高级死灵法师,但这在 4 年后的 MVC5 中仍然是一个问题。
To handle ajax requests properly the anti-forgery token needs to be passed to the server on ajax calls. Integrating it into your post data and models is messy and unnecessary. Adding the token as a custom header is clean and reusable - and you can configure it so you don't have to remember to do it every time.
为了正确处理 ajax 请求,需要在 ajax 调用时将防伪令牌传递给服务器。将其集成到您的帖子数据和模型中是混乱且不必要的。将令牌添加为自定义标头是干净且可重用的 - 您可以对其进行配置,这样您就不必每次都记住这样做。
There is an exception - Unobtrusive ajax does not need special treatment for ajax calls. The token is passed as usual in the regular hidden input field. Exactly the same as a regular POST.
有一个例外 - Unobtrusive ajax 不需要对 ajax 调用进行特殊处理。令牌像往常一样在常规隐藏输入字段中传递。与常规 POST 完全相同。
_Layout.cshtml
_Layout.cshtml
In _layout.cshtml I have this JavaScript block. It doesn't write the token into the DOM, rather it uses jQuery to extract it from the hidden input literal that the MVC Helper generates. The Magic string that is the header name is defined as a constant in the attribute class.
在 _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>
Note the use of single quotes in the beforeSend function - the input element that is rendered uses double quotes that would break the JavaScript literal.
请注意 beforeSend 函数中单引号的使用 - 呈现的输入元素使用双引号会破坏 JavaScript 文字。
Client JavaScript
客户端 JavaScript
When this executes the beforeSend function above is called and the AntiForgeryToken is automatically added to the request headers.
执行此操作时,会调用上面的 beforeSend 函数,并且 AntiForgeryToken 会自动添加到请求标头中。
$.ajax({
type: "POST",
url: "CSRFProtectedMethod",
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function (data) {
//victory
}
});
Server Library
服务器库
A custom attribute is required to process the non standard token. This builds on @viggity's solution, but handles unobtrusive ajax correctly. This code can be tucked away in your common library
处理非标准令牌需要自定义属性。这建立在@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);
}
}
}
}
Server / Controller
服务器/控制器
Now you just apply the attribute to your Action. Even better you can apply the attribute to your controller and all requests will be validated.
现在您只需将该属性应用于您的操作。更好的是,您可以将该属性应用于您的控制器,并且所有请求都将得到验证。
[HttpPost]
[ValidateAntiForgeryTokenOnAllPosts]
public virtual ActionResult CSRFProtectedMethod()
{
return Json(true, JsonRequestBehavior.DenyGet);
}
回答by Edward Brey
Don't use Html.AntiForgeryToken. Instead, use AntiForgery.GetTokensand AntiForgery.Validatefrom Web API as described in Preventing Cross-Site Request Forgery (CSRF) Attacks in ASP.NET MVC Application.
不要使用Html.AntiForgeryToken。相反,请使用Web API 中的AntiForgery.GetTokens和AntiForgery.Validate,如防止 ASP.NET MVC 应用程序中的跨站点请求伪造 (CSRF) 攻击中所述。
回答by 360Airwalk
I was just implementing this actual problem in my current project. i did it for all ajax-POSTs that needed an authenticated user.
我只是在我当前的项目中实施这个实际问题。我为所有需要经过身份验证的用户的 ajax-POST 做了它。
First off i decided to hook my jquery ajax calls so i do not to repeat myself too often. this javascript snippet ensures all ajax (post) calls will add my request validation token to the request. Note: the name __RequestVerificationToken is used by the .Net framework so i can utilize the standard Anti-CSRF features as shown below.
首先,我决定钩住我的 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);
}
}
});
});
In your Views where you need the token to be available to the above javascript just use the common HTML-Helper. You can basically add this code whereever you want. I placed it within a if(Request.IsAuthenticated) statement:
在您需要令牌可用于上述 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
In your controller simply use the standard ASP.Net MVC Anti-CSRF mechanism. I did it like this (though i actually used Salt).
在您的控制器中,只需使用标准的 ASP.Net MVC Anti-CSRF 机制。我是这样做的(虽然我实际上使用了盐)。
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public JsonResult SomeMethod(string param)
{
// do something
return Json(true);
}
With Firebug or a similar tool you can easily see how your POST requests now have a __RequestVerificationToken parameter appended.
使用 Firebug 或类似工具,您可以轻松查看您的 POST 请求现在如何附加了 __RequestVerificationToken 参数。
回答by jball
I think all you have to do is ensure that the "__RequestVerificationToken" input is included in the POST request. The other half of the information (i.e. the token in the user's cookie) is already sent automatically with an AJAX POST request.
我认为您所要做的就是确保“__RequestVerificationToken”输入包含在 POST 请求中。另一半信息(即用户 cookie 中的令牌)已经通过 AJAX POST 请求自动发送。
E.g.,
例如,
$("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) {
// ....
}
});
});
回答by Leonardo Garcia Crespo
You can do this also:
你也可以这样做:
$("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) {
// ....
}
});
});
This is using Razor, but if you're using WebFormssyntax you can just as well use <%= %>tags
这是 using Razor,但如果您使用WebForms语法,您也可以使用<%= %>标签
回答by masterlopau
found this very clever idea from https://gist.github.com/scottrippey/3428114for every $.ajax calls it modifies the request and add the token.
从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();
}
}
});
回答by Ralph Bacon
Further to my comment against @JBall's answer that helped me along the way, this is the final answer that works for me. I'm using MVC and Razor and I'm submitting a form using jQuery AJAX so I can update a partial view with some new results and I didn't want to do a complete postback (and page flicker).
除了我对@JBall 的回答对我有所帮助的评论之外,这是对我有用的最终答案。我正在使用 MVC 和 Razor,我正在使用 jQuery AJAX 提交表单,因此我可以使用一些新结果更新部分视图,并且我不想进行完整的回发(和页面闪烁)。
Add the @Html.AntiForgeryToken()inside the form as usual.
@Html.AntiForgeryToken()像往常一样添加表单内部。
My AJAX submission button code (i.e. an onclick event) is:
我的 AJAX 提交按钮代码(即 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');
}
});
I've left the "success" action in as it shows how the partial view is being updated that contains an MvcJqGrid and how it's being refreshed (very powerful jqGrid grid and this is a brilliant MVC wrapper for it).
我留下了“成功”操作,因为它显示了如何更新包含 MvcJqGrid 的局部视图以及它是如何刷新的(非常强大的 jqGrid 网格,这是一个出色的 MVC 包装器)。
My controller method looks like this:
我的控制器方法如下所示:
//Ajax SUBMIT method
[ValidateAntiForgeryToken]
public ActionResult SearchAjax(EstateOutlet_D model)
{
return View("_Grid", model);
}
I have to admit to not being a fan of POSTing an entire form's data as a Model but if you need to do it then this is one way that works. MVC just makes the data binding too easy so rather than subitting 16 individual values (or a weakly-typed FormCollection) this is OK, I guess. If you know better please let me know as I want to produce robust MVC C# code.
我不得不承认我不喜欢将整个表单的数据作为模型发布,但如果您需要这样做,那么这是一种有效的方法。MVC 只是让数据绑定太容易了,所以我猜,而不是提交 16 个单独的值(或弱类型的 FormCollection)这是可以的。如果您更了解,请告诉我,因为我想生成健壮的 MVC C# 代码。

