2025/2/19,今天同事反馈说登录不上,检查发现在调用接口的时候出现401错误,我们用的jwt实现

在本地调试出现了下面的错误

IDX10503: Signature validation failed. Token does not have a kid. Keys tried: '[PII of type 'System.Text.StringBuilder' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. Number of keys in TokenValidationParameters: '1'.
Number of keys in Configuration: '0'.

Exceptions caught:

'[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.
错误含义分析
  • Signature validation failed:表明 JWT 的签名验证没有通过,即无法证明令牌是由可信的签发者创建且内容未被篡改。
  • Token does not have a kidkid 是 JWT 头中的一个可选参数,用于指定所使用的密钥标识符。当 JWT 令牌中没有提供这个 kid 时,验证系统就很难确定应该使用哪一个密钥来验证签名。
  • Keys tried:系统会尝试使用一些可用的密钥来验证签名,但由于没有 kid 来明确指示使用哪个密钥,这种尝试可能会失败。


首先方向考虑的是因为更新了类库,因为最近并未对代码进行改动。

更新的类库主要是:

<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.1" />


解决过程

上面提到了kid,是不是新的类库强制使用了kid,但是查找了资料,猜测并不是,因为这是一个可选项

WT(JSON Web Token)中的kid,全称是Key Identifier(密钥标识符),它的主要作用就像给每个密钥贴上一个唯一的标签或者名字。这样,在验证JWT令牌的时候,服务器就能知道应该用哪个密钥去验证这个令牌的签名。

具体来说,kid在JWT的头部(Header)中声明,通常与服务器端配置的一组密钥对应。当服务器收到一个JWT令牌时,它会首先查看令牌头部的kid值,然后在自己的密钥库中找到与之对应的密钥。找到正确的密钥后,服务器就会用这个密钥去验证令牌的签名,以确保令牌是真实有效的,没有被篡改过。

验证过程大致如下:

1. 接收令牌:服务器接收到客户端发送的JWT令牌。

2. 解析头部:服务器解析JWT令牌的头部,获取kid的值。

3. 查找密钥:服务器根据kid的值,在自己的密钥库中查找对应的密钥。

4. 验证签名:服务器使用找到的密钥去验证JWT令牌的签名。如果签名验证成功,说明令牌是有效的;如果验证失败,说明令牌可能被篡改过或者使用了错误的密钥。

通过kid,服务器能够准确地找到用于验证签名的密钥,从而确保JWT令牌的安全性和有效性。


继续上面的错误,可看到 PII of type 'System.Text.StringBuilder' is hidden. For more details, see https://aka.ms/IdentityModel/PII.

意思是这里不显示PII(个人识别信息),通过设置,可打开

IdentityModelEventSource.ShowPII = true;

接着就看到了具体的错误信息:

System.ArgumentOutOfRangeException: IDX10720: Unable to create KeyedHashAlgorithm for algorithm 'HS256', the key size must be greater than: '256' bits, key has '128' bits.

到这里错误就明显了,我们使用的key是16个字符,折算一下就是128(16*8),而新的需要256位,也就是32个字符,猜测是旧的类库兼容了这种情况,而新的没有。

下面将密钥进行扩展,问题解决(注意,密钥实际没有变,也可以正常验证)

// 获取原始密钥(假设密钥为128位)
var rawKey = Encoding.UTF8.GetBytes("your-128-bit-secret");
// 强制转换为256位(通过填充或重复)
var paddedKey = new byte[32]; // 256位 
Array.Copy(rawKey, paddedKey, Math.Min(rawKey.Length, 32));
var securityKey = new SymmetricSecurityKey(paddedKey);


其他代码参考

// 生成 256 位随机密钥 
var keyBytes = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(keyBytes);
var key = new SymmetricSecurityKey(keyBytes);

// 使用 RandomNumberGenerator 生成符合要求的密钥
var keyBytes = new byte[32]; // 256 位 
RandomNumberGenerator.Fill(keyBytes);
var securityKey = new SymmetricSecurityKey(keyBytes);