将现有 API 项目迁移到 .NET 8 创建 JWT 令牌时出现错误

Qua*_*023 5 c# jwt asp.net-core-webapi .net-8.0

我刚刚将 ASP.NET Core 6 Web API 升级到 .NET 8(请注意,在 .NET 6 中一切都工作得很好),并且在创建 JWT 令牌时收到此错误:

System.ArgumentOutOfRangeException:'IDX10720:无法为算法'HS256'创建KeyedHashAlgorithm,密钥大小必须大于:'256'位,密钥具有'152'位。(参数'keyBytes')'

根据我对此错误的理解,似乎对秘密需要多长时间有一个新的要求,因此我使秘密更长,这适用于创建令牌,但在读取令牌时我收到一个全新的错误

System.ArgumentException:“IDX12723:无法解码有效负载”[“System.String”类型的 PII 已隐藏。有关更多详细信息,请参阅 https://aka.ms/IdentityModel/PII。]' 作为 Base64Url 编码字符串。

编辑:这是内部异常,也许这可以说明一些问题

JsonException:IDX11020:类型为“String”的 JSON 值无法转换为“JsonTokenType.Number”。读取:“System.IdentityModel.Tokens.Jwt.JwtPayload.iat”,位置:“52”,当前深度:“1”,消耗的字节数:“75”。

这里是否有一些处理 JWT 令牌的重大更改?

这是读取令牌的私有方法的代码

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;
     }
}
Run Code Online (Sandbox Code Playgroud)

以下是生成 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

小智 3

我有完全相同的问题。

JWT 字段 Iat 应该是一个数字(自 1970 年 1 月 1 日以来的秒数)

生成 JWT 时,只需替换:

  claims.Add(new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString())),
Run Code Online (Sandbox Code Playgroud)

和:

 long iat = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds();
 claims.Add(new Claim(JwtRegisteredClaimNames.Iat, iat.ToString()));
Run Code Online (Sandbox Code Playgroud)