Azure App Service无法通过用户凭据/令牌连接Azure KeyVault问题求助
你的问题核心在于 DefaultAzureCredential 并不会自动读取Web应用中通过AddInMemoryTokenCaches缓存的用户OIDC令牌。它的凭证优先级链(环境变量、托管标识、共享令牌缓存等)里的共享令牌缓存是针对桌面/CLI场景的,和Web应用的内存缓存不是同一个存储,所以才会出现所有凭证都不可用的错误。
下面是具体的修复步骤:
1. 更新API权限Scope
首先,你需要在Startup.cs中添加Azure KeyVault的访问Scope,替换原来仅有的user.read(这是Graph API的权限,和KeyVault无关):
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { "https://vault.azure.net/user_impersonation" })
这个Scope是KeyVault允许用户身份模拟访问的标准权限,必须添加才能获取到有效的访问令牌。
2. 在Azure AD应用注册中配置KeyVault权限
确保你的应用注册已经添加了KeyVault的API权限:
- 登录Azure Portal,找到你的Azure AD应用注册
- 进入API权限页面,点击添加权限
- 搜索并选择Azure Key Vault,添加user_impersonation权限
- 点击授予管理员同意(如果你的租户要求管理员审批)
3. 使用用户令牌创建SecretClient
不再依赖DefaultAzureCredential,而是手动获取用户的访问令牌,并用它来初始化SecretClient。具体代码如下:
在Controller中注入ITokenAcquisition
private readonly ITokenAcquisition _tokenAcquisition; private readonly AzureKeyVaultSettings _kvSettings; public HomeController(ITokenAcquisition tokenAcquisition, IOptions<AzureKeyVaultSettings> kvSettings) { _tokenAcquisition = tokenAcquisition; _kvSettings = kvSettings.Value; }
获取令牌并初始化SecretClient
public async Task<IActionResult> Index() { try { // 获取当前登录用户的KeyVault访问令牌 var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync( new[] { "https://vault.azure.net/user_impersonation" }); // 用令牌创建自定义TokenCredential var userTokenCredential = new UserTokenCredential(accessToken); // 初始化SecretClient var client = new SecretClient( new Uri(_kvSettings.KeyVaultBaseUrl), userTokenCredential); // 读取密钥列表 var secretList = new List<SecretProperties>(); await foreach (var secret in client.GetPropertiesOfSecretsAsync()) { secretList.Add(secret); } return View(secretList); } catch (Exception ex) { _logger.LogCritical(ex, "Failed to retrieve KeyVault secrets"); return View("Error", ex); } } // 自定义TokenCredential实现,直接返回已获取的用户令牌 private class UserTokenCredential : TokenCredential { private readonly string _accessToken; public UserTokenCredential(string accessToken) { _accessToken = accessToken; } public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) { // 令牌有效期可以从实际令牌中解析,这里简化为1小时 return new AccessToken(_accessToken, DateTimeOffset.UtcNow.AddHours(1)); } public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) { return new ValueTask<AccessToken>(GetToken(requestContext, cancellationToken)); } }
为什么原来的方案不生效?
DefaultAzureCredential的设计目标是在不同环境中自动选择合适的凭证(本地用VS/CLI,服务器用托管标识等),但它不会读取Web应用中通过Microsoft Identity Web库缓存的用户令牌。这些令牌存储在AddInMemoryTokenCaches提供的缓存中,只有ITokenAcquisition服务可以访问,所以必须手动获取并使用。
另外,你之前的代码中没有请求KeyVault的权限Scope,即使DefaultAzureCredential能找到令牌,也没有访问KeyVault的权限。
内容的提问来源于stack exchange,提问作者Cole W

