JWT签名验证失败
我在使用Blazor Server项目,从API收到一个JWT令牌。现在我已经成功将令牌存储在浏览器Cookie中。当我读取它时,我想再次验证它,这样在重新加载页面后仍然处于登录状态。我在验证令牌时遇到了困难。异常是:
Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: "IDX10517: 签名验证失败。令牌的kid缺失。尝试的密钥: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey, KeyId: '', InternalId: 'BjEm3ItoaZ802MANkueENLnh-KKOsGYUH3FEypvOrDk'. , KeyId:
'. TokenValidationParameters的密钥数量: '1'.
配置中的密钥数量: '0'.
捕获的异常:
'[PII of type 'System.Text.StringBuilder' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
token: '[PII of type 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. See https://aka.ms/IDX10503 for details."
验证端(客户端)看起来是这样的:
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.IdentityModel.Tokens;
using Microsoft.JSInterop;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
namespace bla
{
public class CustomAuthStateProvider : AuthenticationStateProvider
{
private readonly IJSRuntime _js;
private readonly AuthenticationState _anonymous;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly SymmetricSecurityKey _signingKey;
private readonly ILogger<CustomAuthStateProvider> _logger;
private readonly IConfiguration _configuration;
public CustomAuthStateProvider(IJSRuntime js, IHttpContextAccessor httpContextAccessor, SymmetricSecurityKey signingKey, ILogger<CustomAuthStateProvider> logger, IConfiguration configuration)
{
_js = js;
_anonymous = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
_httpContextAccessor = httpContextAccessor;
_signingKey = signingKey;
_logger = logger;
_configuration = configuration;
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
string? token = _httpContextAccessor.HttpContext?.Request.Cookies["jwt_token"];
if (string.IsNullOrWhiteSpace(token))
{
return Task.FromResult(_anonymous);
}
return Task.FromResult(CreateAuthState(token));
}
public void ChangeUser(string userName, Guid userId, string firstName, string lastName)
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
new Claim(ClaimTypes.GivenName, firstName),
new Claim(ClaimTypes.Surname, lastName)
}, "jwt");
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(new ClaimsPrincipal(identity))));
}
internal async Task Logout()
{
try
{
await _js.InvokeVoidAsync("AuthLogout");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to call AuthLogout via JS interop.");
}
NotifyAuthenticationStateChanged(Task.FromResult(_anonymous));
}
private AuthenticationState CreateAuthState(string token)
{
try
{
IConfiguration? jwtSettings = _configuration.GetSection("JwtSettings");
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(1)
};
ClaimsPrincipal principal = handler.ValidateToken(token, validationParameters, out _);
return new AuthenticationState(principal);
}
catch (Exception)
{
return _anonymous;
}
}
}
}
Login.razor:
try
{
LoginResult? result = await IdentityApi.LoginAsync(loginRequest);
if (result != null && !string.IsNullOrEmpty(result.Token))
{
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
JwtSecurityToken jwt = handler.ReadJwtToken(result.Token);
string? userId = jwt.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.NameId)?.Value;
Guid parsedUserId = Guid.TryParse(userId, out Guid id) ? id : Guid.Empty;
string? lastName = jwt.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.FamilyName)?.Value ?? string.Empty;
string? userName = jwt.Claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.PreferredUsername)?.Value ?? string.Empty;
((CustomAuthStateProvider)AuthStateProvider).ChangeUser(userName, parsedUserId, string.Empty, lastName);
// To Program.cs
Navigation.NavigateTo($"/auth/set-cookie?token={Uri.EscapeDataString(result.Token)}", forceLoad: true);
用于发送令牌的API端点:
[AllowAnonymous]
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
if (request is null)
{
return BadRequest("Request is null");
}
User? foundUser;
try
{
foundUser = await UserService.ReadUsersForLogin(request.UserName, request.Password);
}
catch (ArgumentException ex)
{
return BadRequest(ex.Message);
}
catch (FileNotFoundException ex2)
{
return Problem(ex2.Message);
}
catch (InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
if (foundUser is null)
return Unauthorized("Invalid credentials");
IConfiguration? jwtSettings = _configuration.GetSection("JwtSettings");
SymmetricSecurityKey? key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Key"]!));
SigningCredentials? creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
Claim[] claims = new[]
{
new Claim(JwtRegisteredClaimNames.NameId, foundUser.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Gender, foundUser.Gender.ToString()),
new Claim(JwtRegisteredClaimNames.GivenName,foundUser.FirstName),
new Claim(JwtRegisteredClaimNames.FamilyName, foundUser.LastName),
new Claim(JwtRegisteredClaimNames.PreferredUsername, foundUser.UserName),
new Claim(JwtRegisteredClaimNames.AuthTime, DateTime.Now.ToString()),
new Claim(JwtRegisteredClaimNames.Birthdate, foundUser.DateOfBirth?.ToString() ?? "")
};
JwtSecurityToken token = new JwtSecurityToken(
issuer: jwtSettings["Issuer"],
audience: jwtSettings["Audience"],
claims: claims,
expires: DateTime.UtcNow.AddHours(1),
signingCredentials: creds);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expires = token.ValidTo
});
}
解决方案
我可以自己回答这个问题。昨天因为各种变更和测试,我有点糊涂。
我一直在Program.cs中读取和处理密钥,但那里有个错误。现在我在 CreateAuthState 的 CustomAuthStateProvider 中读取密钥。
private AuthenticationState CreateAuthState(string token)
{
try
{
IConfiguration? jwtSettings = _configuration.GetSection("JwtSettings");
var secretKey = jwtSettings["Key"] ?? throw new InvalidOperationException("JwtSettings:Key fehlt!");
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = securityKey,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(1)
};
ClaimsPrincipal principal = handler.ValidateToken(token, validationParameters, out _);
return new AuthenticationState(principal);
}
catch (Exception)
{
return _anonymous;
}
}