如何从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工具中执行相同操作却能成功:

但我的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
相关产品推荐
相关产品推荐

