如何通过依赖注入(DI)配置OpenIddict加密密钥以对接Azure Key Vault?
如何通过合规的依赖注入方式配置OpenIddict的Azure Key Vault密钥
你提到的手动调用BuildServiceProvider()确实是反模式,会导致单例服务重复实例化,完全不推荐。好在OpenIddict和ASP.NET Core的选项系统提供了几种合规的方式来实现你的需求,下面分场景详细说明:
方法1:使用OpenIddict内置的工厂函数重载
OpenIddict的AddSigningKey(服务器端)和AddEncryptionKey(验证端)都提供了接受Func<IServiceProvider, SecurityKey>的重载,这个工厂函数会在服务提供者完全构建完成后才被调用,因此可以安全地从DI容器中解析所需服务:
服务器端(签名密钥配置)
public void ConfigureServices(IServiceCollection services) { // 先注册你的Azure Key Vault客户端服务 services.AddSingleton<IAzureKeyVaultClient>(provider => { // 根据实际情况初始化客户端,比如用Azure.Identity的DefaultAzureCredential return new AzureKeyVaultClient(new DefaultAzureCredential()); }); services.AddOpenIddict() .AddServer(options => { // 配置OpenIddict服务器基础选项(端点、令牌类型等) options.SetAuthorizationEndpoint("/connect/authorize") .SetTokenEndpoint("/connect/token") .AllowAuthorizationCodeFlow(); // 延迟获取Azure Key Vault中的密钥 options.AddSigningKey(async provider => { var keyVaultClient = provider.GetRequiredService<IAzureKeyVaultClient>(); // 替换为你的密钥名称和Key Vault URI var keyBundle = await keyVaultClient.GetKeyAsync("https://your-keyvault.vault.azure.net/keys/your-signing-key"); var rsaKey = keyBundle.Key.ToRSA(); return new RsaSecurityKey(rsaKey); }); }); }
资源API端(验证加密密钥配置)
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IAzureKeyVaultClient>(provider => { return new AzureKeyVaultClient(new DefaultAzureCredential()); }); services.AddOpenIddict() .AddValidation(options => { // 配置验证选项,比如设置颁发者地址 options.SetIssuer("https://your-authorization-server.com"); options.UseLocalServer(); // 同样用工厂函数获取密钥 options.AddEncryptionKey(async provider => { var keyVaultClient = provider.GetRequiredService<IAzureKeyVaultClient>(); var keyBundle = await keyVaultClient.GetKeyAsync("https://your-keyvault.vault.azure.net/keys/your-encryption-key"); var rsaKey = keyBundle.Key.ToRSA(); return new RsaSecurityKey(rsaKey); }); }); }
方法2:使用ASP.NET Core选项系统的PostConfigure
如果你更倾向于遵循ASP.NET Core的配置惯例,可以用PostConfigure来延迟配置OpenIddict选项,它允许你直接注入所需的服务,完全不需要手动处理服务提供者:
服务器端示例
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IAzureKeyVaultClient>(provider => { return new AzureKeyVaultClient(new DefaultAzureCredential()); }); // 先注册OpenIddict服务器的基础配置 services.AddOpenIddict() .AddServer(options => { options.SetAuthorizationEndpoint("/connect/authorize") .SetTokenEndpoint("/connect/token") .AllowAuthorizationCodeFlow(); // 暂时不添加密钥,留到PostConfigure处理 }); // 注入Azure Key Vault客户端并配置签名密钥 services.AddOptions<OpenIddictServerOptions>() .PostConfigure<IAzureKeyVaultClient>(async (options, keyVaultClient) => { var keyBundle = await keyVaultClient.GetKeyAsync("https://your-keyvault.vault.azure.net/keys/your-signing-key"); var rsaKey = keyBundle.Key.ToRSA(); options.AddSigningKey(new RsaSecurityKey(rsaKey)); }); }
资源API端示例
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IAzureKeyVaultClient>(provider => { return new AzureKeyVaultClient(new DefaultAzureCredential()); }); services.AddOpenIddict() .AddValidation(options => { options.SetIssuer("https://your-authorization-server.com"); options.UseLocalServer(); }); services.AddOptions<OpenIddictValidationOptions>() .PostConfigure<IAzureKeyVaultClient>(async (options, keyVaultClient) => { var keyBundle = await keyVaultClient.GetKeyAsync("https://your-keyvault.vault.azure.net/keys/your-encryption-key"); var rsaKey = keyBundle.Key.ToRSA(); options.AddEncryptionKey(new RsaSecurityKey(rsaKey)); }); }
方法3:自定义密钥提供者(支持密钥自动轮转)
如果你的Azure Key Vault密钥需要定期轮转,上面的方法只会在启动时获取一次密钥,无法自动更新。这种情况下,你可以实现OpenIddict的ISigningKeyProvider(服务器端)或IValidationEncryptionKeyProvider(验证端)接口,OpenIddict会定期调用这个提供者来获取最新密钥:
自定义签名密钥提供者
public class AzureKeyVaultSigningKeyProvider : ISigningKeyProvider { private readonly IAzureKeyVaultClient _keyVaultClient; private readonly string _keyIdentifier; public AzureKeyVaultSigningKeyProvider(IAzureKeyVaultClient keyVaultClient, IConfiguration configuration) { _keyVaultClient = keyVaultClient; _keyIdentifier = configuration["OpenIddict:SigningKeyIdentifier"]; } public async ValueTask<IEnumerable<SecurityKey>> GetSigningKeysAsync(CancellationToken cancellationToken) { var keyBundle = await _keyVaultClient.GetKeyAsync(_keyIdentifier, cancellationToken: cancellationToken); var rsaKey = keyBundle.Key.ToRSA(); return new[] { new RsaSecurityKey(rsaKey) }; } }
注册并使用自定义提供者
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IAzureKeyVaultClient>(provider => { return new AzureKeyVaultClient(new DefaultAzureCredential()); }); // 注册自定义密钥提供者 services.AddSingleton<ISigningKeyProvider, AzureKeyVaultSigningKeyProvider>(); services.AddOpenIddict() .AddServer(options => { options.SetAuthorizationEndpoint("/connect/authorize") .SetTokenEndpoint("/connect/token") .AllowAuthorizationCodeFlow(); // 启用使用自定义密钥提供者 options.UseSigningKeyProvider(); }); }
这种方式的优势在于,OpenIddict会根据配置的刷新间隔(默认24小时)自动调用GetSigningKeysAsync获取最新密钥,无需重启应用就能支持密钥轮转。
内容的提问来源于stack exchange,提问作者Alexandre Ribeiro
相关产品推荐
相关产品推荐

