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

.NET 6升级至.NET 8后JWT令牌创建与解析报错求助

.NET 8 JWT处理的破坏性变更及问题解决

问题描述

将ASP.NET Core 6 Web API升级到.NET 8后,JWT令牌创建与解析先后出现两个错误:

  1. 创建令牌时触发错误:

System.ArgumentOutOfRangeException: 'IDX10720: Unable to create KeyedHashAlgorithm for algorithm 'HS256', the key size must be greater than: '256' bits, key has '152' bits. (Parameter 'keyBytes')'
加长密钥后令牌可正常生成,但解析时又出现新错误:
System.ArgumentException: 'IDX12723: Unable to decode the payload '[PII of type 'System.String' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]' as Base64Url encoded string.'
内部异常信息:
JsonException: IDX11020: The JSON value of type: 'String', could not be converted to 'JsonTokenType.Number'. Reading: 'System.IdentityModel.Tokens.Jwt.JwtPayload.iat', Position: '52', CurrentDepth: '1', BytesConsumed: '75'.

相关代码

解析令牌的私有方法

private User? _GetIdentity(HttpContext context, string token)
{
     try
     {
         var handler = new JwtSecurityTokenHandler();
         //var jsonToken = handler.ReadToken(token);
         var tokenS = handler.ReadToken(token) as JwtSecurityToken;//error happens here
         
         var user = new User
         {
             Id = tokenS.Claims.First(claim => claim.Type == ClaimTypes.NameIdentifier).Value,
             UserName = tokenS.Claims.First(claim => claim.Type == ClaimTypes.Name).Value,
             Email = tokenS.Claims.First(claim => claim.Type == ClaimTypes.Email).Value,
             Role = tokenS.Claims.First(claim => claim.Type == ClaimTypes.Role).Value
         };

         if (tokenS.ValidTo < DateTime.UtcNow)
         {
             throw new Exceptions.UnauthorizedAccessException($"...");
         }

         return user;
     }
     catch(Exceptions.UnauthorizedAccessException e)
     {
        _logger.LogError(e.Message);
         throw;
     }
}

JWT令牌生成类

public class JwtService
{
    private const int EXPIRATION_TOKEN_MINUTES = 25;
    private const int EXPIRATION_REFRESH_DAYS = 7;
    private readonly IConfiguration _configuration;

    public JwtService(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public AuthenticationResponse CreateToken(IdentityUser user, IEnumerable<string> roles)
    {
        var expiration = DateTime.Now.AddMinutes(EXPIRATION_TOKEN_MINUTES);
        var refreshExpiration = DateTime.Now.AddDays(EXPIRATION_REFRESH_DAYS);
        var token = CreateJwtToken(
            CreateClaims(user, roles),
            CreateSigningCredentials(),
            expiration
            );
        var refreshToken = CreateJwtToken(
            CreateRefreshTokenClaims(user.Id),
            CreateSigningCredentials(),
            refreshExpiration
            );

        var tokenHandler = new JwtSecurityTokenHandler();

        return new AuthenticationResponse
        {
            Token = tokenHandler.WriteToken(token),
            RefreshToken = tokenHandler.WriteToken(refreshToken),
            Expiration = expiration
        };
    }

    private JwtSecurityToken CreateJwtToken(Claim[] claims, SigningCredentials credentials, DateTime expiration) =>
        new JwtSecurityToken(
            _configuration["Jwt:Issuer"],
            _configuration["Jwt:Audience"],
            claims,
            expires: expiration,
            signingCredentials: credentials
        );

    private Claim[] CreateRefreshTokenClaims(string userId) =>
        new[]
        {
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.ToString()),
            new Claim(ClaimTypes.NameIdentifier, userId)
        };

    private Claim[] CreateClaims(IdentityUser user, IEnumerable<string> roles) =>
         new[] {
            //new Claim(JwtRegisteredClaimNames.Sub, _configuration["Jwt:Subject"]),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString()),
            new Claim(ClaimTypes.NameIdentifier, user.Id),
            new Claim(ClaimTypes.Name, user.UserName),
            new Claim(ClaimTypes.Email, user.Email),

         }
         .Concat(roles.Select(r => new Claim(ClaimTypes.Role, r)).ToArray())
         .ToArray();

    private SigningCredentials CreateSigningCredentials() =>
        new SigningCredentials(
            new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(_configuration["Jwt:Secret"])
            ),
            SecurityAlgorithms.HmacSha256
        );

    public ClaimsIdentity CreateClaimsIdentity(string username, string userId, IEnumerable<string> roles)
    {
        List<Claim> claims = new List<Claim>
        {
            new Claim(ClaimTypes.Name, username),
            new Claim(ClaimTypes.NameIdentifier, userId)
        };

        claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));

        ClaimsIdentity identity = new ClaimsIdentity(
            claims,
            "Token",
            ClaimTypes.Name,
            ClaimTypes.Role
            );

        return identity;
    }

    public ClaimsPrincipal ValidateToken(string token)
    {
        try
        {
            var tokenHandler = new JwtSecurityTokenHandler();

            // Set the token validation parameters
            var validationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidAudience = _configuration["Jwt:Audience"],
                ValidIssuer = _configuration["Jwt:Issuer"],
                IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(_configuration["Jwt:Secret"])
                )
            };

            // Validate and parse the token
            var principal = tokenHandler.ValidateToken(token, validationParameters, out var _);

            return principal;
        }
        catch
        {
            // Token validation failed
            return null;
        }
    }
}

问题原因与解决方案

1. HS256密钥长度的强制校验

.NET 8对JWT签名算法的密钥长度要求做了严格化处理:

  • .NET 6中,HS256算法的密钥长度校验是宽松的,即使密钥不足256位(32字节)也能生成令牌;
  • .NET 8开始严格遵循JWT标准,HS256要求密钥至少256位,否则直接抛出IDX10720错误。
    你加长密钥的操作是正确的,无需额外调整。

2. 标准声明iat的类型校验增强

JWT标准明确规定iat(签发时间)必须是数字类型的Unix时间戳,但你的代码中错误地将其设置为字符串格式的DateTime:

// 错误写法
new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString())
  • .NET 6的解析逻辑对类型校验较宽松,能容忍字符串格式的iat;
  • .NET 8严格遵循标准,会校验iat的类型,字符串格式的iat会触发IDX11020类型转换错误。

修复代码:将CreateClaims和CreateRefreshTokenClaims中的iat声明改为Unix时间戳(秒级):

// CreateClaims方法中修改iat声明
new Claim(JwtRegisteredClaimNames.Iat, ((DateTimeOffset)DateTime.UtcNow).ToUnixTimeSeconds().ToString())

// CreateRefreshTokenClaims方法中修改iat声明
new Claim(JwtRegisteredClaimNames.Iat, ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds().ToString())

注:这里最终仍是字符串,但内容为数字格式,符合JWT标准对iat的类型要求。

额外建议

建议使用JwtService.ValidateToken方法解析令牌,而非直接调用ReadToken。ValidateToken会自动处理标准声明的校验、类型转换及过期检查,比手动解析更安全规范。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 15:37:33