如何在.NET控制台应用中通过REST API获取Azure访问令牌及密钥保管库密钥
问题:.NET控制台应用通过REST API无法获取Azure密钥保管库密钥值
我尝试使用.NET控制台应用通过REST API从Azure密钥保管库中获取密钥,以下是使用的代码:
using Newtonsoft.Json; using System; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json; namespace KeyVaultSecretFetcher { class Program { static async Task Main(string[] args) { // Replace with your Key Vault URL, secret name, and authentication token string keyVaultUrl = "https://KeyVault.vault.azure.net"; string secretName = "Test3"; //string accessToken = "<your-access-token>"; string tenantId = "tenantId"; // Your tenant ID string clientId = "clientId"; // Your application (client) ID string clientSecret = "clientSecret Vaule "; // Your client secret string accessToken = await GetAccessToken(tenantId, clientId, clientSecret); HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); string secretUrl = $"{keyVaultUrl}/secrets/{secretName}?api-version=7.3"; HttpResponseMessage response = await client.GetAsync(secretUrl); if (response.IsSuccessStatusCode) { string responseBody = await response.Content.ReadAsStringAsync(); var secret = System.Text.Json.JsonSerializer.Deserialize<Secret>(responseBody); Console.WriteLine("Secret value: " + secret.Value); } else { Console.WriteLine("Error fetching secret: " + response.StatusCode); } } private static async Task<string> GetAccessToken(string tenantId, string clientId, string clientSecret) { using (var client = new HttpClient()) { var body = new StringContent($"grant_type=client_credentials&client_id={clientId}&client_secret={clientSecret}&resource=https://vault.azure.net", Encoding.UTF8, "application/x-www-form-urlencoded"); var response = await client.PostAsync($"https://login.microsoftonline.com/{tenantId}/oauth2/token", body); response.EnsureSuccessStatusCode(); var jsonResponse = await response.Content.ReadAsStringAsync(); dynamic tokenResponse = JsonConvert.DeserializeObject(jsonResponse); return tokenResponse.access_token; } } class Secret { public string Value { get; set; } } } }
但无法获取密钥值,当前API权限配置情况:
- 配置了委派权限(
user_impersonation) - 应用权限已被禁用
如何仅通过REST API获取密钥值?
问题分析与解决方案
核心问题
你当前使用的是client_credentials(客户端凭据)认证流,该流对应Azure AD的应用权限,但你配置的是委派权限,两者不匹配,导致令牌没有足够权限访问密钥保管库。
解决方案一:改用应用权限(推荐后台控制台场景)
这是控制台应用访问密钥保管库的标准方式,无需用户交互:
- 启用并配置应用权限
- 在Azure AD应用注册中,添加Key Vault的应用权限(选择
Secrets.Get或Secrets.ReadAll等所需权限) - 点击授予管理员同意(必须完成此步骤,否则权限不生效)
- 在Azure AD应用注册中,添加Key Vault的应用权限(选择
- 配置密钥保管库访问策略
- 进入Azure密钥保管库的访问策略页面,添加你的应用注册(通过客户端ID查找),并授予**获取(Get)**密钥的权限
- 优化代码细节
- 移除
clientSecret末尾的多余空格(代码中"clientSecret Vaule "存在空格,会导致认证失败) - 可以将令牌请求的
resource参数替换为scope=https://vault.azure.net/.default(应用权限的标准范围格式),修改后的GetAccessToken方法如下:private static async Task<string> GetAccessToken(string tenantId, string clientId, string clientSecret) { using (var client = new HttpClient()) { // 使用scope参数替代resource,适配应用权限 var body = new StringContent($"grant_type=client_credentials&client_id={clientId}&client_secret={clientSecret}&scope=https://vault.azure.net/.default", Encoding.UTF8, "application/x-www-form-urlencoded"); // 使用v2.0端点更推荐 var response = await client.PostAsync($"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", body); response.EnsureSuccessStatusCode(); var jsonResponse = await response.Content.ReadAsStringAsync(); dynamic tokenResponse = JsonConvert.DeserializeObject(jsonResponse); return tokenResponse.access_token; } }
- 移除
解决方案二:改用设备码流(适合需用户交互的控制台场景,使用委派权限)
如果必须使用委派权限,需改用设备码授权流(让用户在浏览器中登录授权),修改GetAccessToken方法如下:
private static async Task<string> GetAccessToken(string tenantId, string clientId) { using (var client = new HttpClient()) { // 第一步:请求设备码 var deviceCodeRequest = new StringContent($"client_id={clientId}&scope=https://vault.azure.net/user_impersonation", Encoding.UTF8, "application/x-www-form-urlencoded"); var deviceCodeResponse = await client.PostAsync($"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/devicecode", deviceCodeRequest); deviceCodeResponse.EnsureSuccessStatusCode(); var deviceCodeJson = await deviceCodeResponse.Content.ReadAsStringAsync(); dynamic deviceCodeData = JsonConvert.DeserializeObject(deviceCodeJson); Console.WriteLine($"请打开链接:{deviceCodeData.verification_uri},输入代码:{deviceCodeData.user_code} 完成登录"); // 第二步:轮询获取令牌 while (true) { var tokenRequest = new StringContent($"grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id={clientId}&device_code={deviceCodeData.device_code}", Encoding.UTF8, "application/x-www-form-urlencoded"); var tokenResponse = await client.PostAsync($"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", tokenRequest); if (tokenResponse.IsSuccessStatusCode) { var tokenJson = await tokenResponse.Content.ReadAsStringAsync(); dynamic tokenData = JsonConvert.DeserializeObject(tokenJson); return tokenData.access_token; } var errorJson = await tokenResponse.Content.ReadAsStringAsync(); dynamic errorData = JsonConvert.DeserializeObject(errorJson); if (errorData.error == "authorization_pending") { await Task.Delay(Convert.ToInt32(deviceCodeData.interval) * 1000); continue; } throw new Exception($"获取令牌失败:{errorData.error_description}"); } } }
同时,Main方法中不再需要clientSecret,直接调用string accessToken = await GetAccessToken(tenantId, clientId);即可。
额外代码优化建议
- 统一JSON序列化库:代码中同时使用了
Newtonsoft.Json和System.Text.Json,建议选择其中一种(比如都用System.Text.Json)避免混淆 - 复用HttpClient:避免每次请求都创建新的HttpClient实例,可使用静态HttpClient或依赖注入管理
内容的提问来源于stack exchange,提问作者AskMe
相关产品推荐
相关产品推荐

