【问题标题】:ASP.NET Core 3.1 JWT signature invalid when using AddJwtBearer()ASP.NET Core 3.1 JWT 签名在使用 AddJwtBearer() 时无效
【发布时间】:2022-01-07 23:21:45
【问题描述】:

问题: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));
}



但是,当访问受
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

保护的端点时,验证失败 (401)

来自 HTTP 标头的错误消息:Bearer error="invalid_token", error_description="签名无效"



我的 JWT 不记名身份验证配置在这里

.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,
    };

});

【问题讨论】:

    标签: c# asp.net-core jwt encryption-asymmetric jwt-auth


    【解决方案1】:

    虽然您的解决方案显然有效,但它有两个问题,我将提供解决方案。

    第一个问题是您创建的RSA 实现了IDisposable,但是由于RSA 不是工厂的直接结果,因此在生命周期(此处为瞬态)内未正确处理处置。这会导致资源泄漏,其中未处理的 RSA 实例可能会在您的主机运行期间累积(甚至超出“正式”关闭时间)。

    第二个问题是您对BuildServiceProvider 的使用会创建一个全新的服务提供者,而不是其余代码隐式使用的服务提供者。换句话说,这会创建一个与“规范”容器并行的新依赖注入容器。

    解决方法如下。 (注意我不能完美地测试你的场景,但我自己的应用程序中有类似的东西。)我将从中间的关键部分开始:

    services
    .AddTransient(provider => RSA.Create())
    .AddTransient<SecurityKey>(provider =>
    {
        RSA rsa = provider.GetRequiredService<RSA>();
        rsa.ImportRSAPrivateKey(source: Convert.FromBase64String(Configuration["jwt:privateKey"]), bytesRead: out int _);
        return new RsaSecurityKey(rsa);
    });
    

    注意RSA 如何拥有自己的工厂。所以它在正确的时间被处理。安全密钥也有自己的工厂,需要时会查找RSA

    在我刚刚展示的代码上方的某个地方,你会做这样的事情:

    services
    .AddOptions<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme)
    .Configure<SecurityKey>((options, signingKey) =>
    {
        options.IncludeErrorDetails = true;
        options.TokenValidationParameters = new TokenValidationParameters()
        {
            IssuerSigningKey = signingKey,
            ValidAudience = "identityapp",
            ValidIssuer = "identityapp",
            RequireExpirationTime = true,
            RequireAudience = true,
            ValidateIssuer = true,
            ValidateLifetime = true,
            ValidateAudience = true,
        };
    });
    
    services
        .AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
        .AddJwtBearer();
    

    注意TokenValidationParameters 是如何在Configure 方法中移动的! signingKey 将是您在依赖注入容器上注册的SecurityKey!因此我们摆脱了BuildServiceProvider

    警告:Microsoft 的 IdentityModel 似乎有一个错误,即在某些情况下,第二个 RSA 实例使用 RSA、处置它,然后使用另一个 RSA 会失败。例如,这是this SO question 背后的根本问题。您可能会独立于我的解决方案而遇到该问题。但是您可以通过使用AddSingleton 而不是AddTransient 添加RSA(不一定是安全密钥)来回避这个问题。

    【讨论】:

      猜你喜欢
      • 2019-10-30
      • 2018-06-23
      • 1970-01-01
      • 2016-12-29
      • 1970-01-01
      • 2021-03-07
      • 2021-12-07
      • 2020-01-21
      • 2018-01-24
      相关资源
      最近更新 更多