Asp.net mvc MVC3 AntiForgeryToken在Ajax登录时中断

Asp.net mvc MVC3 AntiForgeryToken在Ajax登录时中断,asp.net-mvc,ajax,asp.net-mvc-3,security,antiforgerytoken,Asp.net Mvc,Ajax,Asp.net Mvc 3,Security,Antiforgerytoken,ASP.NET MVC的AntiForgeryToken机制基于当前的HttpContext.User。调用Html.AntiForgeryToken()时,它使用该值构造令牌。基本上是可以的(请参阅中的说明),但是当您通过Ajax调用登录时会出现问题 在我的代码中,当用户登录时,凭据在Ajax中作为Json对象发送(Json中也发送AntiForgeryToken隐藏字段值),服务器验证用户,应用FormsAuthentication.SetAuthCookie(),并返回包含一些用户特定数据

ASP.NET MVC的AntiForgeryToken机制基于当前的
HttpContext.User
。调用
Html.AntiForgeryToken()
时,它使用该值构造令牌。基本上是可以的(请参阅中的说明),但是当您通过Ajax调用登录时会出现问题

在我的代码中,当用户登录时,凭据在Ajax中作为Json对象发送(Json中也发送
AntiForgeryToken
隐藏字段值),服务器验证用户,应用FormsAuthentication.SetAuthCookie(),并返回包含一些用户特定数据的Json结果。这样,我可以避免登录时刷新整个页面

问题在于,服务器的每个后续Ajax请求现在都会在
ValidateAntiForgeryTokenAttribute
时失败,因为它现在需要一个与防伪cookie不兼容的防伪令牌

如何获取有效的防伪令牌以放入客户端的隐藏字段,以便登录后的每个Json请求都会成功?

我试图手动获取一个新的隐藏字段令牌(在操作中使用
AntiForgery.GetHtml()
,提取令牌字符串本身,用Json将其返回给客户端,并用JavaScript手动将其放置在防伪隐藏字段中)但它不起作用-随后的Ajax调用在服务器上的
ValidateAntiForgeryTokenAttribute
上失败。 事实上,每次调用
AntiForgery.GetHtml()
(本质上就是
Html.AntiForgeryToken()
helper所做的)都会生成一个不同的令牌,从而使前一个令牌无效

我还尝试设置
HttpContext.User=new-GenericPrincipal(new-generientity(email),null)详细说明,但不起作用


注意:不适用于我,因为我的具体情况是:Ajax登录会更改服务器上的用户身份,因此在登录之前生成的每个令牌都是无效的;也不适用,因为它解决了一个不同的问题。

您需要在登录时清除并重做任何现有的表单令牌。这意味着您的登录代码必须刷新当前页面(有点扼杀了其中的ajax部分)、您自己的令牌实现,或者您需要刷新令牌。可以请求局部视图、提取令牌并更新表单。实际上,您可以有一个restful url,它只向经过身份验证的用户返回一个令牌。有人可能会说这是一个安全问题,但我不这么认为,因为这只是一种更容易获取令牌的方法,而不是请求任何视图(部分视图或其他视图)

您应该能够通过以下方式轻松获得要替换的令牌实例:

