使用EdDSA签名在ASP.NET Core中使用OpenID Connect验证令牌
2025年8月6日 · by damienbod · in .NET Core, ASP.NET Core, OAuth2, OpenId, Security, Uncategorized · 1条评论
某些身份提供商使用EdDSA / ED25519算法来签名和颁发令牌。本文展示了如何使用ScottBrady的Nuget包和ASP.NET Core来验证这些令牌。使用默认的OpenID Connect设置时,密钥不会被读取,令牌也无法验证。
错误消息可能返回如下内容:
1
|
IDX10511: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.JsonWebKey
|
使用ScottBrady.IdentityModel Nuget包来实现此需求,感谢Scott创建了这个包。
密钥将按照标准中规定的web格式在OpenID Connect服务器上发布。
1
2
3
4
5
6
7
8
9
10
11
|
{
"keys": [
{
"alg": "EdDSA",
"crv": "Ed25519",
"kid": ".....",
"kty": "OKP",
"x": "....."
}
]
}
|
OpenID Connect服务器提供了一个JWK端点,用于发布公钥。这用于验证所颁发令牌的签名。可以使用JsonWebKeySet类型读取密钥列表,并可转换为EdDsaSecurityKey密钥。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Tls;
using ScottBrady.IdentityModel.Crypto;
using ScottBrady.IdentityModel.Tokens;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace RazorPageOidcClient;
public class LoadPublicSigningKeys
{
public static async Task<IEnumerable<EdDsaSecurityKey>> LoadEdDsaKeysAsync(string jwkUrl)
{
using var httpClient = new HttpClient();
var jwkJson = await httpClient.GetStringAsync(jwkUrl);
var jwkSet = JsonSerializer.Deserialize<JsonWebKeySet>(jwkJson);
if(jwkSet == null)
{
throw new ArgumentNullException("Jwk endpoint not working or not found");
}
var keys = new List<EdDsaSecurityKey>();
foreach (var key in jwkSet.Keys.Where(k => k.Alg == "EdDSA" && k.Crv == "Ed25519"))
{
// 解码公钥
byte[] publicKeyBytes = Base64UrlEncoder.DecodeBytes(key.X);
// 仅使用公钥创建EdDSA参数
var parameters = new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519)
{
X = publicKeyBytes
};
// 创建EdDSA公钥
var edDsa = EdDsa.Create(parameters);
keys.Add(new EdDsaSecurityKey(edDsa));
}
return keys;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
var keys = LoadPublicSigningKeys
.LoadEdDsaKeysAsync("https://{authority}/.well-known/jwks.json")
.GetAwaiter().GetResult();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie().AddOpenIdConnect( options =>
{
...
});
|
像这样读取密钥并不是最优的。最好直接挂钩到OpenID Connect已知端点的默认读取中。
改进的解决方案
感谢Frank Quednau
创建OIDC检索器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
internal class OkpEnrichedRetriever : IConfigurationRetriever<OpenIdConnectConfiguration>
{
private readonly IConfigurationRetriever<OpenIdConnectConfiguration> baseImplementation
= new OpenIdConnectConfigurationRetriever();
public async Task<OpenIdConnectConfiguration> GetConfigurationAsync(string address,
IDocumentRetriever retriever, CancellationToken cancel)
{
var config = await baseImplementation.GetConfigurationAsync(address, retriever, cancel);
foreach (var jsonWebKey in config.JsonWebKeySet.Keys)
{
if (ExtendedJsonWebKeyConverter.TryConvertToEdDsaSecurityKey(jsonWebKey, out var key))
{
config.SigningKeys.Add(key);
}
}
return config;
}
}
|
这可以按如下方式使用:
1
2
3
4
5
6
7
8
9
|
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie().AddOpenIdConnect( options =>
{
options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
$"{authority}/.well-known/openid-configuration",
new OkpEnrichedRetriever());
|
现在你有了一个更清晰的解决方案。
链接