问题:
我正在尝试使用非对称RSA算法生成和验证JWT。
我可以使用这个演示代码轻松生成JWT。
AddJwtBearer()
失败,但手动验证令牌有效。我正在尝试使用非对称RSA算法生成和验证JWT。
我可以使用这个演示代码轻松生成JWT。
[HttpPost("[action]")]
[Authorize]
[ValidateAntiForgeryToken]
public async Task<IActionResult> JwtBearerToken() {
AppUser user = await userManager.GetUserAsync(User);
using RSA rsa = RSA.Create(1024 * 2);
rsa.ImportRSAPrivateKey(Convert.FromBase64String(configuration["jwt:privateKey"]), out int _);
var signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256);
var jwt = new JwtSecurityToken(
audience: "identityapp",
issuer: "identityapp",
claims: new List<Claim>() {new Claim(ClaimTypes.NameIdentifier, user.UserName)},
notBefore: DateTime.Now,
expires: DateTime.Now.AddHours(3),
signingCredentials: signingCredentials
);
string token = new JwtSecurityTokenHandler().WriteToken(jwt);
return RedirectToAction(nameof(Index), new {jwt = token});
}
我也可以使用以下演示代码验证令牌及其签名。
[HttpPost("[action]")]
[ValidateAntiForgeryToken]
public IActionResult JwtBearerTokenVerify(string token) {
using RSA rsa = RSA.Create();
rsa.ImportRSAPrivateKey(Convert.FromBase64String(configuration["jwt:privateKey"]), out int _);
var handler = new JwtSecurityTokenHandler();
ClaimsPrincipal principal = handler.ValidateToken(token, new TokenValidationParameters() {
IssuerSigningKey = new RsaSecurityKey(rsa),
ValidAudience = "identityapp",
ValidIssuer = "identityapp",
RequireExpirationTime = true,
RequireAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidateAudience = true,
}, out SecurityToken securityToken);
return RedirectToAction(nameof(Index));
}
但是,当访问一个由以下代码保护的端点时,验证失败(401):
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
HTTP头部返回的错误信息:Bearer error="invalid_token", error_description="The signature is invalid"
我的JWT bearer身份验证配置在这里
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => {
using var rsa = RSA.Create();
rsa.ImportRSAPrivateKey(Convert.FromBase64String(Configuration["jwt:privateKey"]), out int _);
options.IncludeErrorDetails = true;
options.TokenValidationParameters = new TokenValidationParameters() {
IssuerSigningKey = new RsaSecurityKey(rsa),
ValidAudience = "identityapp",
ValidIssuer = "identityapp",
RequireExpirationTime = true,
RequireAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidateAudience = true,
};
});
我可以轻松使用对称密钥和HmacSha256让它起作用 - 但这不是我想要的。
更新
我已经将异常写入响应中,这就是我得到的结果:
IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.RsaSecurityKey, KeyId: '', InternalId: '79b1afb2-0c85-43a1-bb81-e2accf9dff38'. , KeyId:
'.
Exceptions caught:
'System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'RSA'.
at System.Security.Cryptography.RSAImplementation.RSACng.ThrowIfDisposed()
at System.Security.Cryptography.RSAImplementation.RSACng.GetDuplicatedKeyHandle()
at System.Security.Cryptography.RSAImplementation.RSACng.VerifyHash(ReadOnlySpan`1 hash, ReadOnlySpan`1 signature, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding)
at System.Security.Cryptography.RSAImplementation.RSACng.VerifyHash(Byte[] hash, Byte[] signature, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding)
at Microsoft.IdentityModel.Tokens.AsymmetricAdapter.VerifyWithRsa(Byte[] bytes, Byte[] signature)
at Microsoft.IdentityModel.Tokens.AsymmetricAdapter.Verify(Byte[] bytes, Byte[] signature)
at Microsoft.IdentityModel.Tokens.AsymmetricSignatureProvider.Verify(Byte[] input, Byte[] signature)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(Byte[] encodedBytes, Byte[] signature, SecurityKey key, String algorithm, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)
'.
token: '{"alg":"RS256","typ":"JWT"}.{"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier":"mail@mail.com","nbf":1582878368,"exp":1582889168,"iss":"identityapp","aud":"identityapp"}'.
更新 - 解决方案
所以,我猜我从异常消息中找到了解决方法。RSA安全密钥被过早地销毁了。
我将密钥创建从AddJwtBearer()
中提取出来,并改用依赖注入。
这似乎完全可以工作。但我不确定这是否是好的做法。
// Somewhere futher up in the ConfigureServices(IServiceCollection services) method
services.AddTransient<RsaSecurityKey>(provider => {
RSA rsa = RSA.Create();
rsa.ImportRSAPrivateKey(
source: Convert.FromBase64String(Configuration["jwt:privateKey"]),
bytesRead: out int _);
return new RsaSecurityKey(rsa);
});
// Chaining onto services.AddAuthentication()
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => {
SecurityKey rsa = services.BuildServiceProvider().GetRequiredService<RsaSecurityKey>();
options.IncludeErrorDetails = true;
options.TokenValidationParameters = new TokenValidationParameters() {
IssuerSigningKey = rsa,
ValidAudience = "identityapp",
ValidIssuer = "identityapp",
RequireExpirationTime = true,
RequireAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidateAudience = true,
};
});