You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core中Access/Refresh Token Cookie更新后当前请求未生效问题求助

解决方案:在当前请求内临时存储新令牌

核心问题是Response.Cookie的变更仅对下一次请求生效,当前请求的Request.Cookie无法实时更新。我们可以利用HttpContext.Items(当前请求生命周期内的临时存储)来解决这个问题,让当前请求的后续流程直接读取新令牌,同时更新Response.Cookie保证后续请求生效。

1. 封装令牌读写的扩展方法

创建扩展方法统一处理令牌的读取和存储,优先从HttpContext.Items获取(当前请求的新令牌),再回退到Cookie:

public static class TokenHttpContextExtensions
{
    private const string AccessTokenCacheKey = "ActiveAccessToken";
    private const string RefreshTokenCacheKey = "ActiveRefreshToken";

    // 存储新令牌到Items并更新Cookie
    public static void UpdateTokens(this HttpContext context, string accessToken, string refreshToken)
    {
        // 存入当前请求临时存储
        context.Items[AccessTokenCacheKey] = accessToken;
        context.Items[RefreshTokenCacheKey] = refreshToken;

        // 更新Response Cookie(作用于下一次请求)
        var cookieOptions = new CookieOptions
        {
            HttpOnly = true,
            Secure = true,
            SameSite = SameSiteMode.Strict,
            IsEssential = true
        };

        context.Response.Cookies.Append("access_token", accessToken, cookieOptions with { Expires = DateTimeOffset.UtcNow.AddMinutes(10) });
        context.Response.Cookies.Append("refresh_token", refreshToken, cookieOptions with { Expires = DateTimeOffset.UtcNow.AddMinutes(30) });
    }

    // 获取当前有效的Access Token
    public static string GetValidAccessToken(this HttpContext context)
    {
        return context.Items.TryGetValue(AccessTokenCacheKey, out var cachedToken) 
            ? cachedToken.ToString() 
            : context.Request.Cookies["access_token"];
    }

    // 获取当前有效的Refresh Token
    public static string GetValidRefreshToken(this HttpContext context)
    {
        return context.Items.TryGetValue(RefreshTokenCacheKey, out var cachedToken) 
            ? cachedToken.ToString() 
            : context.Request.Cookies["refresh_token"];
    }
}

2. 修改基础控制器的令牌刷新逻辑

在调用API前,用扩展方法读取令牌,刷新后立即存入HttpContext.Items,确保当前请求后续流程能拿到新令牌:

public class BaseController : Controller
{
    private readonly IHttpClientFactory _httpClientFactory;

    public BaseController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    protected async Task<HttpClient> GetAuthenticatedClientAsync()
    {
        var client = _httpClientFactory.CreateClient("ApiClient");
        var accessToken = HttpContext.GetValidAccessToken();

        // 检查Access Token是否失效(示例:解析JWT验证过期时间)
        if (string.IsNullOrEmpty(accessToken) || IsTokenExpired(accessToken))
        {
            var refreshToken = HttpContext.GetValidRefreshToken();
            if (string.IsNullOrEmpty(refreshToken) || IsRefreshTokenExpired(refreshToken))
            {
                // Refresh Token无效,跳转登录
                RedirectToAction("Login", "Account");
                return null;
            }

            // 调用API刷新令牌
            var refreshResponse = await client.PostAsJsonAsync("/api/auth/refresh", new { RefreshToken = refreshToken });
            if (!refreshResponse.IsSuccessStatusCode)
            {
                RedirectToAction("Login", "Account");
                return null;
            }

            var newTokens = await refreshResponse.Content.ReadFromJsonAsync<TokenResponse>();
            // 更新当前请求的令牌缓存和Response Cookie
            HttpContext.UpdateTokens(newTokens.AccessToken, newTokens.RefreshToken);
            accessToken = newTokens.AccessToken;
        }

        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        return client;
    }

    // 辅助方法:验证JWT令牌是否过期(需引入System.IdentityModel.Tokens.Jwt)
    private bool IsTokenExpired(string token)
    {
        var handler = new JwtSecurityTokenHandler();
        if (!handler.CanReadToken(token)) return true;
        
        var jwtToken = handler.ReadJwtToken(token);
        return jwtToken.ValidTo < DateTime.UtcNow;
    }

    private bool IsRefreshTokenExpired(string refreshToken)
    {
        // 根据业务逻辑验证Refresh Token过期时间(比如从数据库查询)
        return false;
    }
}

// 令牌响应模型
public class TokenResponse
{
    public string AccessToken { get; set; }
    public string RefreshToken { get; set; }
}

3. 调整自定义授权属性逻辑

确保授权属性允许Refresh Token有效但Access Token失效的请求通过,因为控制器会在后续流程中完成令牌刷新:

public class CustomAuthorizeAttribute : Attribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var accessToken = context.HttpContext.GetValidAccessToken();
        var refreshToken = context.HttpContext.GetValidRefreshToken();

        // Access Token有效:直接通过
        if (!string.IsNullOrEmpty(accessToken) && !IsTokenExpired(accessToken))
        {
            return;
        }

        // Refresh Token有效:允许进入控制器处理刷新
        if (!string.IsNullOrEmpty(refreshToken) && !IsRefreshTokenExpired(refreshToken))
        {
            return;
        }

        // 两者都无效:跳转登录
        context.Result = new RedirectToActionResult("Login", "Account", null);
    }

    private bool IsTokenExpired(string token)
    {
        // 复用基础控制器的验证逻辑,或提取到公共工具类
        var handler = new JwtSecurityTokenHandler();
        if (!handler.CanReadToken(token)) return true;
        
        var jwtToken = handler.ReadJwtToken(token);
        return jwtToken.ValidTo < DateTime.UtcNow;
    }

    private bool IsRefreshTokenExpired(string refreshToken)
    {
        // 业务逻辑验证
        return false;
    }
}

关键说明

  • HttpContext.Items是当前请求的内存存储,请求结束后自动释放,不会跨请求共享
  • 令牌刷新完成后,当前请求的所有后续代码(包括HttpClient调用)都会读取到新令牌
  • Response.Cookie的更新会在浏览器收到响应后生效,保证下一次请求能直接读取新令牌

内容的提问来源于stack exchange,提问作者Srinidhi S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 22:27:10