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

如何通过HttpClient生成Azure AD v2自定义Scope的Client Credentials令牌?

问题描述

我正在学习为聊天机器人生成OAuth服务令牌,现有代码可成功获取适用于MS Graph API的默认Scope令牌。现需生成带自定义Scope的令牌以调用外部服务(非MS Graph API),该自定义Scope已在Azure租户配置为api://botid-GUID/access_as_user,且通过OAuth工具生成的对应令牌可用。但修改代码中scope参数为该自定义Scope后,使用client_credentials授权类型调用失败,请问如何用类似HttpClient的方式生成该自定义Scope令牌?

原成功获取Graph令牌代码

private async Task<string> GetGraphTokenAsync()
{
    var dict = new Dictionary<string, string>();
    dict.Add("client_id", _graphTokenSettings.ClientId);
    dict.Add("client_secret", _graphTokenSettings.ClientSecret);
    dict.Add("scope", "https://graph.microsoft.com/.default");
    dict.Add("grant_type", "client_credentials");

    string gUrl = $"https://login.microsoftonline.com/{_graphTokenSettings.Tenant}/oauth2/v2.0/token";

    var client = new HttpClient();
    var req = new HttpRequestMessage(HttpMethod.Post, gUrl) { Content = new FormUrlEncodedContent(dict) };

    var httpResponseFromService = await client.SendAsync(req);
    httpResponseFromService.EnsureSuccessStatusCode();

    if (httpResponseFromService.Content is object
        && httpResponseFromService.Content.Headers.ContentType.MediaType == "application/json")
    {
        string stringFromservice = await httpResponseFromService.Content.ReadAsStringAsync();
        JObject tokenresponse = JsonConvert.DeserializeObject<JObject>(stringFromservice);
        string token = tokenresponse["access_token"].Value<string>();
        return token;
    }
    else
    {
        _logger.LogError($"Cannot get token for Microsoft Graph. httpResponseFromService.Content:{httpResponseFromService.Content}" );
        throw new Exception("Cannot get token for Microsoft Graph.");
    }
}

修改后的参数(调用失败)

dict.Add("client_id", _graphTokenSettings.ClientId);
dict.Add("client_secret", _graphTokenSettings.ClientSecret);
dict.Add("scope", "api://botid-GUID/access_as_user");
dict.Add("grant_type", "client_credentials");
解决方案

使用client_credentials授权类型调用自定义Scope时,不能直接指定access_as_user这类用户级Scope——该授权类型属于服务到服务模式,仅支持目标API的应用权限,而非面向用户的委派权限。

步骤1:检查Azure配置

  • 确认代码中client_id对应的Azure AD应用,已被授予目标API(api://botid-GUID)的应用权限,且权限状态为“已授予管理员同意”。
  • 目标API的应用注册中,需确保已添加对应的应用权限(而非委派权限),并将其暴露给客户端应用。

步骤2:调整Scope参数

client_credentials模式下,自定义API的Scope需使用{API标识符}/.default格式,替换原有的委派权限Scope。修改后的参数为:

dict.Add("scope", "api://botid-GUID/.default");

调整后的完整代码

private async Task<string> GetCustomApiTokenAsync()
{
    var dict = new Dictionary<string, string>();
    dict.Add("client_id", _graphTokenSettings.ClientId);
    dict.Add("client_secret", _graphTokenSettings.ClientSecret);
    // 使用服务到服务模式的默认Scope
    dict.Add("scope", "api://botid-GUID/.default");
    dict.Add("grant_type", "client_credentials");

    string tokenUrl = $"https://login.microsoftonline.com/{_graphTokenSettings.Tenant}/oauth2/v2.0/token";

    using var client = new HttpClient();
    var request = new HttpRequestMessage(HttpMethod.Post, tokenUrl) 
    { 
        Content = new FormUrlEncodedContent(dict) 
    };

    var response = await client.SendAsync(request);
    // 捕获错误信息便于排查
    if (!response.IsSuccessStatusCode)
    {
        var errorContent = await response.Content.ReadAsStringAsync();
        _logger.LogError($"获取自定义API令牌失败: {errorContent}");
        throw new Exception($"获取令牌失败,状态码: {response.StatusCode},错误信息: {errorContent}");
    }

    var responseContent = await response.Content.ReadAsStringAsync();
    JObject tokenResponse = JsonConvert.DeserializeObject<JObject>(responseContent);
    return tokenResponse["access_token"].Value<string>();
}

关键说明

  • access_as_user属于委派权限,仅适用于authorization_code或password这类需要用户参与的授权流程,client_credentials模式下无法使用。
  • 若需模拟用户身份调用API,应采用On-Behalf-Of (OBO) 流程,而非client_credentials。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 15:30:54