在 MVC 中如何使用防伪标记进行 Ajax 请求

27
我有一个关于MVC项目的问题。
当我试图使用带有加载面板(如旋转gif或文本)的jquery ajax请求时,我会遇到错误,从fiddler中观察到:
“所需的防伪表单字段“__RequestVerificationToken”不存在。”
如果我在POST操作方法中注释掉 [ValidateAntiForgeryToken] 属性并使用加载面板,则可以正常工作。我想知道为什么会出现这个错误。
我甚至使用了序列化的查询字符串。
__RequestVerificationToken= $('input[name="__RequestVerificationToken"').val()

我仍然遇到了错误

防伪标记无法解密。如果此应用程序由Web Farm或集群托管,请确保所有计算机都运行相同版本的ASP.NET Web Pages,并且配置指定明确的加密和验证密钥。

AutoGenerate不能在集群中使用

我应该使用什么?

这是更新后的问题代码

var token = $('input[name="__RequestVerificationToken"]').val();
$('#submitaddress').click(function subaddr(event) {
    event.preventDefault();
    event.stopPropagation();
  //$('#addAddress').html('<img src="/img/animated-overlay.gif"> Sending...');
   // $('#addAddress').blur();
    //  $(this).bl
    if ($('#Jobid').val()!="") {
        $('#TransportJobId').val(parseInt($('#Jobid').val()));
        $.ajax(
              {
                  url: '/TransportJobAddress/create',
                  type: 'POST',
                  data: "__RequestVerificationToken=" + token + "" + $('form[action="/TransportJobAddress/Create"]').serialize(),
                  success: function poste(data, textStatus, jqXHR) { $('#addAddress').html(data); return false; },
                  error: function err(jqXHR, textStatus, errorThrown) { alert('error at address :' + errorThrown); }
              });
    }
    else {
        var transportid = 2;
        $.ajax({
            url: '/TransportJob/create',
            type: 'POST',
            data: "__RequestVerificationToken=" + token + "" + $('form[action="/TransportJob/Create"]').serialize(),
            success: function sfn(data, textStatus, jqXHR) {
                transportid = parseInt(data);
                $('#Jobid').val(data);
               // alert('inserted id :' + data);
                $('#TransportJobId').val((transportid));
                $.ajax(
         {

             url: '/TransportJobAddress/create',
             type: 'POST',
             //beforeSend: function myintserver(xhr){  
             //        $('#addAddress').html('<div id="temp_load" style="text-align:center">please wait ...</div>');
             //}, 
             data: "__RequestVerificationToken=" + token + "" + $('form[action="/TransportJobAddress/Create"]').serialize(),
             success: function poste(data, textStatus, jqXHR) {
                 $('#addAddress').html(data);

             },
             error: function err(jqXHR, textStatus, errorThrown) {
                 alert('error at address :' + errorThrown);
             }

         });
            },
            error: function myfunction(jqXHR, textStatus, errorThrown) {
                alert("error at transport :" + jqXHR.textStatus);
            },
            complete: function completefunc() {
              //  alert('ajax completed all requests');
                return false;
            }

        });
    }
});
标签
<form action="/TransportJob/Create" method="post"><input     name="__RequestVerificationToken" type="hidden"   value="ydYSei0_RfyBf619dQrhDwwoCM7OwWkJQQEMNvNdAkefiFfYvRQ0MJYYu0zkktNxlJk_y1ZJO9-yb-  COap8mqd0cvh8cDYYik4HJ0pZXTgE1" />   

TransportJob
在同一页上的第二个表单标签

<form action="/TransportJobAddress/Create" method="post" novalidate="novalidate"><input name="__RequestVerificationToken" type="hidden"    value="Np2vUZJPk1TJlv846oPSU6hg4SjMHRcCk1CacaqZbpHOg8WbV4GZv06noRDl7F_iT9qQf3BIXo3n9wGW68sU mki7g3-ku_BSHBDN-g2aaKc1"> 


