9

ASP.NET MVC 的AntiForgeryToken机制基于当前的HttpContext.User. 当您调用Html.AntiForgeryToken(). 基本上没问题(请参阅此处最后一段中的解释),但是当您通过Ajax 调用登录时会出现问题。

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

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

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

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

我也尝试在这里HttpContext.User = new GenericPrincipal(new GenericIdentity(email), null);设置为详细,但它不起作用。

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

4

2 回答 2

6

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

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

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

编辑再读几遍后-我质疑

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

于 2011-10-18T18:42:43.833 回答
3

好的,我所做的是将这里的答案结合起来:jQuery Ajax calls 和 Html.AntiForgeryToken() with a partial。我正在使用淘汰赛,但对于那些不熟悉它的人来说,你仍然应该能够很容易地跟进。

首先我的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>

主要区别在于,我有一个包含 @Html.AntiForgeryToken() 的 AntiForgeryToken 部分,而不是 @Html.AntiForgeryToken()。

所以要真正澄清一下,我现在有一个 AntiForgeryToken.cshtml 文件,其中只有:

@Html.AntiForgeryToken()

现在,当您登录/注销时,您需要更新令牌,以便 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 可能(并且可能会)在您实际登录之前返回。

那应该这样做。更新令牌时我没有遇到任何其他时间,但它只需要另一个调用来更新部分。

于 2012-08-22T17:35:22.293 回答