D365/PowerApps插件中能否获取Azure AD令牌调用受保护外部Azure服务?
可以实现!以下是两种场景的具体方案
作为D365插件开发的老玩家,我可以明确告诉你:完全能在D365插件中获取受Azure AD保护的外部服务令牌,尤其是你提到的以用户身份运行时代表上下文获取的场景,核心是用OAuth 2.0的On-Behalf-Of(OBO)流程,下面分两种常见场景详细说明:
一、代表插件上下文用户获取令牌(委派权限)
这是你重点关注的场景——插件以当前操作的D365用户身份,获取目标Azure服务的访问令牌,步骤如下:
前提条件
- 配置Azure AD应用注册
- 为你的目标Azure服务(Functions/WebAPP)创建应用注册,记录其
客户端ID和API范围(比如api://<function-client-id>/user_impersonation)。 - 为Dynamics 365的服务主体(或者你插件专用的应用注册)授予目标服务的委派权限(比如
user_impersonation),并完成权限同意(管理员同意或用户同意,根据你的租户策略)。
- 为你的目标Azure服务(Functions/WebAPP)创建应用注册,记录其
- 插件权限配置
- 确保插件注册时勾选了允许网络访问(沙箱插件默认禁止出站请求,必须手动开启)。
- 插件运行的用户需要有足够的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; } } }
二、插件以服务主体身份获取令牌(应用权限)
如果不需要代表用户,仅以插件自身的服务主体身份访问外部服务,可以用客户端凭证流程,步骤更简单:
前提条件
- 创建一个专用的Azure AD应用注册(服务主体),授予目标服务的应用权限,并完成管理员同意。
- 在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
相关产品推荐
相关产品推荐