1
你是否在你的视图中添加了令牌?像这样:@Html.AntiForgeryToken() - hofnarwillie
默认情况下,它会在表单标签后的 fieldset 标签之前添加。 - Ravi Hanok
1
将您的AntiForgeryToken添加到ajax调用的标头中,而不是数据中。 - freshbm
刚看到你的更新。你能展示一下你的Ajax请求中的数据和头部字段吗? - Kippie
我有两个表单,点击一次将提交第一个表单,第二个表单将使用第一个表单的id进行提交。 - Ravi Hanok
1
我自己解决了这个问题,因为我的错误是,在收集详细信息之前,我使用了加载面板ID(不应该在按钮单击事件下使用加载面板,我们应该在发送ajax事件之前使用)。除了我的错误解决方案外,@freshbm也给出了解决方案。 - Ravi Hanok
5个回答

37

与其手动将其添加到每个请求中,我通常会做类似于这样的事情:

var token = $('input[name="__RequestVerificationToken"]').val();
$.ajaxPrefilter(function (options, originalOptions) {
  if (options.type.toUpperCase() == "POST") {
    options.data = $.param($.extend(originalOptions.data, { __RequestVerificationToken: token }));
  }
});

这将自动将你的令牌添加到你进行的任何 Ajax POST 中。


很好的解决方法! 只是一个小提醒:如果您正在使用作为ajax调用“数据”的FormData(就像@hofnarwillie答案中所示),则必须使用**.append**: options.data.append("__RequestVerificationToken", token); - Silvestre

18

您是否已将 Token 添加到 ajax 调用的标头中?

在 ajax 调用的消息标头中,您需要添加防伪标记:

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

var headers = {};

headers['__RequestVerificationToken'] = token;

$.ajax({
        url: ... some url,
        headers: headers,
        ....
});

在你的代码中尝试以下内容:

var token = $('input[name="__RequestVerificationToken"]').val();
var tokenadr = $('form[action="/TransportJobAddress/Create"] input[name="__RequestVerificationToken"]').val(); 

var headers = {};
var headersadr = {};
headers['__RequestVerificationToken'] = token;
headersadr['__RequestVerificationToken'] = tokenadr;

$('#submitaddress').click(function subaddr(event) {
    event.preventDefault();
    event.stopPropagation();
  //$('#addAddress').html('<img src="/img/animated-overlay.gif"> Sending...');
   // $('#addAddress').blur();
    //  $(this).bl
    if ($('#Jobid').val()!="") {
        $('#TransportJobId').val(parseInt($('#Jobid').val()));
        $.ajax(
              {
                  url: '/TransportJobAddress/create',
                  type: 'POST',
                  headers:headersadr, 
                  data: "__RequestVerificationToken=" + token + "" + $('form[action="/TransportJobAddress/Create"]').serialize(),
                  success: function poste(data, textStatus, jqXHR) { $('#addAddress').html(data); return false; },
                  error: function err(jqXHR, textStatus, errorThrown) { alert('error at address :' + errorThrown); }
              });
    }
    else {
        var transportid = 2;
        $.ajax({
            url: '/TransportJob/create',
            type: 'POST',
            headers:headers, 
            data: $('form[action="/TransportJob/Create"]').serialize(),
            success: function sfn(data, textStatus, jqXHR) {
                transportid = parseInt(data);
                $('#Jobid').val(data);
               // alert('inserted id :' + data);
                $('#TransportJobId').val((transportid));
                $.ajax(
         {

             url: '/TransportJobAddress/create',
             type: 'POST',
             //beforeSend: function myintserver(xhr){  
             //        $('#addAddress').html('<div id="temp_load" style="text-align:center">please wait ...</div>');
             //},
             headers:headers, 
             data: $('form[action="/TransportJobAddress/Create"]').serialize(),
             success: function poste(data, textStatus, jqXHR) {
                 $('#addAddress').html(data);

             },
             error: function err(jqXHR, textStatus, errorThrown) {
                 alert('error at address :' + errorThrown);
             }

         });
            },
            error: function myfunction(jqXHR, textStatus, errorThrown) {
                alert("error at transport :" + jqXHR.textStatus);
            },
            complete: function completefunc() {
              //  alert('ajax completed all requests');
                return false;
            }

        });
    }
});

在您的ajax调用中添加头部行。


