如何在dotnet核心中验证不对称签名的JWT?

时间:2019-06-21 02:51:36

标签: asp.net-core .net-core jwt rsa

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

2 个答案:

答案 0 :(得分:0)

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

假设您要使用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

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

一点补丁

由于.NET Core支持rsa.FromXmlString() api,因此我只需复制@myloveCc's code即可在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承载身份验证:

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);
}

答案 1 :(得分:0)

我最终实现了OpenID Connect Discovery规范,该规范允许您以标准格式发布令牌端点和密钥集端点。然后,我可以使用AddJwtBearer() AuthenticationBuilder扩展方法来自动缓存键集,验证令牌并填充ClaimsPrincipal

没有人读这本书,不必执行此协议,而应该使用现有的OpenID Server产品,或者如果没有,则使用其中一种流行的开源实现。

另一个答案非常有益,但是它不支持捆绑在密钥集中的多个密钥,并且随后不支持滚动或将密钥集发布为标准JWKS。