【问题标题】:Authenticate with Dynamics 365 from an Azure Function从 Azure 函数使用 Dynamics 365 进行身份验证
【发布时间】:2018-04-27 11:04:06
【问题描述】:

场景

我有一个在线托管的 Dynamics 365 v9 组织。我在我的 Dynamics 组织的不同租户的 Azure Function App 中托管了一组 Azure Functions。

我使用 Dynamics 插件注册工具创建了 web hooks,它在某些事件(例如在 Dynamics 中创建联系人时)通过其端点 URL 将数据发布到我的 Azure Functions。

Dynamics 365 和我的 Azure Functions 之间的身份验证是通过在 HTTP 请求的身份验证 HttpHeader 中传递 x-functions-key 值来实现的。

Azure Functions 以 RemoteExecutionContext 的形式从 Dynamics 中的事件接收数据,我可以使用以下代码读取该数据:

using System.Net;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    var jsonContent = await req.Content.ReadAsStringAsync();

    log.Info(jsonContent);

    return req.CreateResponse(HttpStatusCode.OK);
}

问题

然后,Azure 函数如何通过调用 Dynamics 365 组织进行身份验证以读取和写入数据?

我尝试过的

  1. Xrm 工具

最简单的身份验证方法是使用 Microsoft.Xrm.Tooling.Connector.dll 中的 CrmServiceClient。但是,我不一定有用户名和密码来提供 CrmServiceClient 的构造函数。也许可以通过 HTTP POST 请求安全地传递凭据?

  1. 应用用户

我已尝试在 Dynamics 中注册应用程序用户。我将客户端 ID 和客户端密码提供给我的 Azure Functions,但身份验证失败,因为用户与我的 Azure Functions 位于不同的租户中。

考虑的解决方案

收到的jsonContent 字符串的一个对象称为ParentContext。也许这可以重新用于向调用的 Dynamics 组织进行身份验证。

Marc Schweigert 建议使用 S2S,并在他的 AzureFunctionApp 存储库中提供了一个示例。如果我能让这种方法发挥作用,我会在此处发布解决方案。

【问题讨论】:

  • 应用程序用户是这种情况下的好方法。是的,您必须在 CRM Online 链接到的同一 AAD 租户中创建 AAD 应用程序。你试过吗?应用程序用户示例在这里stackoverflow.com/a/44136348/8053828

标签: c# azure dynamics-crm azure-functions dynamics-365


【解决方案1】:

我没想到您可以明智地使用“真实”用户凭据连接到 CRM。

我会使用服务帐户重新连接到 CRM。创建新的 CRM 用户特别是为此目的,如果您使用户非交互式,则不应使用许可证。然后,您可以使用该服务帐户的凭据通过 CrmServiceClient 连接到 CRM。或者看看Server to Server authentication

如果您能够向您的 Function App 提供用户 ID,则您可以通过 CRM 网络服务使用服务帐户impersonate“真实”用户。

要模拟用户,请在以下实例上设置 CallerId 属性 调用服务的 Web 方法之前的 OrganizationServiceProxy。