你好,freshbm。在第一个表单中添加标题已经起作用了,但是在提交第二个表单时却没有起作用,如何为第二个表单添加标题并与第一个表单区分开来呢?我已经使用了不同的标题,例如:var tokenaddr = $('form[action="/TransportJobAddress/create"] input[name="__RequestVerificationToken"]').val(); headers['__RequestVerificationToken'] = tokenaddr; - Ravi Hanok
我已经在同一页上添加了两个表单标记,如何获取这两个令牌并在头部中使用。 - Ravi Hanok
也许您拼写了表单操作错误:$('form[action="/TransportJobAddress/Create"]在您的注释中,Create是小写字母。tokenaddr的值是什么? - freshbm
如果我在Fiddler中将以下代码用作第二个表单头部:var tokenaddr =('form[action="/TransportJobAddress/create"] input[name="__RequestVerificationToken"]').val(); headers['__RequestVerificationToken'] = tokenaddr; 我会得到响应“RequestVerificationToken不存在”的错误信息。不知道我是如何忽略第二个表单的头部的。 - Ravi Hanok

3
你是否已将令牌添加到你的视图中?像这样:
<form method="post" action="/my-controller/my-action">
    @Html.AntiForgeryToken()
</form>

由于您的控制器接收的是POST请求,因此需要确保在视图中将防伪标记添加到表单中。

编辑:

首先尝试使用JSON构建数据:

var formData = $('form[action="/TransportJobAddress/Create"]').serialize();
$.extend(formData, {'__RequestVerificationToken': token });

//and then in your ajax call:
$.ajax({
    //...
    data:formData
    //...
});

@using (Html.BeginForm()) @Html.AntiForgeryToken() @Html.ValidationSummary(true) 默认情况下这些都是存在的,并且我正在使用它与ajax请求一起。 - Ravi Hanok
但是您正在通过ajax提交表单。您是否确保字段值包含在提交的ajax表单数据中? - hofnarwillie
你能使用 Fiddler 或 Firebug 来查看实际发布了什么吗?或者将执行 AJAX 调用的代码发布到上面的问题中。 - hofnarwillie
是的令牌与查询字符串一起提交,并且从 Fiddler 观察到。 - Ravi Hanok
你是在使用webfarm还是集群? - hofnarwillie
你能否发布上面执行ajax的javascript代码? - hofnarwillie

2

视图或布局:

< form id='_id' method='POST'> @html.antiforgeryToken(); < /form>

Ajax调用函数:

var data={...};
var token=$('#_id').serializeObject();
var dataWithAntiforgeryToken = $.extend(data,token);

$.ajax(
{
....,
data: dataWithAntiforgeryToken;
}
)

0

我想要保护Ajax和普通请求,所以这是我想出来的:

首先使用haacked.com上的优秀博客创建了ConditionalFilterProvider。

然后按照codethinked博客中的描述创建了所有类。

在我的_layout页面中,我添加了$.ajaxPrefilter代码片段,如博客中所述... 这确保了所有我的Ajax回调现在都通过标头发送Antiforgery令牌。

为了将所有内容粘合在一起,我在global.asax / Application_Start中添加了这段代码。

(c, a) =>
                    (c.HttpContext.Request.IsAjaxRequest() &&
                     !string.Equals(c.HttpContext.Request.HttpMethod, "GET"))
                        ? new AjaxValidateAntiForgeryTokenAttribute()
                        : null,
(c, a) =>
                    (!c.HttpContext.Request.IsAjaxRequest() &&
                     string.Equals(c.HttpContext.Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase))
                        ? new ValidateAntiForgeryTokenAttribute()
                        : null

基本上..将属性注入到所有不是GET的控制器中。

之后,我只需要去我的(非常少的)表单中添加@Html.AntiForgeryToken()。

为了证明所有工作正常,我只需尝试使用没有AntiForgeryToken的表单来压缩事物,并获得预期的异常。 并删除$.ajaxPrefilter并创建Ajax请求,预期的异常被接收。


感谢您的建议。这样是否更好? - CodeHacker
是的 - 感谢您为提高本网站质量做出的贡献 :-) - kleopatra
这意味着任何能够模拟ajax调用的人(即任何具有JavaScript调试器的人)都可以在没有伪造令牌的情况下执行对您的服务器的调用。 - David Schmitt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接