You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在.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的应用权限,但你配置的是委派权限,两者不匹配,导致令牌没有足够权限访问密钥保管库。

解决方案一:改用应用权限(推荐后台控制台场景)

这是控制台应用访问密钥保管库的标准方式,无需用户交互:

  1. 启用并配置应用权限
    • 在Azure AD应用注册中,添加Key Vault的应用权限(选择Secrets.Get或Secrets.ReadAll等所需权限)
    • 点击授予管理员同意(必须完成此步骤,否则权限不生效)
  2. 配置密钥保管库访问策略
    • 进入Azure密钥保管库的访问策略页面,添加你的应用注册(通过客户端ID查找),并授予**获取(Get)**密钥的权限
  3. 优化代码细节
    • 移除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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 22:40:09