使用RefreshToken生成新AccessToken后ClaimTypes.NameIdentifier返回null的解决方法
问题解决思路及代码修改
问题根源在于刷新Access Token后,重新生成的ClaimsPrincipal仅包含了令牌相关的Claim,缺少用户ID对应的ClaimTypes.NameIdentifier,导致后续获取用户ID时返回null。以下是两种可行的解决方式:
方式一:从新生成的Access Token中解析用户ID
利用JWT解析工具从新的Access Token中提取用户ID,添加到Claims列表中:
- 确保项目已引入
System.IdentityModel.Tokens.Jwt包 - 修改
AuthorizedHttpClientHandler中刷新成功后的Claims构建逻辑:
if (newToken != null) { // 解析新的Access Token获取用户ID var jwtHandler = new JwtSecurityTokenHandler(); var jwtToken = jwtHandler.ReadJwtToken(newToken.AccessToken); var userId = jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value // 如果你的JWT用的是标准sub字段,替换成下面的判断 // ?? jwtToken.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Sub)?.Value; if (string.IsNullOrWhiteSpace(userId)) { // 处理用户ID为空的异常情况,比如登出用户 await _httpContextAccessor.HttpContext.SignOutAsync(JwtBearerDefaults.AuthenticationScheme); _httpContextAccessor.HttpContext.Response.Redirect("/Login/Index"); await _httpContextAccessor.HttpContext.Response.CompleteAsync(); return response; } var claims = new List<Claim> { new Claim("DirimOpsManagement", newToken.AccessToken), new Claim("RefreshToken", newToken.RefreshToken), new Claim(ClaimTypes.NameIdentifier, userId) // 添加用户ID Claim }; // 后续SignIn逻辑保持不变 var claimsIdentity = new ClaimsIdentity(claims, JwtBearerDefaults.AuthenticationScheme); var authProperties = new AuthenticationProperties { IsPersistent = true }; var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); await _httpContextAccessor.HttpContext.SignInAsync(JwtBearerDefaults.AuthenticationScheme, claimsPrincipal, authProperties); _httpContextAccessor.HttpContext.User = claimsPrincipal; request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", newToken.AccessToken); response = await base.SendAsync(request, cancellationToken); }
方式二:修改Refresh Token接口,直接返回用户ID
这种方式无需解析JWT,更高效,需要调整接口返回模型和逻辑:
- 更新
JwtResponseModel,添加用户ID字段:
public class JwtResponseModel { public string AccessToken { get; set; } public string RefreshToken { get; set; } public string UserId { get; set; } // 新增用户ID字段 // 其他已有字段... }
- 修改Refresh Token命令处理逻辑,返回用户ID:
public async Task<RefreshTokenResponse> Handle(UpdateRefreshTokenCommand request, CancellationToken cancellationToken) { // 原有逻辑保持不变... var user = await _userManager.FindByIdAsync(existingToken.UserID); if (user == null) { throw new Exception("User not found."); } var accessToken = _jwtTokenGenerator.GenerateToken(new JwtTokenRequest { Id = user.Id, Username = user.UserName, Email = user.Email }); existingToken.Token = _jwtTokenGenerator.GenerateRefreshToken(); await _unitOfWork.RefreshTokens.UpdateAsync(existingToken); await _unitOfWork.SaveChangesAsync(cancellationToken); // 返回时包含用户ID return new RefreshTokenResponse { AccessToken = accessToken.AccessToken, AccessTokenExpires = accessToken.Expires, RefreshToken = existingToken.Token, RefreshTokenExpires = existingToken.Expires, UserId = user.Id // 新增返回用户ID }; }
- 修改
AuthorizedHttpClientHandler中的逻辑,直接使用返回的用户ID:
if (newToken != null) { if (string.IsNullOrWhiteSpace(newToken.UserId)) { // 异常处理 await _httpContextAccessor.HttpContext.SignOutAsync(JwtBearerDefaults.AuthenticationScheme); _httpContextAccessor.HttpContext.Response.Redirect("/Login/Index"); await _httpContextAccessor.HttpContext.Response.CompleteAsync(); return response; } var claims = new List<Claim> { new Claim("DirimOpsManagement", newToken.AccessToken), new Claim("RefreshToken", newToken.RefreshToken), new Claim(ClaimTypes.NameIdentifier, newToken.UserId) // 添加用户ID Claim }; // 后续SignIn逻辑保持不变... }
注意事项
- 确保
ClaimTypes.NameIdentifier与你初始登录时生成用户ID Claim的类型一致,如果初始用的是JWT标准字段sub(即JwtRegisteredClaimNames.Sub),则需要对应调整解析或添加Claim时的类型。 - 添加异常处理逻辑,避免用户ID为空时出现空引用异常。
内容的提问来源于stack exchange,提问作者Gökmen Ada
相关产品推荐
相关产品推荐

