如何在 dotnet core 中验证非对称签名的 JWT?
Posted
技术标签:
【中文标题】如何在 dotnet core 中验证非对称签名的 JWT?【英文标题】:How do I verify an asymmetrically signed JWT in dotnet core? 【发布时间】:2019-11-03 21:08:23 【问题描述】:我找到了 .NET FW 中的非对称签名示例和 .NET Core 中的对称签名示例,但我无法弄清楚如何在 .NET Core 中非对称地验证 JWT。给定 JWK 集的 URL 或给定公钥,如何在 .NET Core 中验证令牌?
【问题讨论】:
【参考方案1】:非对称签名和对称签名之间的唯一区别是签名密钥。只需为令牌验证参数构造一个新的非对称安全密钥即可。
假设您想使用 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 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);
【讨论】:
我只需要公钥来验证令牌,对吧? @Genfood 是的,这是真的 :)【参考方案2】:我最终实现了OpenID Connect Discovery 规范,它允许您以标准格式发布令牌端点和密钥集端点。然后我可以使用AddJwtBearer()
AuthenticationBuilder
扩展方法来自动缓存密钥集,验证令牌,并填充ClaimsPrincipal
。
要编写您自己的实现 OpenID Connect 发现协议的令牌服务,您需要:
实现一个路由 /keys
,它服务于从您的 pfx 证书派生的 Microsoft.IdentityModel.Tokens.JsonWebKeySet
对象。
JsonWebKeySet GetJwksFromCertificates(IEnumerable<X509Certificate2> certificates)
var jwks = new JsonWebKeySet();
foreach (var certificate in certificates)
var rsaParameters = ((RSA)certificate.PublicKey.Key).ExportParameters(false);
var jwk = new JsonWebKey
// https://tools.ietf.org/html/rfc7517#section-4
Kty = certificate.PublicKey.Key.KeyExchangeAlgorithm,
Use = "sig",
Kid = certificate.Thumbprint,
X5t = certificate.Thumbprint,
// https://tools.ietf.org/html/rfc7517#appendix-B
N = Convert.ToBase64String(rsaParameters.Modulus),
E = Convert.ToBase64String(rsaParameters.Exponent),
;
jwks.Keys.Add(jwk);
return jwks;
实现一个返回501 Not Implemented
的路由/not-yet-implemented
。
实现一个路由/.well-known/openid-configuration
为Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration
对象提供服务。
OpenIdConnectConfiguration GetOpenIdConnectConfiguration(string issuer)
var configuration = new OpenIdConnectConfiguration
Issuer = issuer,
TokenEndpoint = issuer + "/token",
AuthorizationEndpoint = issuer + "/not-yet-implemented",
JwksUri = issuer + "/keys",
;
configuration.GrantTypesSupported.Add(grantType);
return configuration;
实现一个路由/token
,它使用您的应用程序特定逻辑来验证用户并生成ClaimsIdentity
,然后使用JwtSecurityTokenHandler
创建一个System.IdentityModel.Tokens.Jwt.JwtSecurityToken
。
JwtSecurityToken CreateJwt(
string issuer,
TimeSpan lifetime,
ClaimsIdentity claimsIdentity,
X509Certificate2 signingCertificate)
var tokenDescriptor = new SecurityTokenDescriptor
Issuer = issuer,
Expires = DateTime.UtcNow.Add(lifetime),
NotBefore = DateTime.UtcNow,
Subject = claimsIdentity,
SigningCredentials = new X509SigningCredentials(signingCertificate),
;
return new JwtSecurityTokenHandler().CreateJwtSecurityToken(tokenDescriptor);
我还鼓励您为您的 /token
路由实施 OAuth client_credentials
授权流程。
更新
我发表了一篇完整的文章:non-paywalled link。
【讨论】:
您能否分享您的方法的示例代码 - 这将非常有帮助。谢谢!! 刚刚添加了路由列表以及如何实现它们以上是关于如何在 dotnet core 中验证非对称签名的 JWT?的主要内容,如果未能解决你的问题,请参考以下文章