在AJAX后ASP.NET MVC中包含AntiForgeryToken

我在使用Ajax的AntiForgeryToken时遇到了问题。我使用的是ASP.NET MVC 3。我在jQuery Ajax调用和HTML.AntiForgeryToken()中尝试了该解决方案。使用该解决方案,现在正在传递令牌:

var data = { ... } // with token, key is '__RequestVerificationToken'


$.ajax({
type: "POST",
data: data,
datatype: "json",
traditional: true,
contentType: "application/json; charset=utf-8",
url: myURL,
success: function (response) {
...
},
error: function (response) {
...
}
});

当我删除[ValidateAntiForgeryToken]属性,只是为了查看数据(带有令牌)是否作为参数传递给控制器时,我可以看到它们正在被传递。但由于某种原因,当我将属性放回时,仍然会弹出“A required anti-forgery token was not supplied or was invalid. ”消息。

有什么想法吗?

编辑

AntiForgeryToken是在表单中生成的,但我没有使用提交操作来提交它。相反,我只是使用jQuery获取令牌的值,然后尝试使用Ajax发布它。

下面是包含令牌的表单,它位于顶部母版页中:

<form id="__AjaxAntiForgeryForm" action="#" method="post">
@Html.AntiForgeryToken()
</form>
215386 次浏览

您错误地将contentType指定为application/json

这里有一个如何工作的例子。

控制器:

public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}


[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(string someValue)
{
return Json(new { someValue = someValue });
}
}

查看:

@using (Html.BeginForm(null, null, FormMethod.Post, new { id = "__AjaxAntiForgeryForm" }))
{
@Html.AntiForgeryToken()
}


<div id="myDiv" data-url="@Url.Action("Index", "Home")">
Click me to send an AJAX request to a controller action
decorated with the [ValidateAntiForgeryToken] attribute
</div>


<script type="text/javascript">
$('#myDiv').submit(function () {
var form = $('#__AjaxAntiForgeryForm');
var token = $('input[name="__RequestVerificationToken"]', form).val();
$.ajax({
url: $(this).data('url'),
type: 'POST',
data: {
__RequestVerificationToken: token,
someValue: 'some value'
},
success: function (result) {
alert(result.someValue);
}
});
return false;
});
</script>

我所做的另一个(较少使用JavaScript)方法,大致如下:

首先,一个HTML助手

public static MvcHtmlString AntiForgeryTokenForAjaxPost(this HtmlHelper helper)
{
var antiForgeryInputTag = helper.AntiForgeryToken().ToString();
// Above gets the following: <input name="__RequestVerificationToken" type="hidden" value="PnQE7R0MIBBAzC7SqtVvwrJpGbRvPgzWHo5dSyoSaZoabRjf9pCyzjujYBU_qKDJmwIOiPRDwBV1TNVdXFVgzAvN9_l2yt9-nf4Owif0qIDz7WRAmydVPIm6_pmJAI--wvvFQO7g0VvoFArFtAR2v6Ch1wmXCZ89v0-lNOGZLZc1" />
var removedStart = antiForgeryInputTag.Replace(@"<input name=""__RequestVerificationToken"" type=""hidden"" value=""", "");
var tokenValue = removedStart.Replace(@""" />", "");
if (antiForgeryInputTag == removedStart || removedStart == tokenValue)
throw new InvalidOperationException("Oops! The Html.AntiForgeryToken() method seems to return something I did not expect.");
return new MvcHtmlString(string.Format(@"{0}:""{1}""", "__RequestVerificationToken", tokenValue));
}

将返回一个字符串

__RequestVerificationToken:"P5g2D8vRyE3aBn7qQKfVVVAsQc853s-naENvpUAPZLipuw0pa_ffBf9cINzFgIRPwsf7Ykjt46ttJy5ox5r3mzpqvmgNYdnKc1125jphQV0NnM5nGFtcXXqoY3RpusTH_WcHPzH4S4l1PmB8Uu7ubZBftqFdxCLC5n-xT0fHcAY1"

所以我们可以这样使用它。

$(function () {
$("#submit-list").click(function () {
$.ajax({
url: '@Url.Action("SortDataSourceLibraries")',
data: { items: $(".sortable").sortable('toArray'), @Html.AntiForgeryTokenForAjaxPost() },
type: 'post',
traditional: true
});
});
});