【讨论】:

    【解决方案2】:

    我最近做了类似的事情,但没有依赖 Azure 订阅身份验证功能来连接回 D365。在我的情况下,呼叫来自其他地方的 Azure 函数,但返回的连接没有什么不同。在任何这些情况下,身份验证都不会通过。如果 AAD 用户向您的 Function 应用程序进行身份验证,您仍需要使用应用程序用户连接到 D365,然后模拟呼叫您的用户。

    首先,确保您在 Azure AD 中的 App Registrations 下注册的应用程序属于“Web 应用程序/API”类型,而不是“Native”类型。编辑已注册应用的设置并确保以下内容:

    1. 不考虑应用程序 ID,我稍后将其称为 appId。
    2. 在“API 访问 - 所需权限”下,添加 Dynamics CRM Online (Microsoft.CRM) 而不是 Dynamics 365。
    3. 在“API 访问 - 密钥”下,创建一个具有适当有效期的密钥。如果您有多个功能/应用程序作为此“应用程序”连接回来,您可以创建多个键。我稍后会将此密钥称为“clientSecret”。

    如果“Keys”选项不可用,则您已注册了本机应用程序。

    我将 appId 和 clientSecret 存储在 Function App 的应用程序配置部分,并使用通常的 System.Configuration.ConfigurationManager.AppSettings 集合访问它们。

    以下示例使用对 AuthenticationParameters 的调用来查找权限和资源 URL,但您可以使用在线无数示例轻松手动构建这些 URL。我发现如果它们发生变化,它只会自行更新,所以以后的工作会更少。

    这些都是简单的例子,我只是在掩饰刷新令牌和所有这些东西的需要。

    然后使用 OData 访问 D365:

    string odataUrl = "https://org.crm6.dynamics.com/api/data/v8.2/"; // trailing slash actually matters
    string appId = "some-guid";
    string clientSecret = "some key";
    
    AuthenticationParameters authArg = AuthenticationParameters.CreateFromResourceUrlAsync(new Uri(odataUrl)).Result;
    AuthenticationContext authCtx = new AuthenticationContext(authArg.Authority);
    AuthenticationResult authRes = authCtx.AcquireTokenAsync(authArg.Resource, new ClientCredential(appId, clientSecret)).Result;
    
    using (HttpClient client = new HttpClient()) {
      client.TimeOut = TimeSpan.FromMinutes (2);
      client.DefaultRequestHeaders.Add("Authorization", authRes.CreateAuthorizationHeader ());
      using (HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, $"{odataUrl}accounts?$select=name&$top=10")) {
        using (HttpResponseMessage res = client.SendAsync(req).Result) {
          if (res.IsSuccessStatusCode) {
            Console.WriteLine(res.Content.ReadAsStringAsync().Result);
          }
          else {
            // cry
          }
        }
      }
    }
    

    如果您想使用组织服务和 LINQ 访问 D365,请使用以下内容。我花了一段时间才弄清楚的两个主要部分是那个奇怪的 organization.svc URL 的格式,以及使用 Microsoft.Xrm.Sdk.WebServiceClient.OrganizationWebProxyClient 而不是 Tooling:

    string odataUrl = "https://org.crm6.dynamics.com/xrmservices/2011/organization.svc/web?SdkClientVersion=8.2"; // don't question the url, just accept it.
    string appId = "some-guid";
    string clientSecret = "some key";
    
    AuthenticationParameters authArg = AuthenticationParameters.CreateFromResourceUrlAsync(new Uri(odataUrl)).Result;
    AuthenticationContext authCtx = new AuthenticationContext(authArg.Authority);
    AuthenticationResult authRes = authCtx.AcquireTokenAsync(authArg.Resource, new ClientCredential(appId, clientSecret)).Result;
    
    using (OrganizationWebProxyClient webProxyClient = new OrganizationWebProxyClient(new Uri(orgSvcUrl), false)) {
      webProxyClient.HeaderToken = authRes.AccessToken;
      using (OrganizationServiceContext ctx = new OrganizationServiceContext((IOrganizationService)webProxyClient)) {
        var accounts = (from i in ctx.CreateQuery("account") orderby i["name"] select i).Take(10);
        foreach (var account in accounts)
          Console.WriteLine(account["name"]);
      }
    }
    

    不确定您在 Webhook 注册中返回什么上下文,尚未尝试过,但只需确保 Authorization 标头中有一个不记名令牌通常会这样做,并且上面的两个示例以不同的方式注入它,所以您应该能够将这里需要的东西拼接在一起。

    【讨论】:

      【解决方案3】:

      这也是我很好奇的事情,但我没有机会对此进行实验。

      对于您的第二个选项,您是否已在目标 AAD 中注册了应用程序并获得了同意?

      https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/use-multi-tenant-server-server-authentication

      当他们同意时,您的注册应用程序将被添加到 Azure AD 企业应用程序列表中,并且可供 Azure AD 租户的用户使用。

      只有在管理员同意后,您才必须在订阅者的 Dynamics 365 租户中创建应用程序用户

      我认为访问问题的根源与应用程序的服务主体对象(目标租户的本地对象)有关

      服务主体对象

      https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-application-objects#service-principal-object

      为了访问受 Azure AD 租户保护的资源,需要访问的实体必须由安全主体表示。对于用户(用户主体)和应用程序(服务主体)都是如此。安全主体定义该租户中用户/应用程序的访问策略和权限。这启用了核心功能,例如在登录期间对用户/应用程序进行身份验证,以及在资源访问期间进行授权。

      将应用程序对象视为您的应用程序的全局表示以供所有租户使用,将服务主体视为本地表示以供在特定租户中使用。

      HTH

      -克里斯

      【讨论】:

        【解决方案4】:

        使用 S2S,您可以使用 AcquireToken 来检索 Bearer

          var clientcred = new ClientCredential(clientId, clientSecret);
                        AuthenticationContext authContext = new AuthenticationContext(aadInstance, false);
                        AuthenticationResult result = authContext.AcquireToken(organizationUrl, clientcred);
        
        
                        token = result.AccessToken;
                        ExpireDate = result.ExpiresOn.DateTime;
        
        
                        client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
        

        【讨论】:

          猜你喜欢
          • 2022-01-24
          • 2017-07-01
          • 1970-01-01
          • 2018-11-20
          • 1970-01-01
          • 2016-06-04
          • 1970-01-01
          • 1970-01-01
          • 2018-11-29
          相关资源
          最近更新 更多