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

如何从Azure OAuth 2.0 Token(v2)端点获取Access Token?代码调用报403

如何从Azure OAuth 2.0 Token(v2)端点获取Access Token?

我尝试从端点 https://login.microsoftonline.com/{AzureTenantId}/oauth2/v2.0/authorize 获取Access Token,按照微软文档传递了client_id、scope、client_secret、grant_type、resource和audience参数,但调用时收到403 Forbidden响应。不过在Bruno工具中执行相同操作却能成功:

Bruno工具成功请求截图

但我的C#代码调用失败,代码如下:

public HttpStatusCode InvokeWebAPIPostJson(string apiBaseURL, string urlSuffix, SaveCommentModel saveCommentModel)
{
    var token = WebAPIControllerExtensions.GetAuthenticationTokenAsync(this.configuration, ApplicationConstants.AUTHORITY_BASEURI, ApplicationConstants.GRANT_TYPE, ApplicationConstants.SCOPE, ApplicationConstants.CONTENT_TYPE);
    double cltTimeout = ApplicationConstants.HTTPCLIENT_TIMEOUT;
    var url = apiBaseURL ?? string.Empty;

    using var httpRequestClient = new HttpClient();
    httpRequestClient.Timeout = TimeSpan.FromMinutes(cltTimeout);
    httpRequestClient.BaseAddress = new Uri(url);
    httpRequestClient.DefaultRequestHeaders.Accept.Clear();
    httpRequestClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    httpRequestClient.DefaultRequestHeaders.Add("Accept", "*/*");
    httpRequestClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
    string saveComment = JsonConvert.SerializeObject(saveCommentModel);
    var stringvar = "\"" + saveComment + "\"";
    StringContent jsonContent = new(saveComment, Encoding.UTF8, "application/json");

    var resp = httpRequestClient.PostAsync($"{urlSuffix}", jsonContent).Result;
    var statusCode = resp.StatusCode;
    return statusCode;
}

// 获取token的方法:
public static string GetAuthenticationTokenAsync(
    IConfiguration configuration, string authBaseURL, string grantTypeStr
    , string scopeStr, string contentType)
{
    var audienceVal = configuration["Keyvault:Audience"] ?? string.Empty;
    var clientIdVal = configuration["Keyvault:ClientId"] ?? string.Empty;
    var secretVal = configuration["Keyvault:Secret"] ?? string.Empty;
    var tenantVal = configuration["Keyvault:Tenant"] ?? string.Empty;

    var audienceId = configuration.GetValue<string>(audienceVal) ?? string.Empty;
    var client_id = configuration.GetValue<string>(clientIdVal) ?? string.Empty;
    var client_secret = configuration.GetValue<string>(secretVal) ?? string.Empty;
    var tenantId = configuration.GetValue<string>(tenantVal) ?? string.Empty;

    string authorityUri = $"{authBaseURL}{tenantId}/oauth2/v2.0/authorize";
    string grant_type = grantTypeStr;
    string scope = $"{scopeStr}";
    var list = new List<KeyValuePair<string, string>>();
    var values = new Dictionary<string, string>()
        {
            { "client_id", client_id },
            { "scope", $"{client_id}/.default" },
            { "client_secret", client_secret },
            { "grant_type", grant_type },
            { "resource", client_id },
            { "audience", audienceId }
        };

    list.AddRange(values);
    var content = new FormUrlEncodedContent(list);
    var request = new HttpRequestMessage
    {
        Method = HttpMethod.Post,
        RequestUri = new Uri(authorityUri),
        Content = content
    };

    request.Content.Headers.ContentType = new MediaTypeWithQualityHeaderValue(contentType) { CharSet = "UTF-8" };
    var clt = new HttpClient();
    var result = clt.PostAsync(authorityUri, content);
    var resultContent = result.Result.Content;
    var bearerData = resultContent.ReadAsStringAsync().Result;
    var bearerToken = JObject.Parse(bearerData)["access_token"].ToString();

    return bearerToken;
}

问题排查与修复方案

1. 错误的端点地址

你当前使用的/oauth2/v2.0/authorize是授权端点,用于获取授权码,并非直接获取Access Token的令牌端点。Azure OAuth 2.0 v2的令牌端点应为:
https://login.microsoftonline.com/{AzureTenantId}/oauth2/v2.0/token