它似乎起作用了!

我知道这是个老问题了。但无论如何,我会加上我的答案,可能会帮助像我这样的人。

如果您不想处理控制器的POST操作的结果,例如调用Accounts控制器的LoggOff方法,您可以按照以下@Darindimitrov的答案版本进行操作:

@using (Html.BeginForm("LoggOff", "Accounts", FormMethod.Post, new { id = "__AjaxAntiForgeryForm" }))
{
@Html.AntiForgeryToken()
}


<!-- this could be a button -->
<a href="#" id="ajaxSubmit">Submit</a>


<script type="text/javascript">
$('#ajaxSubmit').click(function () {


$('#__AjaxAntiForgeryForm').submit();


return false;
});
</script>

就是这么简单!当您在HTML代码中使用@Html.AntiForgeryToken()时,这意味着服务器已对此页面进行签名,并且从此特定页面发送到服务器的每个请求都有一个防止黑客发送虚假请求的签名。因此,要让服务器对此页面进行身份验证,您应该执行两个步骤:

1.发送名为__RequestVerificationToken的参数,并使用以下代码获取其值:

<script type="text/javascript">
function gettoken() {
var token = '@Html.AntiForgeryToken()';
token = $(token).val();
return token;
}
</script>

例如,以Ajax调用为例

$.ajax({
type: "POST",
url: "/Account/Login",
data: {
__RequestVerificationToken: gettoken(),
uname: uname,
pass: pass
},
dataType: 'json',
contentType: 'application/x-www-form-urlencoded; charset=utf-8',
success: successFu,
});

步骤2只需通过[ValidateAntiForgeryToken]来修饰您的操作方法。

我尝试了很多变通方法,但没有一种对我有效。例外情况是“必需的防伪表单字段”__“ RequestVerificationToken ”。

帮助我解决问题的是将.ajax格式转换为.post:

$.post(
url,
$(formId).serialize(),
function (data) {
$(formId).html(data);
});

在ASP.NET MVC中,当您使用@Html.AntiForgeryToken()时,Razor会创建一个名称__RequestVerificationToken的隐藏输入字段来存储令牌。如果您想要编写一个Ajax实现,您必须自己获取这个令牌,并将其作为参数传递给服务器,以便对其进行验证。

步骤1:获取令牌

var token = $('input[name="`__RequestVerificationToken`"]').val();

步骤2:在Ajax调用中传递令牌

function registerStudent() {


var student = {
"FirstName": $('#fName').val(),
"LastName": $('#lName').val(),
"Email": $('#email').val(),
"Phone": $('#phone').val(),
};


$.ajax({
url: '/Student/RegisterStudent',
type: 'POST',
data: {
__RequestVerificationToken:token,
student: student,
},
dataType: 'JSON',
contentType:'application/x-www-form-urlencoded; charset=utf-8',
success: function (response) {
if (response.result == "Success") {
alert('Student Registered Succesfully!')


}
},
error: function (x,h,r) {
alert('Something went wrong')
}
})
};

注意:内容类型应为'application/x-www-form-urlencoded; charset=utf-8'

我已经在GitHub上传了这个项目。你可以下载试试。

https://github.com/lambda2016/ajaxvalidateantiforgerytoken.





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 "";
}
}

请随意使用下面的功能:

function AjaxPostWithAntiForgeryToken(destinationUrl, successCallback) {
var token = $('input[name="__RequestVerificationToken"]').val();
var headers = {};
headers["__RequestVerificationToken"] = token;
$.ajax({
type: "POST",
url: destinationUrl,
data: { __RequestVerificationToken: token }, // Your other data will go here
dataType: "json",
success: function (response) {
successCallback(response);
},
error: function (xhr, status, error) {
// handle failure
}
});

}

在ASP.NET Core中,您可以直接请求令牌,如文件所述

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions{
public string GetAntiXsrfRequestToken()
{
return Xsrf.GetAndStoreTokens(Context).RequestToken;
}
}

并在JavaScript中使用它:

function DoSomething(id) {
$.post("/something/todo/"+id,
{ "__RequestVerificationToken": '@GetAntiXsrfRequestToken()' });
}

您可以添加建议的全局过滤器,如文件所述

services.AddMvc(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
})

更新

