【问题标题】:include antiforgerytoken in ajax post ASP.NET MVC在 ajax post ASP.NET MVC 中包含 antiforgerytoken
【发布时间】:2013-01-06 13:42:33
【问题描述】:

我在使用 ajax 的 AntiForgeryToken 时遇到问题。我正在使用 ASP.NET MVC 3。我尝试了jQuery Ajax calls and the 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>

【问题讨论】:

    标签: asp.net ajax asp.net-mvc asp.net-mvc-3 csrf


    【解决方案1】:

    您错误地将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>
    

    【讨论】:

    • 您好,感谢您的快速回复。对不起,我没有在问题中提到它;我目前没有使用提交操作。 (令牌在一个表单中,但我没有使用提交按钮来提交它)。是否可以将内容类型更改为其他内容?
    • 您没有使用提交操作这一事实并没有太大改变我的答案。您需要做的就是订阅一些其他事件(按钮单击、锚点单击或其他任何事情,只需读取隐藏字段值)。就发送 AJAX 请求而言,您可以使用我的回答中提供的示例。您不应使用contentTypeapplication/json,因为服务器期望__RequestVerificationToken 参数成为使用application/x-www-form-urlencoded 的POST 请求有效负载的一部分。
    • 这段代码$(this).data('url'),如何理解我的控制器和操作的url。请解释。谢谢
    • 最初的问题是关于 contentType: 'application/json'。对于常规的 ajax 帖子,包括表单帖子中的 __RequestVerificationToken 显然会起作用,因为它就像一个常规的表单帖子。但是,当您想发布 json (因此是内容类型)时,这似乎不起作用。所以这是一个错误地接受上述作为答案的情况。
    • 我需要使用“ModelState.IsValid”吗?我怎么知道这是有效的?
    【解决方案2】:
    我所做的

    另一种(较少 javascriptish)方法是这样的:

    首先,一个 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
            });
        });
    });
    

    而且它似乎有效!

    【讨论】:

    • +1,很好。我只是将@Html.AntiForgeryTokenForAjaxPost 一分为二,以便一方面获取令牌名称,另一方面获取其值。否则语法高亮就一团糟。它最终是这样的(也从返回的结果中删除了单引号,因此它的行为类似于任何 MVC 助手,例如 @Url):'@Html.AntiForgeryTokenName' : '@Html.AntiForgeryTokenValue'
    • 不错。有了这个,你有 ajax 调用 n cshtm 文件....在我看来,你不应该用剃须刀对 js 进行过多的处理。
    • 我对这个问题投了反对票,因为我相信更简单的方法是使用 AntiForgery 静态类。获取 HTML 并替换它而不是直接获取令牌值是不好的做法。 ASP.NET 是完全开源的:github.com/ASP-NET-MVC/aspnetwebstack/blob/…(但现在值得使用仅获取令牌的自定义扩展方法编写另一个答案)
    • 获取令牌值的更简洁的方法是使用 XElement。 XElement.Parse(antiForgeryInputTag).Attribute("value").Value
    • @transformer var antiForgeryInputTag = helper.AntiForgeryToken().ToString(); return XElement.Parse(antiForgeryInputTag).Attribute("value").Value
    【解决方案3】:

    就是这么简单!当您在 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,
    });
    

    第二步只是用[ValidateAntiForgeryToken]装饰你的动作方法

    【讨论】:

    • 谢谢,非常适合 json 帖子...我缺少 contentType :(
    • 谢谢。使用$(htmlWithInputString).val() 获取令牌的好主意。我用 data 属性做到了(以避免 html 中的内联脚本)。 HTML 中的 &lt;div class="js-html-anti-forgery-token" data-anti-forgery-token-html-input="@(Html.AntiForgeryToken().ToString())"&gt; 和 JS 中的 $($(".js-html-anti-forgery-token").data("antiForgeryTokenHtmlInput")).val() 之类的东西。
    【解决方案4】:

    在 Asp.Net Core 中,您可以直接请求令牌,as documented

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

    您可以添加推荐的全局过滤器,as documented

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

    更新

    上述解决方案适用于 .cshtml 中的脚本。如果不是这种情况,那么您不能直接使用它。我的解决方案是先使用隐藏字段来存储值。

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

    当没有表格时:

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

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

    每个表单都包含这个令牌。因此,您也可以通过name 搜索现有字段,而不是在隐藏字段中添加相同标记的另一个副本。请注意:一个文档中可以有多个表单,所以name 在这种情况下不是唯一的。不像id 属性,应该是唯一的。

    在脚本中,按 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());
    }
    

    【讨论】:

    • 我认为这个解决方案只有在你的 javascript 也在你的 cshtml 文件中时才有效。
    【解决方案5】:

    在 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

    【讨论】:

    • 我如何在这里使用表单序列化学生:$('#frm-student').serialize(),
    【解决方案6】:
    功能删除人员(ID){ var data = new FormData(); data.append("__RequestVerificationToken", "@HtmlHelper.GetAntiForgeryToken()"); $.ajax({ 类型:'POST', url: '/人事/删除/' + id, 数据:数据, 缓存:假, 处理数据:假, 内容类型:假, 成功:函数(结果){ } }); } 公共静态类 HtmlHelper { 公共静态字符串 GetAntiForgeryToken() { System.Text.RegularExpressions.Match 值 = System.Text.RegularExpressions.Regex.Match(System.Web.Helpers.AntiForgery.GetHtml().ToString(), "(?:value=\")(.*)(? :\")"); 如果(价值。成功) { 返回值.Groups[1].Value; } 返回 ””; } }

    【讨论】:

      【解决方案7】:

      在帐户控制器中:

          // 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 很重要...

      【讨论】:

      • 不太实用,这意味着您必须对每个表单进行编码,如果表单有很多元素,那可能会很痛苦:(
      【解决方案8】:

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

      如果您不想处理来自控制器的 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>
      

      【讨论】:

        【解决方案9】:

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

        【讨论】:

          【解决方案10】:

          我尝试了很多解决方法,但没有一个对我有用。例外是“所需的防伪表单字段“__RequestVerificationToken”。

          帮助我的是将表单 .ajax 转换为 .post:

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

          【讨论】:

            【解决方案11】:

            请随意使用以下功能:

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

            }

            【讨论】:

              【解决方案12】:

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

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

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

               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'
                          });
                          }
                          });
              

              这里我的 Action 有一个字符串类型的参数

                  [HttpPost]
                  [ValidateAntiForgeryToken]
                  public JsonResult GetData( string Query)
                  {
              

              【讨论】:

                【解决方案13】:
                 @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>
                    }
                

                【讨论】:

                  猜你喜欢
                  • 2010-11-23
                  • 2015-06-25
                  • 2018-06-11
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多