可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have implemented in my app the mitigation to CSRF attacks following the informations that I have read on some blog post around the internet. In particular these post have been the driver of my implementation
- Best Practices for ASP.NET MVC from the ASP.NET and Web Tools Developer Content Team
- Anatomy of a Cross-site Request Forgery Attack from Phil Haack blog
- AntiForgeryToken in the ASP.NET MVC Framework - Html.AntiForgeryToken and ValidateAntiForgeryToken Attribute from David Hayden blog
Basically those articles and recommendations says that to prevent the CSRF attack anybody should implement the following code:
1) Add the [ValidateAntiForgeryToken]
on every action that accept the POST Http verb
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SomeAction( SomeModel model ) {
}
2) Add the <%= Html.AntiForgeryToken() %>
helper inside forms that submits data to the server
<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.
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:
$(\"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?
Sorry for the long post and thanks very much for helping out
EDIT:
As per jayrdub answer I have used in the following way
$(\"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) {
// ....
}
});
});
回答1:
I use a simple js function like this
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({
type: \"post\",
dataType: \"html\",
url: $(this).attr(\"rel\"),
data: AddAntiForgeryToken({ id: parseInt($(this).attr(\"title\")) }),
success: function (response) {
// ....
}
});
回答2:
I like the solution provided by 360Airwalk, but it may be improved a bit.
The first problem is that if you make $.post()
with empty data, jQuery doesn\'t add a Content-Type
header, and in this case ASP.NET MVC fails to receive and check the token. So you have to ensure the header is always there.
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.
$(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);
}
}
});
});
回答3:
Don\'t use Html.AntiForgeryToken. Instead, use AntiForgery.GetTokens and AntiForgery.Validate from Web API as described in Preventing Cross-Site Request Forgery (CSRF) Attacks.
回答4:
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:
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.
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 () {
...
}
});
回答5:
I feel like an advanced necromancer here, but this is still an issue 4 years later in 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.
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.
_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.
<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.
Client JavaScript
When this executes the beforeSend function above is called and the AntiForgeryToken is automatically added to the request headers.
$.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
[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);
}
回答6:
I was just implementing this actual problem in my current project. i did it for all ajax-POSTs that needed an authenticated user.
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.
$(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:
@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).
[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.
回答7:
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.
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) {
// ....
}
});
});
回答8:
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 WebForms
syntax you can just as well use <%= %>
tags
回答9:
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).
Add the @Html.AntiForgeryToken()
inside the form as usual.
My AJAX submission button code (i.e. an onclick event) is:
//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).
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.
回答10:
1.Define Function to get Token from server
@function
{
public string TokenHeaderValue()
{
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
return cookieToken + \":\" + formToken;
}
}
2.Get token and set header before send to server
var token = \'@TokenHeaderValue()\';
$http({
method: \"POST\",
url: \'./MainBackend/MessageDelete\',
data: dataSend,
headers: {
\'RequestVerificationToken\': token
}
}).success(function (data) {
alert(data)
});
3. Onserver Validation on HttpRequestBase on method you handle 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);
回答11:
found this very clever idea from https://gist.github.com/scottrippey/3428114 for every $.ajax calls it modifies the request and add the token.
// 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();
}
}
});
回答12:
I aware it\'s been some time since this question was posted, but I found really useful resource, which discusses usage of AntiForgeryToken and makes it less troublesome to use. It also provides jquery plugin for easily including antiforgery token in AJAX calls:
Anti-Forgery Request Recipes For ASP.NET MVC And AJAX
I\'m not contributing much, but maybe someone will find it useful.
回答13:
Slight improvement to 360Airwalk solution. This imbeds the Anti Forgery Token within the javascript function, so @Html.AntiForgeryToken() no longer needs to be included on every view.
$(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);
}
}
});
});
回答14:
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 \"\";
}
}
回答15:
I\'m using a ajax post to run a delete method (happens to be from a visjs timeline but that\'s not relelvant). This is what I sis:
This is my 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>
All I added here was @Html.AntiForgeryToken()
to make the token appear in the page
Then in my ajax post I used:
$.ajax(
{
type: \'POST\',
url: \'/ScheduleWorks/Delete/\' + item.id,
data: {
\'__RequestVerificationToken\':
$(\"input[name=\'__RequestVerificationToken\']\").val()
}
}
);
Which adds the token value, scraped off the page, to the fields posted
Before this I tried putting the value in the headers but I got the same error
Feel free to post improvements. This certainly seems to be a simple approach that I can understand
回答16:
first use @Html.AntiForgeryToken() in html
$.ajax({
url: \"@Url.Action(\"SomeMethod\", \"SomeController\")\",
type: \'POST\',
data: JSON.stringify(jsonObject),
contentType: \'application/json; charset=utf-8\',
dataType: \'json\',
async: false,
beforeSend: function (request) {
request.setRequestHeader(\"RequestVerificationToken\", $(\"[name=\'__RequestVerificationToken\']\").val());
},
success: function (msg) {
alert(msg);
}
回答17:
Here is the easiest way I\'ve seen. Note: Make sure you have \"@Html.AntiForgeryToken()\" in your View
$(\"a.markAsDone\").click(function (event) {
event.preventDefault();
var sToken = document.getElementsByName(\"__RequestVerificationToken\")[0].value;
$.ajax({
url: $(this).attr(\"rel\"),
type: \"POST\",
contentType: \"application/x-www-form-urlencoded\",
data: { \'__RequestVerificationToken\': sToken, \'id\': parseInt($(this).attr(\"title\")) }
})
.done(function (data) {
//Process MVC Data here
})
.fail(function (jqXHR, textStatus, errorThrown) {
//Process Failure here
});
});
回答18:
Okay lots of posts here, none of them helped me, days and days of google, and still no further I got to the point the wr-writing the whole app from scratch, and then I noticed this little nugget in my Web.confg
<httpCookies requireSSL=\"false\" domain=\"*.localLookup.net\"/>
Now I don\'t know why I added it however I have since noticed, its ignored in debug mode and not in a production mode (IE Installed to IIS Somewhere)
For me the solution was one of 2 options, since I don\'t remember why I added it I cant be sure other things don\'t depend on it, and second the domain name must be all lower case and a TLD not like ive done in *.localLookup.net
Maybe it helps maybe it don\'t. I hope it does help someone
回答19:
AntiforgeryToken is still a pain, none of the examples above worked word for word for me. Too many for\'s there. So I combined them all. Need a @Html.AntiforgeryToken in a form hanging around iirc
Solved as so:
function Forgizzle(eggs) {
eggs.__RequestVerificationToken = $($(\"input[name=__RequestVerificationToken]\")[0]).val();
return eggs;
}
$.ajax({
url: url,
type: \'post\',
data: Forgizzle({ id: id, sweets: milkway }),
});
When in doubt, add more $ signs