IDX10503:签名验证失败。令牌没有孩子。尝试过的键:'System.Text.StringBuilder'

IDX10503: Signature validation failed. Token does not have a kid. Keys tried: 'System.Text.StringBuilder'

我有以下 JWT 令牌,

eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjbGllbnRpZCIsImF1ZCI6ImNsaWVudGlkIiwic3ViIjoiMTIzIiwiYSI6IjQ1NiIsImlhdCI6MTYyMTc5OTU5OCwiZXhwIjoxNjIxNzk5NjU4fQ.hglbX63zhPwTOsB-zSiOMfxEKl5OaIk6zX1o9-LEhP3nro8fa5_3QyIH7I5971j-xuO1bccX1TOh0kNcQ-ACAg

这是使用生成的,

    public static string GenerateToken(string key, string a1, string a2)
    {
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
        var token = new JwtSecurityToken(
            claims: new Claim[]
            {
            new Claim(JwtRegisteredClaimNames.Iss, "clientid"),
            new Claim(JwtRegisteredClaimNames.Aud, "clientid"),
            new Claim(JwtRegisteredClaimNames.Sub, a1),
            new Claim("a", a2),
            new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
            },
            //notBefore: new DateTimeOffset(DateTime.Now).DateTime,
            expires: new DateTimeOffset(DateTime.Now.AddMinutes(1)).DateTime,
            signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha512)
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

var key = "Ym7AD3OT2kpuIRcVAXCweYhV64B0Oi9ETAO6XRbqB8LDL3tF4bMk9x/59PljcGbP5v38BSzCjD1VTwuO6iWA8uzDVAjw2fMNfcT2/LyRlMOsynblo3envlivtgHnKkZj6HqRrG5ltgwy5NsCQ7WwwYPkldhLTF+wUYAnq28+QnU=";
// Key is test                
var token = GenerateToken(key, "123", "456");

获得令牌后,我正在使用以下代码进行验证,

var key = "Ym7AD3OT2kpuIRcVAXCweYhV64B0Oi9ETAO6XRbqB8LDL3tF4bMk9x/59PljcGbP5v38BSzCjD1VTwuO6iWA8uzDVAjw2fMNfcT2/LyRlMOsynblo3envlivtgHnKkZj6HqRrG5ltgwy5NsCQ7WwwYPkldhLTF+wUYAnq28+QnU=";
// key is test

var hmac = new HMACSHA512(Convert.FromBase64String(key));
var validationParameters = new TokenValidationParameters
            {
                ValidAudience = "clientid",
                ValidIssuer = "clientid",
                IssuerSigningKey = new SymmetricSecurityKey(hmac.Key)
            };
            var tokenHandler = new JwtSecurityTokenHandler();
            return tokenHandler.ValidateToken(token, validationParameters, out var validToken);

但是我遇到了以下错误,

IDX10503: Signature validation failed. Token does not have a kid. Keys tried: 'System.Text.StringBuilder'.
Exceptions caught:
 'System.Text.StringBuilder'.
token: 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'.

问题出在这一行,

var hmac = new HMACSHA512(Convert.FromBase64String(key));

我改成了,

var hmac = new HMACSHA512(Encoding.UTF8.GetBytes(key));

该错误具有误导性。错误的源代码位于 https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/d6f2b66d788195b50f2b1f700beb497851194c73/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs#L1016

我收到相同的错误消息,但我的问题不同。此行有问题:

new Ed25519PublicKeyParameters(Encoding.UTF8.GetBytes(key), 0);

所以我改成这样:

new Ed25519PublicKeyParameters(Convert.FromBase64String(key), 0);

密钥以 base64 格式保存到文件中,但我将其加载为 UTF8 字节。

除了能够使用如上所示的 base64 编码键值外,您还可以使用字符串。不过有一些注意事项。

使用 SymmetricSecurityKey 创建 HMACSHA256 或 HMACSHA512 的处理程序不会执行 zero-bit 填充到哈希大小 (As per RFC2104, Step 1),这会导致签名算法失败。如果用作密钥的字符串大于密钥大小(256 或 512 位),则一切正常。

以下是为较短的签名字符串生成适当的 SymmetricSecurityKey 的两种方法:

第一种方法:手动用零字节填充字符串并生成 base64 密钥。像 Cyber​​Chef 这样的东西可以用来将 /0 字符附加到 256 位的 32 个字符或 512 位的 64 个字符:

Manually generating a base64 key via CyberChef

这些密钥将产生相同的 JWT

Equivalent keys in JWT.io

这可以像上面 Imran 演示的那样使用...

// Set up the signingKey for HS256
// Base64 signingKey
//SymmetricSecurityKey signingKey = new SymmetricSecurityKey(Convert.FromBase64String("c2VjcmV0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="));

第二种方法:不进行 base64 转换,SymmetricSecurityKey 将采用 ascii 字节[],只要您已经完成填充:

// Short String signingKey
// Note: Not advised. Short keys can be bruteforced, allowing tokens to be forged. 
// Note: manually padding to 256 bits if it is a short key, as the SymmetricSignatureProvider does not do the HMACSHA256 RFC2104 padding for you.
// SymmetricSecurityKey signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("secret".PadRight((256/8), '[=11=]')));
SymmetricSecurityKey signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("secret".PadRight((512/8), '[=11=]')));

无论采用哪种方法,您都可以在默认的 JwtBearer 处理程序中使用生成的 SymmetricSecurityKey:

builder.Services.AddAuthentication(options => {
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateAudience = true,
        ValidateIssuer = true,
        ValidIssuer = "https://localhost:7046/",
        ValidAudience = "https://localhost:7046/",
        RequireSignedTokens = true,
        IssuerSigningKey = signingKey,
        ValidateLifetime = true
    };
});

builder.Services.AddAuthorization(auth =>
            {
                auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
                        .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
                    .RequireAuthenticatedUser().Build());
            });

最后一点,我在一次添加 Microsoft.IdentityModel.Tokens 包的测试中遇到了一些问题。我仍然要 运行 这个,但是那个包中可能有冲突 class...

我这里有一个使用这种方法的示例:Example project in GitHub

值得注意的是,不建议为您的 HMAC 使用短对称密钥,因为秘密可以相对容易地被暴力破解,尤其是当它出现在已知密码列表中时。值得检查你的密钥过去 HaveIBeenPwned - PwnedPasswords to have some confidence of whether it has been used before. This is mainly provided for people creating demonstrations of short HMAC's. If you are uncomfortable with using PwnedPasswords, see the following about the K-anonymity 他们使用的模型,并在浏览器工具中观察你的网络选项卡时进行实验,看看它是如何工作的。