You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core 3.1升级至.NET 8后JWT令牌Kid验证失败求助

.NET Core 3.1升级至.NET 8后JWT Bearer令牌验证失败问题

近期将项目从.NET Core 3.1升级至.NET 8后,JWT Bearer令牌的验证环节出现失败,具体报错发生在令牌刷新(SlideToken)流程中的验证步骤。

原正常运行代码

Startup认证配置

var appSettings = Configuration.GetSection("AuthSettings").Get<AuthSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.S);

services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(x =>
            {
                x.RequireHttpsMetadata = false;
                x.SaveToken = true;
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ValidateLifetime = true
                };
            });

TokenHandler实现

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using Acme.Services.Api.Code.Settings;
using Acme.Services.Core;
using Acme.Services.Core.Repository;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;

namespace Acme.Services.Api.Controllers
{
    public interface ITokenHandler
    {
        string GenerateToken(string[] roles, int userId, int daysOffset);
        string SlideToken(string replace, out DateTime expires);
    }

    public class TokenHandler : ITokenHandler
    {
        private readonly ISettingsRepository _settingsRepository;
        private readonly AuthSettings _authSettings;

        public TokenHandler(IOptions<AuthSettings> authSettings, ISettingsRepository settingsRepository)
        {
            _settingsRepository = settingsRepository;
            _authSettings = authSettings.Value;
        }

        public string GenerateToken(string[] roles, int userId, int daysOffset)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_authSettings.S);
                    
            var claims = roles.Select(x => new Claim(ClaimTypes.Role, x)).ToList();
            claims.Add(new Claim(ClaimTypes.Name, userId.ToString()));
                    
            var tokenDescriptor = new SecurityTokenDescriptor
                    {
                        Subject = new ClaimsIdentity(claims),
                        Expires =  GetExpiryDateMinutes().AddDays(daysOffset),
                        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
                    };

            var token = tokenHandler.CreateToken(tokenDescriptor);

            return tokenHandler.WriteToken(token);
        }
                
        public string SlideToken(string tokenValue, out DateTime expires)
        {
            var key = Encoding.ASCII.GetBytes(_authSettings.S);

            var handler = new JwtSecurityTokenHandler();
            var validations = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(key),
                        ValidateIssuer = false,
                        ValidateAudience = false,
                        ValidateLifetime = false
                    };
                            
            ClaimsPrincipal claims = handler.ValidateToken(tokenValue, validations, out var tokenSecure);

            expires = GetExpiryDateMinutes();
                    
            var tokenDescriptor = new SecurityTokenDescriptor
                    {
                        Subject = new ClaimsIdentity(claims.Claims),
                        Expires = expires,
                        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
                    };

            var token = handler.CreateToken(tokenDescriptor);

            return handler.WriteToken(token);
        }
                
        private DateTime GetExpiryDateMinutes()
        {
            var date =  DateTime.Now.AddMinutes(int.Parse(_settingsRepository.GetByNameAllowNull("TokenExpiryInMinutes")?.Value ??  "30"));
            return date;
        }
    }
}

错误信息

Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: IDX10517: Signature validation failed. The token's kid is missing. Keys tried: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey, KeyId: '', InternalId: 'CPMLsTVgBeiAqC-MBrp6XPSJEfX2rM9anuEqZHauImw'. , KeyId: '. Number of keys in TokenValidationParameters: '1'.
Number of keys in Configuration: '0'.

Exceptions caught:
'[PII of type 'System.String' 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.