上面的解决方案在.cshtml的脚本中起作用。如果不是这样,那么您就不能直接使用它。我的解决方案是首先使用一个隐藏字段来存储值。

我的解决方法,仍然使用GetAntiXsrfRequestToken

当没有表格时:

<input type="hidden" id="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">

name属性可以省略,因为我使用的是id属性。

每种形式包括此内标识。因此,您也可以通过name来搜索现有字段,而不是在隐藏字段中添加相同标记的另一个副本。请注意:一个文档中可以有多个表单,因此name在这种情况下并不唯一。与_不同,ABC_2属性应该是唯一的。

在脚本中,按ID查找:

function DoSomething(id) {
$.post("/something/todo/"+id,
{ "__RequestVerificationToken": $('#RequestVerificationToken').val() });
}

另一种不必引用令牌的方法是使用脚本提交表单。

样表:

<form id="my_form" action="/something/todo/create" method="post">
</form>

令牌将作为隐藏字段自动添加到表单中:

<form id="my_form" action="/something/todo/create" method="post">
<input name="__RequestVerificationToken" type="hidden" value="Cf..." /></form>

并在脚本中提交:

function DoSomething() {
$('#my_form').submit();
}

或使用POST方法:

function DoSomething() {
var form = $('#my_form');


$.post("/something/todo/create", form.serialize());
}

在帐户控制器中:

    // POST: /Account/SendVerificationCodeSMS
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public JsonResult SendVerificationCodeSMS(string PhoneNumber)
{
return Json(PhoneNumber);
}

视图中:

$.ajax(
{
url: "/Account/SendVerificationCodeSMS",
method: "POST",
contentType: 'application/x-www-form-urlencoded; charset=utf-8',
dataType: "json",
data: {
PhoneNumber: $('[name="PhoneNumber"]').val(),
__RequestVerificationToken: $('[name="__RequestVerificationToken"]').val()
},
success: function (data, textStatus, jqXHR) {
if (textStatus == "success") {
alert(data);
// Do something on page
}
else {
// Do something on page
}
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(textStatus);
console.log(jqXHR.status);
console.log(jqXHR.statusText);
console.log(jqXHR.responseText);
}
});

重要的是将contentType设置为'application/x-www-form-urlencoded; charset=utf-8',或者直接从对象中省略contentType

如果令牌是由不同的控制器提供的,则它将不起作用。例如,如果视图是由Accounts控制器返回的,但您将ABC_1_到Clients控制器,则它将不起作用。

创建一个负责添加令牌的方法

var addAntiForgeryToken = function (data) {
data.__RequestVerificationToken = $("[name='__RequestVerificationToken']").val();
return data;
};

现在,在将数据/参数传递给如下所示的操作时使用此方法

 var Query = $("#Query").val();
$.ajax({
url: '@Url.Action("GetData", "DataCheck")',
type: "POST",
data: addAntiForgeryToken({ Query: Query }),
dataType: 'JSON',
success: function (data) {
if (data.message == "Success") {
$('#itemtable').html(data.List);
return false;
}
},
error: function (xhr) {
$.notify({
message: 'Error',
status: 'danger',
pos: 'bottom-right'
});
}
});

在这里,我的操作只有一个字符串类型的参数。

    [HttpPost]
[ValidateAntiForgeryToken]
public JsonResult GetData( string Query)
{
 @using (Ajax.BeginForm("SendInvitation", "Profile",
new AjaxOptions { HttpMethod = "POST", OnSuccess = "SendInvitationFn" },
new { @class = "form-horizontal", id = "invitation-form" }))
{
@Html.AntiForgeryToken()
<span class="red" id="invitation-result">@Html.ValidationSummary()</span>


<div class="modal-body">
<div class="row-fluid marg-b-15">
<label class="block">
</label>
<input type="text" id="EmailTo" name="EmailTo" placeholder="forExample@gmail.com" value="" />
</div>
</div>


<div class="modal-footer right">
<div class="row-fluid">
<button type="submit" class="btn btn-changepass-new">send</button>
</div>
</div>
}

对我来说,解决方案是将令牌作为标头发送,而不是作为Ajax调用中的数据:

    $.ajax({
type: "POST",
url: destinationUrl,
data: someData,
headers:{
"RequestVerificationToken": token
},
dataType: "json",
success: function (response) {
successCallback(response);
},
error: function (xhr, status, error) {
// handle failure
}
});