var token = $('input[name=""__RequestVerificationToken""]'); var-token=$('input[name=”“\uu RequestVerificationToken”“]); 编辑 在再读几遍之后,我提出了一个问题


如果用户没有登录,为什么表单上会有令牌。是否允许在未登录和已登录的情况下“操作”同一表单?即使在这种情况下,网络上的大多数站点也会重定向以进行登录。我理解正确吗?如果是这样,您可能想考虑跳过令牌,或者使用第二类型的令牌用于未经身份验证的用户。我相信您是说,未经身份验证的用户已经可以在应用程序中提交某些内容了(如果我理解正确的话)。

好的,我所做的是将这里的答案结合起来:使用部分。我使用的是击倒,但对于那些不熟悉它的人来说,你们应该仍然能够很容易地跟随

首先,我的html:

<form id="__AjaxAntiForgeryForm" action="#" method="post">@{Html.RenderPartial("AntiForgeryToken");}</form>
<div id="loginTestView">
    <button data-bind="visible: signedIn() == false,click: signIn">Sign In</button>
    <button data-bind="visible: signedIn, click: signOut">Sign Out</button>

    <form>
        <button data-bind="click: testToken">Test Token</button>
    </form>
</div>
现在,当您登录/注销时,需要更新令牌,以便javascript/jquery如下所示:

$(document).ready(function () {
    AddAntiForgeryToken = function (data) {
        data.__RequestVerificationToken = $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val();
        return data;
    };

    var viewmodel = function () {
        var vm = this;

        vm.signedIn = ko.observable(false);

        vm.signIn = function () {
            $.post('Home/SignIn', function () {
                vm.signedIn(true);
                $.get('Home/GetAuthToken', function (newToken) {
                    $('#__AjaxAntiForgeryForm').html(newToken);
                });
            });

        };
        vm.signOut = function () {
            $.post('Home/SignOut', function () {
                vm.signedIn(false);
                $.get('Home/GetAuthToken', function (newToken) {
                    $('#__AjaxAntiForgeryForm').html(newToken);
                });
            });
        };
        vm.testToken = function () {
            $.post('Home/TestToken', AddAntiForgeryToken({ stuff: 'stuff' }));
        };
    };

    ko.applyBindings(new viewmodel(), $('#loginTestView')[0]);
});
这里需要注意的主要问题是,$.get需要发生在$.post之后才能登录/注销。这段代码可以清理一下,但这是主要的外卖。如果不这样做,则由于请求是异步的,$.get可能(而且可能会)在您实际登录之前返回


应该这样做。我没有遇到过更新令牌的任何其他情况,但只需要再次调用即可更新部分令牌。

当用户未经身份验证时,为什么要在登录页面上使用AntiForgeryToken。你在保护什么?登录功能不是一个页面,它是站点模板中的一个片段。在登录特性中确实不需要它,但是问题在服务器端的登录方法设置当前用户(HttpContext.user)并返回后出现。在这个阶段,页面应该已经有了一些防伪令牌隐藏字段,以服务进一步的Ajax调用。这和你的问题有什么关系吗。。。“问题在于,在调用堆栈深处的引擎盖下,属性窥视Request.Form集合以获取防伪令牌。但是,当您发布JSON编码的数据时,没有表单集合可言。”没有Jasper,不适用于我……同样的问题,你有没有找到这个问题的实际解决方案?至于你的最后一个问题,为什么我需要为未经身份验证的用户提供令牌,请参阅我对Ben评论的回答。我将尝试使用标记创建局部视图,并使用结果更新您。听起来是个好主意!我还没有开始,但我会接受你的答案,因为这听起来很有希望:-)很好的解决方案@rball,我会尝试一下(不过需要一些时间)。
$(document).ready(function () {
    AddAntiForgeryToken = function (data) {
        data.__RequestVerificationToken = $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val();
        return data;
    };

    var viewmodel = function () {
        var vm = this;

        vm.signedIn = ko.observable(false);

        vm.signIn = function () {
            $.post('Home/SignIn', function () {
                vm.signedIn(true);
                $.get('Home/GetAuthToken', function (newToken) {
                    $('#__AjaxAntiForgeryForm').html(newToken);
                });
            });

        };
        vm.signOut = function () {
            $.post('Home/SignOut', function () {
                vm.signedIn(false);
                $.get('Home/GetAuthToken', function (newToken) {
                    $('#__AjaxAntiForgeryForm').html(newToken);
                });
            });
        };
        vm.testToken = function () {
            $.post('Home/TestToken', AddAntiForgeryToken({ stuff: 'stuff' }));
        };
    };

    ko.applyBindings(new viewmodel(), $('#loginTestView')[0]);
});