在 ajax post ASP.NET MVC 中包含防伪令牌

include antiforgerytoken in ajax post ASP.NET MVC

提问人:OJ Raqueño 提问时间:1/23/2013 最后编辑:CommunityOJ Raqueño 更新时间:3/31/2023 访问量:231521

问:

我在使用 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.

有什么想法吗?

编辑

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

下面是包含令牌的窗体,位于顶部母版页:

<form id="__AjaxAntiForgeryForm" action="#" method="post">
    @Html.AntiForgeryToken()
</form>
asp.net ajax asp.net-mvc asp.net-mvc-3 csrf

评论


答:

334赞 Darin Dimitrov 1/23/2013 #1

您错误地指定了 to .contentTypeapplication/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>

评论

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

我做的另一种(不那么 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
        });
    });
});

它似乎有效!

评论

5赞 Askolein 3/18/2014
+1,不错。我只是将它一分为二,以便一只手获得令牌名称,另一只手获得其值。否则语法高亮就搞砸了。它最终是这样的(也从返回的结果中删除了单引号,以便它的行为与任何 MVC 帮助程序一样,例如 @Url):@Html.AntiForgeryTokenForAjaxPost'@Html.AntiForgeryTokenName' : '@Html.AntiForgeryTokenValue'
4赞 bunny1985 3/12/2015
尼特不错。有了这个,你就有了ajax调用n cshtm文件....在我看来,你不应该用剃须刀那么多 MOX JS。
0赞 usr-local-ΕΨΗΕΛΩΝ 6/16/2015
我否决了这个问题,因为我相信更简单的方法是使用 AntiForgery 静态类。获取 HTML 并替换它而不是直接获取令牌值是一种不好的做法。ASP.NET 是完全开源的:github.com/ASP-NET-MVC/aspnetwebstack/blob/... (但现在可能值得使用仅获取令牌的自定义扩展方法编写另一个答案)
4赞 darrunategui 11/24/2017
仅获取令牌值的更简洁方法是使用 XElement。XElement.Parse(antiForgeryInputTag).Attribute("value").Value
3赞 darrunategui 1/11/2018
@transformervar antiForgeryInputTag = helper.AntiForgeryToken().ToString(); return XElement.Parse(antiForgeryInputTag).Attribute("value").Value
3赞 Aamir 6/28/2015 #3

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

如果你不想处理控制器的 post 操作的结果,比如调用控制器的方法,你可以按照以下版本的 @DarinDimitrov 的答案来操作:LoggOffAccounts

@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>
68赞 Abolfazl 2/22/2016 #4

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

1.发送一个名为 and 的参数,以获取其值,使用以下代码:__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]

评论

1赞 Snziv Gupta 7/19/2018
谢谢,适用于json帖子...我缺少 contentType :(
1赞 Mariusz Pawelski 2/24/2021
谢谢。使用获取令牌的好主意。我用 data 属性做到了这一点(以避免 html 中的内联脚本)。在 HTML 和 JS 中类似这样的东西。$(htmlWithInputString).val()<div class="js-html-anti-forgery-token" data-anti-forgery-token-html-input="@(Html.AntiForgeryToken().ToString())">$($(".js-html-anti-forgery-token").data("antiForgeryTokenHtmlInput")).val()
0赞 Stefan Michev 7/19/2016 #5

我尝试了很多工作轮,但没有一个对我有用。例外情况是“所需的防伪表单字段”__RequestVerificationToken”。

对我有帮助的是将 .ajax 表单切换到 .post:

$.post(
    url,
    $(formId).serialize(),
    function (data) {
        $(formId).html(data);
    });
8赞 Frank Odoom 7/20/2016 #6

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

第 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

评论

0赞 LittleDragon 5/27/2018
我如何使用表单在这里序列化学生:$('#frm-student').serialize(),
7赞 ismail eski 9/2/2016 #7
        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 "";
            }
        }
0赞 Komal Narang 9/20/2018 #8

随意使用以下功能:

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

}

16赞 user4864425 10/4/2018 #9

在 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()">

由于我使用该属性,因此可以省略该属性。nameid

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

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

评论

0赞 carlin.scott 2/14/2020
我认为这个解决方案只有在你的javascript也在你的cshtml文件中时才有效。
4赞 Adel Mourad 11/10/2018 #10

在 Account controller:

    // 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

评论

