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

D365/PowerApps插件中能否获取Azure AD令牌调用受保护外部Azure服务?

可以实现!以下是两种场景的具体方案

作为D365插件开发的老玩家,我可以明确告诉你:完全能在D365插件中获取受Azure AD保护的外部服务令牌,尤其是你提到的以用户身份运行时代表上下文获取的场景,核心是用OAuth 2.0的On-Behalf-Of(OBO)流程,下面分两种常见场景详细说明:

一、代表插件上下文用户获取令牌(委派权限)

这是你重点关注的场景——插件以当前操作的D365用户身份,获取目标Azure服务的访问令牌,步骤如下:

前提条件

  1. 配置Azure AD应用注册
    • 为你的目标Azure服务(Functions/WebAPP)创建应用注册,记录其客户端ID和API范围(比如api://<function-client-id>/user_impersonation)。
    • 为Dynamics 365的服务主体(或者你插件专用的应用注册)授予目标服务的委派权限(比如user_impersonation),并完成权限同意(管理员同意或用户同意,根据你的租户策略)。
  2. 插件权限配置
    • 确保插件注册时勾选了允许网络访问(沙箱插件默认禁止出站请求,必须手动开启)。
    • 插件运行的用户需要有足够的D365权限,同时该用户在Azure AD中能访问目标服务。

具体实现代码示例

核心是先获取当前用户的D365身份令牌,再通过OBO流程交换成目标服务的令牌:

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Collections.Generic;
using Newtonsoft.Json;
using Microsoft.Xrm.Sdk;

public class ExternalServiceTokenPlugin : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
        var serviceProxy = (IOrganizationService)serviceProvider.GetService(typeof(IOrganizationService));
        
        // 1. 获取当前用户的D365身份令牌
        var authCredentials = ((OrganizationServiceProxy)serviceProxy).AuthenticationCredentials;
        string userJwtToken = authCredentials.SecurityToken as string;
        
        if (string.IsNullOrEmpty(userJwtToken))
        {
            throw new InvalidPluginExecutionException("无法获取当前用户的身份令牌");
        }

        // 2. 调用OBO流程交换目标服务令牌
        var targetToken = GetOnBehalfOfTokenAsync(userJwtToken).GetAwaiter().GetResult();
        
        // 3. 使用令牌访问外部服务
        CallExternalService(targetToken).GetAwaiter().GetResult();
    }

    private async Task<string> GetOnBehalfOfTokenAsync(string userToken)
    {
        // 替换为你的租户ID、插件用的应用注册ID和密钥、目标服务范围
        string tenantId = "your-tenant-id";
        string clientId = "plugin-app-client-id";
        string clientSecret = "plugin-app-client-secret";
        string targetScope = "api://your-function-app-client-id/user_impersonation";

        var tokenEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token";
        
        using (var client = new HttpClient())
        {
            var formData = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
                new KeyValuePair<string, string>("assertion", userToken),
                new KeyValuePair<string, string>("scope", targetScope),
                new KeyValuePair<string, string>("client_id", clientId),
                new KeyValuePair<string, string>("client_secret", clientSecret),
                new KeyValuePair<string, string>("requested_token_use", "on_behalf_of")
            });

            var response = await client.PostAsync(tokenEndpoint, formData);
            response.EnsureSuccessStatusCode();
            
            var responseJson = await response.Content.ReadAsStringAsync();
            var tokenResult = JsonConvert.DeserializeObject<TokenResponse>(responseJson);
            
            return tokenResult.AccessToken;
        }
    }

    private async Task CallExternalService(string accessToken)
    {
        using (var client = new HttpClient())
        {
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
            var response = await client.GetAsync("https://your-function-app.azurewebsites.net/api/your-function");
            response.EnsureSuccessStatusCode();
            
            // 处理外部服务响应
            var content = await response.Content.ReadAsStringAsync();
            // ...
        }
    }

    // 辅助类用于反序列化令牌响应
    private class TokenResponse
    {
        [JsonProperty("access_token")]
        public string AccessToken { get; set; }
    }
}

二、插件以服务主体身份获取令牌(应用权限)

如果不需要代表用户,仅以插件自身的服务主体身份访问外部服务,可以用客户端凭证流程,步骤更简单:

前提条件

  1. 创建一个专用的Azure AD应用注册(服务主体),授予目标服务的应用权限,并完成管理员同意。
  2. 在D365中注册该服务主体为应用用户,赋予插件运行所需的D365权限。

代码示例

使用MSAL库(需在插件中引用Microsoft.Identity.Client程序集):

using Microsoft.Identity.Client;

private async Task<string> GetAppOnlyTokenAsync()
{
    string tenantId = "your-tenant-id";
    string clientId = "plugin-service-principal-client-id";
    string clientSecret = "plugin-service-principal-client-secret";
    string targetScope = "api://your-function-app-client-id/.default";

    var app = ConfidentialClientApplicationBuilder
        .Create(clientId)
        .WithClientSecret(clientSecret)
        .WithAuthority(new Uri($"https://login.microsoftonline.com/{tenantId}"))
        .Build();

    var result = await app.AcquireTokenForClient(new[] { targetScope })
        .ExecuteAsync();
    
    return result.AccessToken;
}

关键注意事项

  • 网络访问:沙箱插件必须开启“允许网络访问”,否则无法调用Azure AD和外部服务。
  • 安全存储:绝对不要硬编码client_secret,建议用D365的环境变量或者Azure Key Vault(插件中可通过Key Vault SDK访问)存储敏感信息。
  • 令牌有效期:Azure AD令牌默认有效期1小时,插件是短生命周期任务,无需缓存,每次获取即可。
  • 权限验证:确保所有权限都已正确配置并完成同意,否则会返回401/403错误。

内容的提问来源于stack exchange,提问作者Poul K. Sørensen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 07:27:38