JWT签名验证失败

前端开发 2026-07-07

我在使用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中读取和处理密钥,但那里有个错误。现在我在 CreateAuthStateCustomAuthStateProvider 中读取密钥。

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;
    }
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章