OpenIddict 加密与签名凭据
为了保护颁发的令牌,OpenIddict 使用了两种类型的凭据:
- 签名凭据 用于防止篡改,可以是非对称的(如 RSA 或 ECDSA 密钥)或对称的。
- 加密凭据 用于确保令牌内容不会被恶意方读取,同样可以是非对称的(如 RSA 密钥)或对称的。
使用可选的 ASP.NET Core 数据保护集成生成的令牌依赖于其自身的密钥环,这与本文档中讨论的凭据是分开的。 有关数据保护的更多信息,请访问 ASP.NET Core 数据保护 。
在授权服务器选项中注册凭据
OpenIddict 允许注册一个或多个密钥(原始密钥或嵌入在 X.509 证书中的密钥)。
当注册了多个密钥/证书时(这对于实现密钥轮换非常有用),OpenIddict 将根据以下算法选择最合适的密钥:
- 除非是对身份令牌进行签名(这要求使用非对称密钥),否则总是优先选择 对称密钥。
- 嵌入在 X.509 证书中的 非对称密钥 根据 NotAfter 和 NotBefore 日期排序(尚未生效的证书不会被 OpenIddict 使用,并且优先选择有效期最远的证书)。
- 总是优先选择 X.509 证书 而非原始的 RSA/ECDSA 密钥。
注册临时密钥
出于开发目的,可以使用 临时密钥 来签署或加密令牌,这种密钥不会被持久化或在实例间共享:
services.AddOpenIddict()
.AddServer(options =>
{
options.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey();
});
options.AddEphemeralEncryptionKey()
生成一个非对称 RSA 密钥,该密钥不直接用来加密令牌,而是用于加密一个中间的、每个令牌专用的对称密钥,之后使用这个对称密钥首先通过 AES 加密令牌内容。关于此机制的更多信息,请阅读 使用 RSAES OAEP 的密钥加密 。
注册开发证书
为了开发目的,OpenIddict 可以在运行 OpenIddict 服务器功能的用户账户的证书存储中生成并存储一个证书。与临时密钥不同,开发证书会被持久化——尽管不会在实例间共享——并且当应用程序主机重启时会重新使用。
services.AddOpenIddict()
.AddServer(options =>
{
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
});
options.AddDevelopmentEncryptionCertificate()
或 options.AddDevelopmentSigningCertificate()
将在运行时抛出 PlatformNotSupportedException
。
options.AddDevelopmentEncryptionCertificate()
或 options.AddDevelopmentSigningCertificate()
:尝试在 IIS 或 Azure App Service 上使用它们会导致运行时异常(除非应用程序池配置为加载用户配置文件)。为了避免这种情况,可以考虑创建自签名证书并将其存储在主机计算机的 X.509 证书存储中。
注册密钥
要注册签名或加密密钥,可以向 options.AddSigningKey()
/ options.AddEncryptionKey()
方法提供一个 SecurityKey
实例,通常是 SymmetricSecurityKey
或 RsaSecurityKey
:
services.AddOpenIddict()
.AddServer(options =>
{
options.AddEncryptionKey(new SymmetricSecurityKey(
Convert.FromBase64String("DRjd/GnduI3Efzen9V9BvbNUfc/VKgXltV7Kbk9sMkY=")));
});
虽然签名密钥可以是对称或非对称的,但 OpenIddict 要求至少注册一个非对称密钥来签署身份令牌。如果同时注册了非对称和对称签名密钥,对称密钥在保护访问令牌、授权代码或刷新令牌时将始终优先,而非对称密钥则用于签署身份令牌,这些令牌旨在公开验证。
注册证书(推荐用于生产环境)
要注册签名或加密证书,可以使用 options.AddSigningCertificate()
/ options.AddEncryptionCertificate()
方法并传入 X509Certificate2
实例。或者,也可以提供一个唯一标识操作系统机器或用户证书存储中证书的 thumbprint
(指纹)。
在生产环境中,建议使用两个独立于 HTTPS 所用证书的 RSA 证书:一个用于加密,一个用于签名。证书可以使用.NET Core 的 CertificateRequest
API 本地生成和自签名:
using var algorithm = RSA.Create(keySizeInBits: 2048);
var subject = new X500DistinguishedName("CN=Fabrikam Encryption Certificate");
var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true));
var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2));
File.WriteAllBytes("encryption-certificate.pfx", certificate.Export(X509ContentType.Pfx, string.Empty));
using var algorithm = RSA.Create(keySizeInBits: 2048);
var subject = new X500DistinguishedName("CN=Fabrikam Signing Certificate");
var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true));
var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2));
File.WriteAllBytes("signing-certificate.pfx", certificate.Export(X509ContentType.Pfx, string.Empty));
存储证书的最佳位置取决于你的主机:
- 对于 IIS 应用,推荐将证书存储在机器存储中。
- 在 Azure 上,证书可以通过特殊的
WEBSITE_LOAD_CERTIFICATES
标志上传并暴露给 Azure App Service 应用。更多详情,请访问 [在 Azure App Service 中代码中使用 TLS/SSL 证书](链接未给出,请自行搜索相关 Azure 文档页面) 。
在 API/资源验证选项中导入凭证
使用 options.UseLocalServer() 集成
当 API 和授权服务器属于同一项目时,可以通过调用 options.UseLocalServer()
轻松地导入签名和加密凭证:
services.AddOpenIddict()
.AddValidation(options =>
{
options.UseLocalServer();
});
使用 OpenID Connect 发现(仅限非对称签名密钥)
当 API 和授权服务器托管在不同的应用程序中时,可以使用 标准 OpenID Connect 发现 自动导入非对称签名密钥:
services.AddOpenIddict()
.AddValidation(options =>
{
options.SetIssuer("https://localhost:44319/");
options.UseSystemNetHttp();
});
在令牌验证参数中注册对称签名密钥
与非对称签名密钥不同,对称密钥(与基于 HMAC 的算法如 HS256 一起使用)不能安全地由 OpenID Connect 发现端点暴露。因此,它们不能被 OpenIddict 验证处理器自动导入。对于需要使用对称签名密钥的应用程序,可以使用高级配置 API 在其令牌验证选项中注册它:
services.AddOpenIddict()
.AddValidation(options =>
{
options.Configure(options => options.TokenValidationParameters.IssuerSigningKey =
new SymmetricSecurityKey(
Convert.FromBase64String("DRjd/GnduI3Efzen9V9BvbNUfc/VKgXltV7Kbk9sMkY=")));
});
注册加密密钥或证书
为了导入加密密钥/证书,可以使用与 OpenIddict 服务器功能提供的相同重载方法:
services.AddOpenIddict()
.AddValidation(options =>
{
options.AddEncryptionCertificate("b82f36609cdaff9a95de60e8d5ac774b2e496c4b");
});