如何在 C# 中正确使用 OpenID Connect jwks_uri 元数据?
Posted
技术标签:
【中文标题】如何在 C# 中正确使用 OpenID Connect jwks_uri 元数据?【英文标题】:How to properly consume OpenID Connect jwks_uri metadata in C#? 【发布时间】:2018-04-17 17:14:01 【问题描述】:OpenID Connect 发现文档通常包含jwks_uri
属性。从jwks_uri
返回的数据似乎至少有两种不同的形式。一种形式包含名为x5c
和x5t
的字段。这方面的一个示例如下所示:
"keys": [
"kty": "RSA",
"use": "sig",
"kid": "C61F8F2524D080D0DB0A508747A94C2161DEDAC8",
"x5t": "xh-PJSTQgNDbClCHR6lMIWHe2sg", <------ HERE
"e": "AQAB",
"n": "lueb...",
"x5c": [
"MIIC/..." <------ HERE
],
"alg": "RS256"
]
我看到的另一个版本省略了 x5c 和 x5t 属性,但包含 e
和 n
。一个例子是:
"keys": [
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"kid": "cb11e2f233aee0329a5344570349cddb6b8ff252",
"n": "sJ46h...", <------ HERE
"e": "AQAB" <------ HERE
]
我正在使用 C# 的 Microsoft.IdentityModel.Tokens.TokenValidationParameters
,我正在尝试弄清楚如何提供属性 IssuerSigningKey
。这个类的一个示例用法是
new TokenValidationParameters
ValidateAudience = true,
ValidateIssuer = true,
...,
IssuerSigningKey = new X509SecurityKey(???) or new JsonWebKey(???) //How to create this based on x5c/x5t and also how to create this based on e and n ?
鉴于这两种不同的 JWK 格式,我如何使用它们将 IssuerSigningKey
提供给 TokenValidationParameter
,以便我可以验证访问令牌?
【问题讨论】:
非常小的反馈/更正:“每个 OpenID Connect 提供者都发布了一个发现文档……”并不是真的。 “发现”是规范的可选部分,一些提供者可能不会实现它。 咳嗽苹果咳嗽:bitbucket.org/openid/connect/src/default/… @GregPendlebury 你是绝对正确的。我会更新这个。感谢您指出这一点。 【参考方案1】:这就是我最终的结果:
//Model the JSON Web Key Set
public class JsonWebKeySet
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = "keys", Required = Required.Default)]
public JsonWebKey[] Keys get; set;
//Model the JSON Web Key object
public class JsonWebKey
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = "kty", Required = Required.Default)]
public string Kty get; set;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = "use", Required = Required.Default)]
public string Use get; set;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = "kid", Required = Required.Default)]
public string Kid get; set;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = "x5t", Required = Required.Default)]
public string X5T get; set;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = "e", Required = Required.Default)]
public string E get; set;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = "n", Required = Required.Default)]
public string N get; set;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = "x5c", Required = Required.Default)]
public string[] X5C get; set;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = "alg", Required = Required.Default)]
public string Alg get; set;
我首先向 OpenID Connect 发现文档中提供的 jwks_uri
端点发出请求。该请求将相应地填充上述对象。然后我将JsonWebKeySet
对象传递给创建ClaimsPrincipal
的方法
string idToken = "<the id_token that was returned from the Token endpoint>";
List<SecurityKey> keys = this.GetSecurityKeys(jsonWebKeySet);
var parameters = new TokenValidationParameters
ValidateAudience = true,
ValidAudience = tokenValidationParams.Audience,
ValidateIssuer = true,
ValidIssuer = tokenValidationParams.Issuer,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = keys,
NameClaimType = NameClaimType,
RoleClaimType = RoleClaimType
;
var handler = new JwtSecurityTokenHandler();
handler.InboundClaimTypeMap.Clear();
SecurityToken jwt;
ClaimsPrincipal claimsPrincipal = handler.ValidateToken(idToken, parameters, out jwt);
// validate nonce
var nonceClaim = claimsPrincipal.FindFirst("nonce")?.Value ?? string.Empty;
if (!string.Equals(nonceClaim, "<add nonce value here>", StringComparison.Ordinal))
throw new AuthException("An error occurred during the authentication process - invalid nonce parameter");
return claimsPrincipal;
GetSecurityKeys
方法是这样实现的
private List<SecurityKey> GetSecurityKeys(JsonWebKeySet jsonWebKeySet)
var keys = new List<SecurityKey>();
foreach (var key in jsonWebKeySet.Keys)
if (key.Kty == OpenIdConnectConstants.Rsa)
if (key.X5C != null && key.X5C.Length > 0)
string certificateString = key.X5C[0];
var certificate = new X509Certificate2(Convert.FromBase64String(certificateString));
var x509SecurityKey = new X509SecurityKey(certificate)
KeyId = key.Kid
;
keys.Add(x509SecurityKey);
else if (!string.IsNullOrWhiteSpace(key.E) && !string.IsNullOrWhiteSpace(key.N))
byte[] exponent = Base64UrlUtility.Decode(key.E);
byte[] modulus = Base64UrlUtility.Decode(key.N);
var rsaParameters = new RSAParameters
Exponent = exponent,
Modulus = modulus
;
var rsaSecurityKey = new RsaSecurityKey(rsaParameters)
KeyId = key.Kid
;
keys.Add(rsaSecurityKey);
else
throw new PlatformAuthException("JWK data is missing in token validation");
else
throw new NotImplementedException("Only RSA key type is implemented for token validation");
return keys;
【讨论】:
OpenIdConnectConstants 和 Base64Url 定义在哪里?谢谢 @owade OIDC 常量位于包含常量的文件中。 Rsa 属性就是值“RSA”。 Base64Url 只是一个静态函数,看起来像这里看到的第三个答案***.com/questions/1228701/… 我从互联网的尽头来到这个答案......有趣的是,关于如何理解IssuerSigningKeys
和令牌验证的信息如此之少。我很高兴找到你的作品,Rob。
感谢您的解决方案。我对 OAuth 很陌生。例如,当 API 从 Azure AD 接收令牌时,此验证是否足够?我们需要更多控制吗?【参考方案2】:
RSA 公钥将始终至少包含成员 kty
(值为 RSA
)、n
和 e
(AQAB
,即几乎所有密钥的 65537 公钥)。
其他成员是可选的,用于提供有关密钥的信息。 一般来说,你会发现以下推荐的成员:
它的 ID (kid
),
如何使用(签名或加密)
它们是为什么算法设计的(在您的示例中为RS256
)。
当密钥来自 X.509 证书时,您通常会找到 x5t
或 x5t#256
(分别为 sha1 和 sha256 证书指纹)。
某些系统无法直接使用 JWK,并且提供了 PKCS#1 密钥(x5c
成员)。
您可以使用 (n
,e
) 对或 x5c
成员(如果提供)。这取决于您使用的库/第三方应用程序的功能。
【讨论】:
稍微扩展一下 Florent 的评论:x5c(证书链),如果已解码(例如,尝试运行 openssl x509 -in certificate.pem -text -noout , 显示模数和指数。这些与 JWK 规范中的 n 和 e 值相同(编码不同;证书输出以十六进制显示,而 JWK 参数以 base64url 编码显示。这是一个呈现相同信息的不同方式。最终,使用密钥进行验证时最重要的是模数和指数。【参考方案3】:一点更新 - Microsoft.IdentityModel.Tokens nuget 包括采用 jwk JSON 字符串的 JsonWebKey with a constructor。
// JSON class
public class OpenIdConnectKeyCollection
[JsonProperty("keys")]
public ICollection<JToken> JsonWebKeys get; set;
// map the keys using the JSON ctor
var jsonKeys = keysResp.JsonWebKeys;
var jwk = jsonKeys
.Select(k => new JsonWebKey(k.ToString()))
.ToList();
【讨论】:
以上是关于如何在 C# 中正确使用 OpenID Connect jwks_uri 元数据?的主要内容,如果未能解决你的问题,请参考以下文章
使用 C# 以编程方式向 MVC 控制器验证 Azure Ad/OpenId 并获取重定向 uri