0赞 djack109 5/9/2020
不是很实用,这意味着你必须对每个表单进行编码,如果表单有很多元素,这可能是一个痛苦的:(
1赞 OutstandingBill 8/13/2019 #11

如果令牌由其他控制器提供,则令牌将不起作用。例如,如果视图是由控制器返回的,而是您返回给控制器的,则该视图将不起作用。AccountsPOSTClients

0赞 Aijaj Hussain Ansari 8/11/2021 #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'
            });
            }
            });

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

    [HttpPost]
    [ValidateAntiForgeryToken]
    public JsonResult GetData( string Query)
    {
0赞 mahdi 11/17/2021 #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="[email protected]" value="" />
            </div>
        </div>

        <div class="modal-footer right">
            <div class="row-fluid">
                <button type="submit" class="btn btn-changepass-new">send</button>
            </div>
        </div>
    }
3赞 TsvetiZlateva 10/31/2022 #14

对我来说,解决方案是将令牌作为标头发送,而不是作为 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
       }
   });
0赞 Chris Mylonas 3/31/2023 #15

我一直在为此苦苦挣扎,并提出了一个既声明不需要大量客户端或服务器更改的解决方案。 这适用于 ASPNET MVC .NET 4.8,但也可以在 .NET Core 中轻松完成。

简述:

  • 使用 ASPNET MVC AntiForgery.Validate() 和 AntiForgery.GetTokens()。
  • 将令牌作为隐藏字段保留在页面中。
  • 从页面获取令牌,并使用 jQuery.ajaxSend() 透明地将其“推送”到所有 JSON 调用中。
  • 使用 ValidateAntiForgeryJSONToken(自定义 FilterAttribute)处理收到的令牌。

好处:

  • 只需使用 [ValidateAntiForgeryJSONToken] 修饰控制器操作即可。
  • 添加一次令牌。
  • 将令牌沿 JSON 集中推送。

代码如下:

ValidateAntiForgeryJSONToken attibute

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    public class ValidateAntiForgeryJSONToken : FilterAttribute, IAuthorizationFilter
    {
        public ValidateAntiForgeryJSONToken()
        {
        }

        public virtual void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
                throw new ArgumentNullException(nameof(filterContext));

            var request = filterContext.RequestContext.HttpContext.Request;
            if (request == null)
                return;

            var stream = request.InputStream;
            var encoding = request.ContentEncoding;
            var reader = new StreamReader(stream, encoding);
            var json = reader.ReadToEnd();
            request.InputStream.Position = 0;
            var token = "";
            try
            {
                JObject o = JObject.Parse(json);
                var jToken = o.GetValue("ajaxAFT", StringComparison.InvariantCultureIgnoreCase);
                token = jToken.ToString();
            }
            catch (Exception)
            {
            }

            ValidateAntiForgeryToken(token);
        }

        public static void ValidateAntiForgeryToken(string token)
        {
            string cookieToken = "";
            string formToken = "";

            if (!String.IsNullOrWhiteSpace(token))
            {
                string[] tokens = token.Split(':');
                if (tokens.Length == 2)
                {
                    cookieToken = tokens[0].Trim();
                    formToken = tokens[1].Trim();
                }
            }

            try
            {
                AntiForgery.Validate(cookieToken, formToken);
            }
            catch (Exception)
            {
                throw new BaseException();
            }
        }
    }

控制器

 
        [HttpPost]
        [ValidateAntiForgeryJSONToken]
        public ActionResult TestAJAXCall(string id)
        {
        }

阿贾克斯拦截

'use strict';

$(document)
    .ajaxSend(function (event, jqXHR, ajaxSettings) {
        if (ajaxSettings !== undefined) {
            try {
                let payload = JSON.parse(ajaxSettings.data);
                let token = payload.ajaxAFT;
                if (token === undefined) {
                    token = $("#_ajaxAFT").val();
                    payload.ajaxAFT = token;
                    ajaxSettings.data = JSON.stringify(payload);
                }
            } catch (e) {

            }
        }
    });

Razor 令牌注入

 @Html.AjaxAntiForgeryToken()

Razor 令牌帮助程序

public static HtmlString AjaxAntiForgeryToken(this HtmlHelper helper)
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            var myToken = cookieToken + ":" + formToken;

            var model = new FormHiddenInputFormModel()
            {
                Id = "_ajaxAFT",
                Value = myToken

            };
            return helper.Partial("Forms/_FormHiddenInputText", model);
        }