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
相关产品推荐
相关产品推荐

