调用 HttpContext.SignIn 后针对错误用户的 .NET Core Antiforgery.GetAndStoreTokens

Dan*_*cco 9 c# csrf .net-core

我一直在使用 .NET Core 的防伪 Cookie ( https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery ),并且我总是在登录:

// - HomeController.cs
[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> Login()
{
    ClaimsIdentity identity = new ClaimsIdentity("myAuthType");
    ClaimsPrincipal principal = new ClaimsPrincipal(identity);

    await HttpContext.SignInAsync("myScheme", principal,
        new AuthenticationProperties
        {
            ExpiresUtc = DateTime.UtcNow.AddMinutes(10),
            AllowRefresh = true,
            IsPersistent = true
        });

    return View();
}
Run Code Online (Sandbox Code Playgroud)

代码完成后,每个调用/视图都会生成一个新的 X-XSRF-Cookie 并发送到浏览器,浏览器会为某些 Api 调用发送回该值。其代码是:

// - Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddAntiforgery(o => o.HeaderName = CrossSiteAntiForgery.Header);
    services.AddMvc(o => {
        o.Filters.AddService(typeof(CrossSiteAntiForgery));
    });
}

// - CrossSiteAntiForgery.cs
public class CrossSiteAntiForgery : ResultFilterAttribute
{
    public const string Cookie = "XSRF-TOKEN";
    public const string Header = "X-XSRF-TOKEN";

    private IAntiforgery _antiforgery;
    public CrossSiteAntiForgery(IAntiforgery antiforgery)
    {
        _antiforgery = antiforgery;
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        AntiforgeryTokenSet tokens = _antiforgery.GetAndStoreTokens(context.HttpContext);
        context.HttpContext.Response.Cookies.Append(Cookie, tokens.RequestToken, new CookieOptions() { HttpOnly = false });            
    }
}
Run Code Online (Sandbox Code Playgroud)

但我想通过 Ajax 登录只是因为。所以我保留了所有内容并仅更改了登录名:

[HttpPost("Api/User/Login"), ValidateAntiForgeryToken, Produces("application/json")]
public async Task<IActionResult> Login()
{
    var userFromDB = DB.GetUser(1);

    ClaimsIdentity identity = new ClaimsIdentity("myAuthType");
    ClaimsPrincipal principal = new ClaimsPrincipal(identity);

    await HttpContext.SignInAsync("myScheme", principal,
        new AuthenticationProperties
        {
            ExpiresUtc = DateTime.UtcNow.AddMinutes(10),
            AllowRefresh = true,
            IsPersistent = true
        });

    return Json(userFromDB);
}
Run Code Online (Sandbox Code Playgroud)

但是现在我收到错误(错误请求),因为 XSRF 令牌不匹配。经过一些(实际上很多)挖掘后,我想我发现了问题,浏览器Set-Cookie确实从ajax正确接收了标头并设置了新的cookie值,但该值无效,因为似乎每当_antiforgery.GetAndStoreTokens(HttpContext)调用时,它都会使用当前的值用户以某种方式生成令牌。当我返回新视图时,上下文将使用新(登录)用户进行更新,生成令牌并将其发送到浏览器,一切正常。当我使用 ajax 和 call 时HttpContext.SignInAsync(),当前用户尚未更新,因此生成的令牌对于登录用户将无效。

// - Login()
var user = HttpContext.User;
await HttpContext.SignInAsync("Login", principal,
    new AuthenticationProperties
        {
            ExpiresUtc = DateTime.UtcNow.AddMinutes(10),
            AllowRefresh = true,
            IsPersistent = true
        });
var loggedInUser = HttpContext.User;

bool truth = user.Equals(loggedInUser); // - true
// - meaning anything that relies on the new logged in User is invalid from here on.
Run Code Online (Sandbox Code Playgroud)

.NET 控制台中的错误消息是The provided antiforgery token was meant for a different claims-based user我在这里尝试了这个建议,它看起来很老套,它只适用于第二个请求,这意味着用户需要为每个用户更改(登录和注销)看到第一个“错误请求”。登录后如何获取/更新用户,以便 XSRF 令牌生成正常工作?或者,如果有更好的解决方案,我该如何解决这个问题?

编辑:我现在正在解决这个问题,以防有人遇到同样的问题:

public async Task<IActionResult> Login()
{
    // - Login code omitted for brevity.
    return RedirectToAction("LoginUnelegantWorkaround");
}

public IActionResult LoginUnelegantWorkaround()
{
    var model = DB.FetchModelIWasGoingToReturnBefore();

    return Json(model);
    // - token is properly generated now since User has been updated, everything works
}

// - Same thing needed for Logout
Run Code Online (Sandbox Code Playgroud)