.NET Core 6 Blazor WASM应用间歇性出现IdentityServer密钥丢失问题
.NET Core 6 DataProtection + Azure 环境密钥环丢失问题排查与解决
核心问题
你的场景中,CryptographicException: 密钥{keyid}未在密钥环中找到 本质是ASP.NET Core DataProtection组件无法找到对应解密密钥,导致Duende IdentityServer无法解密存储的密钥材料。结合Azure App Service+Blob存储密钥+Key Vault加密的架构,问题根源集中在密钥环缓存刷新、存储同步、密钥生命周期管理三个维度。
具体原因及修复方案
1. 密钥环缓存未及时刷新
默认情况下,DataProtection会缓存密钥环24小时,仅在启动或缓存过期时才从Blob重新加载。若Blob中密钥更新(自动轮换),但App Service实例缓存未刷新,就会出现密钥找不到的情况。
修复步骤:
- 显式配置密钥环刷新周期与密钥有效期,强制实例定期获取最新密钥:
var keyVaultSettings = builder.Configuration.GetSection("KeyVault").Get<KeyVaultSettings>(); builder.Services.AddDataProtection() .PersistKeysToAzureBlobStorage(keyVaultSettings.ConnectionString, keyVaultSettings.ContainerName, keyVaultSettings.BlobName) .ProtectKeysWithAzureKeyVault(new Uri(keyVaultSettings.KeyIdentifier), new DefaultAzureCredential()) .SetDefaultKeyLifetime(TimeSpan.FromDays(7)) // 设置密钥有效期,按需调整 .SetKeyRingRefreshPeriod(TimeSpan.FromHours(1)); // 每1小时刷新一次密钥环 - 开启App Service的Always On功能,防止实例休眠导致缓存刷新机制失效。
2. Azure Blob存储一致性延迟
多实例部署时,某实例生成新密钥写入Blob后,其他实例可能因Blob默认的最终一致性无法立即读取新密钥,造成密钥环不一致。
修复步骤:
- 将存储密钥的Blob或容器设置为强一致性(通过Azure Portal或CLI修改),确保所有实例能实时获取最新密钥文件。
- 启用Blob变化监听,自动触发密钥环刷新:
var blobClient = new BlobClient(keyVaultSettings.ConnectionString, keyVaultSettings.ContainerName, keyVaultSettings.BlobName); builder.Services.AddDataProtection() .PersistKeysToAzureBlobStorage(blobClient) .ProtectKeysWithAzureKeyVault(new Uri(keyVaultSettings.KeyIdentifier), new DefaultAzureCredential()) .UseBlobStorageKeyWatcher(blobClient); // 监听Blob变化,自动刷新密钥环需确保引用
Microsoft.AspNetCore.DataProtection.AzureStorage包,版本与.NET Core 6兼容。
3. Key Vault密钥轮换不匹配
若Key Vault密钥启用自动轮换,而DataProtection仍使用旧版本密钥解密,会导致密钥找不到错误。
修复步骤:
- 确保
DefaultAzureCredential拥有Key Vault的KeyGet权限,允许访问所有密钥版本。 - 禁止手动删除Key Vault旧密钥版本,直到所有实例完成密钥环刷新(旧密钥需用于解密历史加密数据)。
4. 客户端缓存残留
常规窗口错误持续、无痕窗口正常的现象,说明客户端缓存了用失效密钥加密的身份验证Cookie/令牌。
修复步骤:
- 缩短IdentityServer令牌过期时间,减少旧令牌有效时长:
builder.Services.AddIdentityServer() // 生产环境替换为Key Vault签名密钥 .AddSigningCredential(new Uri(keyVaultSettings.KeyIdentifier), new DefaultAzureCredential()) .SetAccessTokenLifetime(TimeSpan.FromMinutes(15)) .SetIdentityTokenLifetime(TimeSpan.FromMinutes(15)); - 在应用重启后,通过响应头禁用客户端缓存:
app.Use(async (context, next) => { context.Response.Headers.CacheControl = "no-cache, no-store, must-revalidate"; context.Response.Headers.Expires = "-1"; await next(); });
临时恢复优化
替代手动重启+无痕窗口的方式:
- 使用Azure App Service部署槽位热重启:部署到staging槽后交换至生产槽,实例会逐步刷新,无服务中断。
- 添加管理员接口触发密钥环刷新:
[ApiController] [Route("api/dataprotection")] [Authorize(Roles = "Admin")] public class DataProtectionController : ControllerBase { private readonly IKeyManager _keyManager; public DataProtectionController(IKeyManager keyManager) { _keyManager = keyManager; } [HttpPost("refresh")] public IActionResult RefreshKeyRing() { _keyManager.RefreshKeyRing(); return Ok("密钥环已刷新"); } }
内容的提问来源于stack exchange,提问作者Chris Searles
相关产品推荐
相关产品推荐

