如何在 C# 中正确使用 OpenID Connect jwks_uri 元数据?
How to properly consume OpenID Connect jwks_uri metadata in C#?
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
。 class 的示例用法是
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
以便我可以验证访问令牌?
RSA public 密钥将始终至少包含成员 kty
(值为 RSA
)、n
和 e
(AQAB
即 65537 public 几乎所有键的指数)。
其他成员是可选的,用于提供有关密钥的信息。
一般来说,您会发现以下推荐成员:
- 其ID (
kid
),
- 如何使用(签名或加密)
- 它们是为什么算法设计的(
RS256
在你的例子中)。
当密钥来自 X.509 证书时,您通常会发现 x5t
或 x5t#256
(分别为 sha1 和 sha256 证书指纹)。
某些系统无法直接使用 JWK,因此提供了 PKCS#1 密钥(x5c
成员)。
您可以使用 (n
,e
) 对或 x5c
成员(如果提供)。这取决于您使用的 library/third 聚会应用程序的功能。
这就是我最终的结果:
//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;
}
一些更新 - Microsoft.IdentityModel.Tokens nuget includes the JsonWebKey with a constructor 接受 jwk JSON 字符串。
// 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();
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
。 class 的示例用法是
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
以便我可以验证访问令牌?
RSA public 密钥将始终至少包含成员 kty
(值为 RSA
)、n
和 e
(AQAB
即 65537 public 几乎所有键的指数)。
其他成员是可选的,用于提供有关密钥的信息。 一般来说,您会发现以下推荐成员:
- 其ID (
kid
), - 如何使用(签名或加密)
- 它们是为什么算法设计的(
RS256
在你的例子中)。
当密钥来自 X.509 证书时,您通常会发现 x5t
或 x5t#256
(分别为 sha1 和 sha256 证书指纹)。
某些系统无法直接使用 JWK,因此提供了 PKCS#1 密钥(x5c
成员)。
您可以使用 (n
,e
) 对或 x5c
成员(如果提供)。这取决于您使用的 library/third 聚会应用程序的功能。
这就是我最终的结果:
//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;
}
一些更新 - Microsoft.IdentityModel.Tokens nuget includes the JsonWebKey with a constructor 接受 jwk JSON 字符串。
// 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();