Blazor 8 InteractiveWebAssembly模式下JWT跨端同步更新问题
针对你遇到的客户端刷新令牌后,服务端Cookie未更新导致刷新令牌重复使用失败的问题,以下是两种可行的解决方案:
方案一:客户端刷新令牌后主动同步服务端Cookie
核心思路是客户端完成令牌刷新后,立即调用服务端API,将新令牌同步到服务端的认证Cookie中,确保两端令牌状态一致。
1. 服务端添加令牌同步API
在服务端创建一个API端点,接收客户端传来的新令牌,重新生成认证Cookie:
[ApiController] [Route("api/auth")] [Authorize] public class AuthSyncController : ControllerBase { private readonly IAuthenticationService _authService; private const string AuthScheme = IdentityConstants.ApplicationScheme; public AuthSyncController(IAuthenticationService authService) { _authService = authService; } [HttpPost("sync-tokens")] public async Task<IActionResult> SyncTokens([FromBody] TokenSyncModel model) { // 解析新访问令牌的用户Claims(确保令牌合法性) var jwtHandler = new JwtSecurityTokenHandler(); var jwtToken = jwtHandler.ReadJwtToken(model.AccessToken); var claims = jwtToken.Claims.ToList(); // 添加新令牌到Claims集合 claims.Add(new Claim("access_token", model.AccessToken)); claims.Add(new Claim("refresh_token", model.RefreshToken)); // 构建新的认证主体并更新Cookie var newIdentity = new ClaimsIdentity(claims, AuthScheme); var newPrincipal = new ClaimsPrincipal(newIdentity); await _authService.SignInAsync(HttpContext, AuthScheme, newPrincipal, new AuthenticationProperties { IsPersistent = true }); return Ok(); } } public class TokenSyncModel { public string AccessToken { get; set; } public string RefreshToken { get; set; } }
2. 客户端刷新令牌后调用同步API
修改客户端PersistentAuthenticationStateProvider的令牌刷新逻辑,成功获取新令牌后立即调用服务端同步接口:
public class ClientAuthStateProvider : PersistentAuthenticationStateProvider { private readonly HttpClient _httpClient; private readonly IAuthApiService _authApi; private Task<AuthenticationState> _authStateTask; public ClientAuthStateProvider(HttpClient httpClient, IAuthApiService authApi) { _httpClient = httpClient; _authApi = authApi; _authStateTask = base.GetAuthenticationStateAsync(); } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { var authState = await _authStateTask; var user = authState.User; if (user.Identity.IsAuthenticated && IsTokenExpired(user.FindFirst("access_token")?.Value)) { var oldRefreshToken = user.FindFirst("refresh_token")?.Value; // 调用外部API获取新令牌 var newTokens = await _authApi.RefreshTokenAsync(oldRefreshToken); // 构建新的认证主体 var jwtHandler = new JwtSecurityTokenHandler(); var jwtToken = jwtHandler.ReadJwtToken(newTokens.AccessToken); var claims = jwtToken.Claims.ToList(); claims.Add(new Claim("access_token", newTokens.AccessToken)); claims.Add(new Claim("refresh_token", newTokens.RefreshToken)); var newIdentity = new ClaimsIdentity(claims, IdentityConstants.ApplicationScheme); var newPrincipal = new ClaimsPrincipal(newIdentity); // 更新客户端本地认证状态 _authStateTask = Task.FromResult(new AuthenticationState(newPrincipal)); // 同步到服务端更新Cookie try { await _httpClient.PostAsJsonAsync("/api/auth/sync-tokens", new TokenSyncModel { AccessToken = newTokens.AccessToken, RefreshToken = newTokens.RefreshToken }); } catch { // 同步失败时强制登出,避免状态不一致 await SignOutAsync(); _authStateTask = Task.FromResult(new AuthenticationState(new ClaimsPrincipal())); } } return await _authStateTask; } private bool IsTokenExpired(string token) { // 实现JWT过期校验逻辑 if (string.IsNullOrEmpty(token)) return true; var jwtHandler = new JwtSecurityTokenHandler(); var jwtToken = jwtHandler.ReadJwtToken(token); return jwtToken.ValidTo <= DateTime.UtcNow; } private async Task SignOutAsync() { await _httpClient.PostAsync("/Account/Logout", null); } }
方案二:服务端刷新失败时触发状态重置
修改服务端PersistingServerAuthenticationStateProvider的刷新逻辑,当检测到刷新令牌无效(已被客户端使用)时,直接登出当前用户,强制客户端重新同步状态:
public class ServerAuthStateProvider : PersistingServerAuthenticationStateProvider { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IAuthenticationService _authService; private readonly IAuthApiService _authApi; public ServerAuthStateProvider(IHttpContextAccessor httpContextAccessor, IAuthenticationService authService, IAuthApiService authApi) { _httpContextAccessor = httpContextAccessor; _authService = authService; _authApi = authApi; } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { var authState = await base.GetAuthenticationStateAsync(); var user = authState.User; if (user.Identity.IsAuthenticated && IsTokenExpired(user.FindFirst("access_token")?.Value)) { var refreshToken = user.FindFirst("refresh_token")?.Value; try { var newTokens = await _authApi.RefreshTokenAsync(refreshToken); // 构建新认证主体并更新状态 var newPrincipal = BuildPrincipalFromTokens(newTokens); await _authService.SignInAsync(_httpContextAccessor.HttpContext, IdentityConstants.ApplicationScheme, newPrincipal); return new AuthenticationState(newPrincipal); } catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized) { // 刷新令牌无效,说明客户端已使用,直接登出 await _authService.SignOutAsync(_httpContextAccessor.HttpContext, IdentityConstants.ApplicationScheme); return new AuthenticationState(new ClaimsPrincipal()); } } return authState; } private ClaimsPrincipal BuildPrincipalFromTokens(TokenResponse tokens) { // 实现从令牌构建ClaimsPrincipal的逻辑 var jwtHandler = new JwtSecurityTokenHandler(); var jwtToken = jwtHandler.ReadJwtToken(tokens.AccessToken); var claims = jwtToken.Claims.ToList(); claims.Add(new Claim("access_token", tokens.AccessToken)); claims.Add(new Claim("refresh_token", tokens.RefreshToken)); return new ClaimsPrincipal(new ClaimsIdentity(claims, IdentityConstants.ApplicationScheme)); } private bool IsTokenExpired(string token) { // 同客户端的过期校验逻辑 if (string.IsNullOrEmpty(token)) return true; var jwtHandler = new JwtSecurityTokenHandler(); var jwtToken = jwtHandler.ReadJwtToken(token); return jwtToken.ValidTo <= DateTime.UtcNow; } }
关键注意事项
- 令牌合法性校验:服务端同步API需验证传入令牌的有效性,避免恶意请求篡改令牌。
- 异常处理:同步失败或刷新失败时,需强制登出用户,避免两端状态不一致引发更多问题。
- 认证Scheme统一:确保客户端和服务端使用相同的认证Scheme(如
IdentityConstants.ApplicationScheme)。
内容的提问来源于stack exchange,提问作者GLuca74
相关产品推荐
相关产品推荐

