首页 > 解决方案 > 如何在 dotnet core 中验证非对称签名的 JWT?

问题描述

我找到了 .NET FW 中的非对称签名示例和 .NET Core 中的对称签名示例,但我无法弄清楚如何在 .NET Core 中非对称地验证 JWT。给定 JWK 集的 URL 或给定公钥,如何在 .NET Core 中验证令牌?

标签: asp.net-core.net-corejwtrsa

解决方案


A Symmetric Signing 和 Symmetric Signing 之间的唯一区别是签名密钥。只需为令牌验证参数构造一个新的非对称安全密钥即可。

假设您想使用 RSA 算法。让我们使用 powershell 导出一对 RSA 密钥,如下所示:

$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider -ArgumentList 2048

$rsa.ToXmlString($true) | Out-File key.private.xml
$rsa.ToXmlString($false) | Out-File key.public.xml

现在我们将使用这两个密钥对令牌进行签名。

一点修补

由于rsa.FromXmlString()api 是 .NET Core 支持的,所以我只是复制@myloveCc 的代码在 C#中构造一个RsaParameters(这项工作通过以下ParseXmlString()方法完成):

public static class KeyHelper 
{
    public static RSAParameters ParseXmlString( string xml){
        RSAParameters parameters = new RSAParameters();

        System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();
        xmlDoc.LoadXml(xml);

        if (xmlDoc.DocumentElement.Name.Equals("RSAKeyValue"))
        {
            foreach (System.Xml.XmlNode node in xmlDoc.DocumentElement.ChildNodes)
            {
                switch (node.Name)
                {
                    case "Modulus": parameters.Modulus = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    case "Exponent": parameters.Exponent = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    case "P": parameters.P = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    case "Q": parameters.Q = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    case "DP": parameters.DP = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    case "DQ": parameters.DQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    case "InverseQ": parameters.InverseQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                    case "D": parameters.D = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
                }
            }
        }
        else
        {
            throw new Exception("Invalid XML RSA key.");
        }
        return parameters;
    }


    public static RsaSecurityKey BuildRsaSigningKey(string xml){ 
        var parameters = ParseXmlString(xml);
        var rsaProvider = new RSACryptoServiceProvider(2048);
        rsaProvider.ImportParameters(parameters);
        var key = new RsaSecurityKey(rsaProvider);   
        return key;
    }  
}

这里我添加了一个BuildRsaSigningKey()辅助方法来生成一个SecurityKey.

代币生成

这是一个使用 RSA 生成令牌的演示:


public string GenerateToken(DateTime expiry)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var Identity = new ClaimsIdentity(new[]
    {
        new Claim(ClaimTypes.Name,          "..."),
        // ... other claims
   });

    var xml = "<RSAKeyValue> load...from..local...files...</RSAKeyValue>";
    SecurityKey key =  KeyHelper.BuildRsaSigningKey(xml); 

    var Token = new JwtSecurityToken
    (
        issuer: "test",
        audience: "test-app",
        claims: Identity.Claims,
        notBefore: DateTime.UtcNow,
        expires: expiry,
        signingCredentials: new SigningCredentials(key, SecurityAlgorithms.RsaSha256Signature, SecurityAlgorithms.Sha256Digest)
    );
    var TokenString = tokenHandler.WriteToken(Token);
    return TokenString;
}

令牌验证

要自动验证它,请配置 JWT Bearer 身份验证,如下所示:

Services.AddAuthentication(A =>
{
    A.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    A.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(O =>
{
    var xml = "<RSAKeyValue> load...from..local...files...</RSAKeyValue>";
    var key = KeyHelper.BuildRsaSigningKey(xml);

    O.RequireHttpsMetadata = false;
    O.SaveToken = true;
    O.IncludeErrorDetails = true;
    O.TokenValidationParameters = new TokenValidationParameters
    {
        IssuerSigningKey = key,
        ValidateIssuerSigningKey = true,
        ValidateLifetime = true,   
        // ... other settings
    };
});

如果您想手动验证它:

public IActionResult ValidateTokenManually(string jwt)
{
    var xml = "<RSAKeyValue>... the keys ...</RSAKeyValue>";
    SecurityKey key = KeyHelper.BuildRsaSigningKey(xml);    

    var validationParameters = new TokenValidationParameters
    {
        IssuerSigningKey = key,
        RequireSignedTokens = true,
        RequireExpirationTime = true,
        ValidateLifetime = true,
        // ... other settings
    };

    var tokenHandler = new JwtSecurityTokenHandler();
    var principal = tokenHandler.ValidateToken(jwt, validationParameters, out var rawValidatedToken);
    var securityToken = (JwtSecurityToken)rawValidatedToken;
    return Ok(principal);
}

推荐阅读