2. 参数错误与冗余

  • v2端点不支持resource参数,这是v1端点的专属参数,需移除。
  • 若使用客户端凭据流(grant_type=client_credentials),scope应设置为目标API的受众(audience)加上/.default,而非当前客户端ID的/.default,除非你调用的是自身API。
  • 确保grant_type的值为client_credentials,这是服务间调用的正确授权类型。

3. 异步代码实现问题

你的GetAuthenticationTokenAsync方法使用.Result同步等待异步操作,极易导致死锁,应改为全异步实现;同时HttpClient应复用(建议注入单例),而非每次创建新实例。

修复后的代码示例

获取Token的异步方法

public static async Task<string> GetAuthenticationTokenAsync(
    IConfiguration configuration, string authBaseURL, string grantTypeStr, string scopeStr, string contentType)
{
    var audienceVal = configuration["Keyvault:Audience"] ?? string.Empty;
    var clientIdVal = configuration["Keyvault:ClientId"] ?? string.Empty;
    var secretVal = configuration["Keyvault:Secret"] ?? string.Empty;
    var tenantVal = configuration["Keyvault:Tenant"] ?? string.Empty;

    var audienceId = configuration.GetValue<string>(audienceVal) ?? string.Empty;
    var client_id = configuration.GetValue<string>(clientIdVal) ?? string.Empty;
    var client_secret = configuration.GetValue<string>(secretVal) ?? string.Empty;
    var tenantId = configuration.GetValue<string>(tenantVal) ?? string.Empty;

    // 使用正确的令牌端点
    string tokenUri = $"{authBaseURL}{tenantId}/oauth2/v2.0/token";
    
    var values = new Dictionary<string, string>()
    {
        { "client_id", client_id },
        { "scope", $"{audienceId}/.default" }, // 目标API受众 + /.default
        { "client_secret", client_secret },
        { "grant_type", grantTypeStr } // 确保值为"client_credentials"
        // 移除resource参数
    };

    var content = new FormUrlEncodedContent(values);
    content.Headers.ContentType = new MediaTypeWithQualityHeaderValue(contentType) { CharSet = "UTF-8" };

    using var clt = new HttpClient();
    var result = await clt.PostAsync(tokenUri, content);
    result.EnsureSuccessStatusCode(); // 主动抛出异常,便于排查错误

    var bearerData = await result.Content.ReadAsStringAsync();
    var bearerToken = JObject.Parse(bearerData)["access_token"].ToString();

    return bearerToken;
}

调用API的方法

public async Task<HttpStatusCode> InvokeWebAPIPostJson(string apiBaseURL, string urlSuffix, SaveCommentModel saveCommentModel)
{
    // 异步获取Token,避免同步阻塞
    var token = await WebAPIControllerExtensions.GetAuthenticationTokenAsync(this.configuration, ApplicationConstants.AUTHORITY_BASEURI, ApplicationConstants.GRANT_TYPE, ApplicationConstants.SCOPE, ApplicationConstants.CONTENT_TYPE);
    double cltTimeout = ApplicationConstants.HTTPCLIENT_TIMEOUT;
    var url = apiBaseURL ?? string.Empty;

    using var httpRequestClient = new HttpClient();
    httpRequestClient.Timeout = TimeSpan.FromMinutes(cltTimeout);
    httpRequestClient.BaseAddress = new Uri(url);
    httpRequestClient.DefaultRequestHeaders.Accept.Clear();
    httpRequestClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    httpRequestClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
    
    string saveComment = JsonConvert.SerializeObject(saveCommentModel);
    StringContent jsonContent = new StringContent(saveComment, Encoding.UTF8, "application/json");

    var resp = await httpRequestClient.PostAsync($"{urlSuffix}", jsonContent);
    return resp.StatusCode;
}

额外注意事项

  • 确保Azure AD应用注册中,当前客户端已被授予目标API的应用权限,且完成了管理员同意。
  • 检查配置中的Audience是否为目标API的正确受众(通常是API的客户端ID或应用ID URI)。
  • 始终使用await处理异步操作,避免用.Result或.Wait()引发死锁。

内容的提问来源于stack exchange,提问作者User536

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 00:50:58