首页 > 解决方案 > 通过angularjs登录时,MVC更新`__RequestVerificationToken`令牌

问题描述

我有一个包含一些 AngularJS 组件的 MVC 网站。

当我使用 angularjs 发出帖子请求时,我总是__RequestVerificationToken在页面上包含来自隐藏输入的令牌。

我的问题是这样的:

用户启动匿名会话,即未登录。

用户使用 angularjs 组件登录,该组件发送一个 post 请求。我的 MVC 控制器验证凭据和“__RequestVerificationToken”。用户登录后,它会返回一个新令牌。

然后 angularjs 控制器获取新令牌并更新隐藏输入以用于任何未来的请求。

但是,我使用 angularjs 发出的下一个请求验证失败,因为 var tokenCookie = filterContext.HttpContext.Request.Cookies.Get(AntiForgeryConfig.CookieName);(请参见下面的代码示例)仍然是来自匿名会话的旧令牌。

尽管“X-XSRF-Token”(见下面的代码示例)是新的。

如何将包含令牌的 http cookie (tokenCookie) 更新/更新为新的?

我在下面发布了我的代码副本。

我的动作过滤器:

public sealed class WebApiValidateAntiForgeryTokenAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
            var headers = filterContext.HttpContext.Request.Headers;

            var tokenCookie = filterContext.HttpContext.Request.Cookies.Get(AntiForgeryConfig.CookieName);

            var tokenHeader = string.Empty;
            if (headers.AllKeys.Contains("X-XSRF-Token"))
            {
                tokenHeader = headers.GetValues("X-XSRF-Token").FirstOrDefault();
            }

           AntiForgery.Validate(tokenCookie != null ? tokenCookie.Value : null, tokenHeader);

        base.OnActionExecuting(filterContext);
    }
}

我的登录控制器:

[WebApiValidateAntiForgeryTokenAttribute]
[HttpPost]
public ActionResult login(string email, string password)
{
    if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(password)) return new HttpUnauthorizedResult();

    var rq = HttpContext.Request;
    var r = validateLogin(email, password, true); // my login handling
    if (r.Success)
    {
        Response.StatusCode = (int)HttpStatusCode.OK;

        // within an action construct AJAX response and pass updated token to client
        return Json(new
        {
            __RequestVerificationToken = UpdateRequestVerificationToken(Request)
        });
    }
    else
    {
        return new HttpUnauthorizedResult();
    }
}

/// <summary>
/// resets AntiForgery validation token and update a cookie
/// The new antiforgery cookie is set as the results and sent
/// back to client with Ajax
/// </summary>
/// <param name="Request">request from current context</param>
/// <returns>string - a form token to pass to AJAX response</returns>
private string UpdateRequestVerificationToken(HttpRequestBase Request)
{
    string formToken;
    string cookieToken;
    const string __RequestVerificationToken = "__RequestVerificationToken";
    AntiForgery.GetTokens(Request.Form[__RequestVerificationToken], out cookieToken, out formToken);
    if (Request.Cookies.AllKeys.Contains(__RequestVerificationToken))
    {
        HttpCookie cookie = Request.Cookies[__RequestVerificationToken];
        cookie.HttpOnly = true;
        cookie.Name = __RequestVerificationToken;
        cookie.Value = cookieToken;
        Response.Cookies.Add(cookie);
    }
    return formToken;
}

我的 angularjs 登录处理:

login(email, password) {
    return new Promise((resolve, reject) => {
        return this.AccountRequest.login(email, password)
            .then(response => {
                const newToken = response.data['__RequestVerificationToken'];
                const oldTokenElement = angular.element('input[name="__RequestVerificationToken"]');
                oldTokenElement.val(newToken); // confirmed the new token has been updated in the hidden element

                resolve(this.refresh.bind(this));
            });
    })
}

每次我使用 angularjs 发出帖子请求时:

post(uri, queryParams = {}, data = null) {

    this.$http.defaults.headers.common['X-XSRF-Token'] = angular.element('input[name="__RequestVerificationToken"]').attr('value');
    this.$http.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest'; // Needed to ensure we get back http error instead of a webpage
    const config = this.makeConfig('POST', uri, queryParams, data);

    return this.$http(config);
}

makeConfig(method, uri, params = {}, data = null) {
    return {
        method,
        data,
        params,
        url: uri.toString(),
    };
}

标签: javascriptc#angularjsasp.net-mvc

解决方案


您的代码与您将值分配给现有 cookie 的方式有点不一致。

您的方法与方法返回的令牌 ( formToken) 与您的 cookie ( cookieToken) 中设置的令牌不匹配。注意方法的最后一行...

const string __RequestVerificationToken = "__RequestVerificationToken";
HttpCookie cookie = Request.Cookies[__RequestVerificationToken];

// check for null
if (cookie == null)
{
    // no cookie found, create it... or respond with an error
    throw new NotImplementedException();
}

// grab the cookie
AntiForgery.GetTokens(
    Request.Form[__RequestVerificationToken],
    out string cookieToken, 
    out string formToken);

if (!String.IsNullOrEmpty(cookieToken)
{
    // update the cookie value(s)
    cookie.Values[__RequestVerificationToken] = cookieToken;
    //...
}

// update the expiration timestamp
cookie.Expires = DateTime.UtcNow.AddDays(30);

// overwrite the cookie
Response.Cookies.Add(cookie);

// return the NEW token!
return cookieToken;

边注..

你可能不应该const在这里使用......

const newToken = response.data['__RequestVerificationToken']; // <-- doesn't look right...
const oldTokenElement = angular.element('input[name="__RequestVerificationToken"]');

Aconst不允许您重新分配该值。因此,newToken永远不会在客户端重新分配新的令牌值。您可能要考虑在此处使用 alet或 a 。var


推荐阅读