at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignatureAndIssuerSecurityKey(String token, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateJWS(String token, TokenValidationParameters validationParameters, BaseConfiguration currentConfiguration, SecurityToken& signatureValidatedToken, ExceptionDispatchInfo& exceptionThrown)
--- End of stack trace from previous location ---
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, JwtSecurityToken outerToken, TokenValidationParameters validationParameters, SecurityToken& signatureValidatedToken)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
at Acme.Services.Api.Controllers.TokenHandler.SlideToken(String tokenValue, DateTime& expires) in /Users/mattowens/dev/app-Acme-api-portal/api/Acme.Services.Api/Controllers/TokenHandler.cs:line 66
at Acme.Services.Api.SlidingSessionMiddleware.<>c__DisplayClass4_0.b__0() in /Users/mattowens/dev/app-Acme-api-portal/api/Acme.Services.Api/SlidingSessionMiddleware.cs:line 30
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.g__ProcessEvents|241_0(HttpProtocol protocol, Stack`1 events)

项目依赖包

<ItemGroup>
    <PackageReference Include="AspNetCore.RouteAnalyzer" Version="0.5.3" />
    <PackageReference Include="AutoMapper" Version="13.0.1" />
    <PackageReference Include="Check.Password.Strength" Version="1.0.3" />
    <PackageReference Include="Edi.RouteDebugger" Version="1.9.0" />
    <PackageReference Include="Ical.Net" Version="4.3.1" />
    <PackageReference Include="Karambolo.Extensions.Logging.File" Version="3.6.0" />
    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.11" />
    <PackageReference Include="OfficeOpenXml.Core.ExcelPackage" Version="1.0.0" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
    <PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.2" />
    <PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="7.2.0" />
    <PackageReference Include="System.Data.SqlClient" Version="4.9.0" />
    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.11" />
    <PackageReference Include="PartialResponse.AspNetCore.Mvc.Formatters.Json" Version="5.1.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.0" />
    <PackageReference Include="ValueInjecter" Version="3.2.0" />
</ItemGroup>

错误发生代码行

ClaimsPrincipal claims = handler.ValidateToken(tokenValue, validations, out var tokenSecure);

问题原因与解决方案

问题原因

升级到.NET 8后,使用的System.IdentityModel.Tokens.Jwt 8.x版本对JWT令牌的验证逻辑更严格:默认要求令牌的头部包含kid(密钥ID)字段,而原代码生成的令牌未设置kid,同时验证时仅配置了单个IssuerSigningKey,导致验证过程中无法匹配密钥,触发IDX10517错误。

解决方案

1. 生成令牌时为签名密钥指定固定KeyId

修改令牌生成逻辑,给对称密钥添加固定的KeyId,确保生成的令牌头部包含kid字段:

修改后的GenerateToken方法片段:

public string GenerateToken(string[] roles, int userId, int daysOffset)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes(_authSettings.S);
    var securityKey = new SymmetricSecurityKey(key);
    // 基于密钥生成唯一KeyId,确保全局一致
    securityKey.KeyId = Convert.ToBase64String(System.Security.Cryptography.MD5.Create().ComputeHash(key));
            
    var claims = roles.Select(x => new Claim(ClaimTypes.Role, x)).ToList();
    claims.Add(new Claim(ClaimTypes.Name, userId.ToString()));
            
    var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(claims),
                Expires =  GetExpiryDateMinutes().AddDays(daysOffset),
                SigningCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature)
            };

    var token = tokenHandler.CreateToken(tokenDescriptor);
    return tokenHandler.WriteToken(token);
}

2. 验证时配置IssuerSigningKeys集合

将原代码中单个IssuerSigningKey替换为IssuerSigningKeys集合,包含带KeyId的密钥:

修改Startup中的认证配置
services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(x =>
            {
                x.RequireHttpsMetadata = false;
                x.SaveToken = true;
                var key = Encoding.ASCII.GetBytes(appSettings.S);
                var securityKey = new SymmetricSecurityKey(key);
                securityKey.KeyId = Convert.ToBase64String(System.Security.Cryptography.MD5.Create().ComputeHash(key));
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    // 改用IssuerSigningKeys集合
                    IssuerSigningKeys = new List<SecurityKey> { securityKey },
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ValidateLifetime = true
                };
            });
修改SlideToken中的验证参数
public string SlideToken(string tokenValue, out DateTime expires)
{
    var key = Encoding.ASCII.GetBytes(_authSettings.S);
    var securityKey = new SymmetricSecurityKey(key);
    securityKey.KeyId = Convert.ToBase64String(System.Security.Cryptography.MD5.Create().ComputeHash(key));

    var handler = new JwtSecurityTokenHandler();
    var validations = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                // 改用IssuerSigningKeys集合
                IssuerSigningKeys = new List<SecurityKey> { securityKey },
                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateLifetime = false
            };
                    
    ClaimsPrincipal claims = handler.ValidateToken(tokenValue, validations, out var tokenSecure);

    expires = GetExpiryDateMinutes();
            
    var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(claims.Claims),
                Expires = expires,
                SigningCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature)
            };

    var token = handler.CreateToken(tokenDescriptor);
    return handler.WriteToken(token);
}

3. 可选:临时关闭kid验证(不推荐)

如果暂时无法修改令牌生成逻辑,可通过设置TokenValidationParameters的ValidateIssuerSigningKey为false跳过密钥验证,但这会降低安全性,仅建议作为临时方案:

var validations = new TokenValidationParameters
{
    ValidateIssuerSigningKey = false, // 关闭密钥签名验证
    ValidateIssuer = false,
    ValidateAudience = false,
    ValidateLifetime = false
};

内容的提问来源于stack exchange,提问作者KnowHoper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 12:00:56