Аутентификация с Dynamics 365 из функции Azure

Сценарий

У меня есть организация Dynamics 365 v9, размещенная в Интернете. У меня есть набор функций Azure, размещенный в приложении функций Azure в другом клиенте, нежели моя организация Dynamics.

Я создал веб-хуки, используя Инструмент регистрации подключаемого модуля Dynamics, который при определенных событиях (например, при создании контакта в Dynamics) отправляет данные POST в мои функции Azure через свои URL-адреса конечных точек.

Аутентификация между Dynamics 365 и моими функциями Azure достигается путем передачи значения x-functions-key в HttpHeader аутентификации HTTP-запроса.

Функции Azure получают данные из события в Dynamics в виде RemoteExecutionContext, который я могу прочитать, используя следующий код:

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

Самый простой способ аутентификации - использовать CrmServiceClient из Microsoft.Xrm.Tooling.Connector.dll. Однако мне не обязательно иметь имя пользователя и пароль для предоставления конструктора CrmServiceClient. Возможно, учетные данные можно безопасно передать через запрос HTTP POST?

  1. Пользователь приложения

Я пробовал зарегистрировать пользователя приложения в Dynamics. Я предоставляю идентификатор клиента и секрет клиента для своих функций Azure, но проверка подлинности не выполняется, поскольку пользователь находится в другом клиенте для моих функций Azure.

Рассмотренные решения

Один объект полученной строки jsonContent называется ParentContext. Возможно, это можно будет повторно использовать для повторной аутентификации в вызывающей организации Dynamics.

Марк Швайгерт рекомендовал использовать S2S и предоставил образец для своего AzureFunctionApp репозиторий. Если мне удастся заставить этот подход работать, я опубликую решение здесь.


person Dave Clark    schedule 27.04.2018    source источник
comment
Пользователь приложения - хороший подход в этом сценарии. Да, вам необходимо создать приложение AAD в том же клиенте AAD, с которым связана CRM Online. Вы пробовали это? Пример пользователя приложения здесь stackoverflow.com/a/44136348/8053828   -  person andresm53    schedule 27.04.2018


Ответы (4)


Я бы не подумал, что вы можете разумно использовать «настоящие» учетные данные пользователей для подключения к CRM.

Я бы использовал учетную запись службы, чтобы снова подключиться к CRM. Создайте нового пользователя CRM специально для этой цели, если вы сделаете пользователя неинтерактивным, вам не нужно использовать лицензию. Затем вы можете использовать учетные данные этой учетной записи службы для подключения к CRM с помощью CrmServiceClient. В качестве альтернативы взгляните на аутентификацию с сервера на сервер.

Если вы можете предоставить идентификатор пользователя своему приложению-функции, вы используете учетную запись службы для выдавать себя за" настоящих "пользователей через веб-службы CRM.

Чтобы выдать себя за пользователя, установите свойство CallerId в экземпляре OrganizationServiceProxy перед вызовом веб-методов службы.

person James Wood    schedule 27.04.2018

Недавно я сделал нечто подобное, но не полагаясь на функцию аутентификации подписки Azure для подключения к D365. В моем случае вызовы к функциям Azure приходили из других мест, но обратное соединение не изменилось. Аутентификация НЕ проходит ни в одном из этих случаев. Если пользователь AAD аутентифицируется в вашем приложении-функции, вам все равно необходимо подключиться к D365 с помощью пользователя приложения, а затем выдать себя за пользователя, который вам позвонил.

Во-первых, убедитесь, что приложение, которое вы зарегистрировали в Azure AD в разделе «Регистрация приложений», относится к типу «Веб-приложение / API», а не «Собственное». Измените настройки зарегистрированного приложения и убедитесь в следующем:

  1. Не принимайте во внимание идентификатор приложения, который я позже буду называть appId.
  2. В разделе «Доступ к API - необходимые разрешения» добавьте Dynamics CRM Online (Microsoft.CRM), а НЕ Dynamics 365.
  3. В разделе «Доступ к API - Ключи» создайте ключ с соответствующим сроком действия. Вы можете создать несколько ключей, если у вас есть несколько функций / приложений, которые подключаются обратно как это «Приложение». Позже я буду называть этот ключ clientSecret.

Если параметр «Ключи» недоступен, вы зарегистрировали собственное приложение.

Я сохранил appId и clientSecret в разделе конфигурации приложения приложения-функции и получил к ним доступ, используя обычную коллекцию System.Configuration.ConfigurationManager.AppSettings.

В приведенных ниже примерах используется вызов AuthenticationParameters для поиска URL-адресов органов власти и ресурсов, но вы также можете легко создать эти URL-адреса вручную, используя бесчисленные примеры в Интернете. Я считаю, что это просто обновится, если они когда-либо изменятся, так что меньше работы позже.

Это простые примеры, и я замалчиваю необходимость обновлять токены и все такое.

Затем для доступа к D365 с помощью OData:

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
      }
    }
  }
}

Если вы хотите получить доступ к D365 с помощью службы организации и LINQ, используйте следующее. Две основные части, на которые мне потребовалось некоторое время, - это формат этого странно выглядящего URL-адреса organization.svc и использование 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, еще не пробовал, но просто убедился, что токен-носитель в заголовке авторизации обычно делает это, и два приведенных выше примера вводят его по-разному, чтобы вы могли соедините вместе то, что нужно отсюда.

person Alexander Liffers    schedule 11.05.2018

Мне это тоже интересно, но у меня не было возможности поэкспериментировать с этим.

Для вашего второго варианта зарегистрировали ли вы приложение и дали ли согласие в целевом AAD?

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

Когда они предоставят согласие, ваше зарегистрированное приложение будет добавлено в список приложений Azure AD Enterprise и станет доступным для пользователей клиента Azure AD.

Только после того, как администратор предоставит согласие, вы должны создать пользователя приложения в клиенте Dynamics 365 подписчика.

Я считаю, что корень проблемы с доступом связан с основным объектом службы приложения (локальный объект для целевого клиента)

Объект-участник службы

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

Чтобы получить доступ к ресурсам, которые защищены клиентом Azure AD, объект, которому требуется доступ, должен быть представлен участником безопасности. Это верно как для пользователей (участник-пользователь), так и для приложений (субъект-служба). Участник безопасности определяет политику доступа и разрешения для пользователя / приложения в этом клиенте. Это позволяет использовать основные функции, такие как аутентификация пользователя / приложения во время входа в систему и авторизация во время доступа к ресурсам.

Рассматривайте объект приложения как глобальное представление вашего приложения для использования всеми арендаторами, а субъект-службу - как локальное представление для использования в конкретном арендаторе.

HTH

-Крис

person Chris Fernando    schedule 27.04.2018

Используя 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);
person viktorh    schedule 10.09.2018