Проверка подлинности с помощью Dynamics 365 из функции Azure

сценарий

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

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

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

Функции 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 Tooling

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

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

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

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

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

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

4 ответа

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

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

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

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

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

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

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

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

Я сохранил appId и clientSecret в разделе конфигурации приложения Function Function и получил к ним доступ, используя обычную коллекцию 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, еще не пробовал, но просто удостоверьтесь, что в заголовке авторизации обычно есть токен-носитель, и два приведенных выше примера внедряют его по-разному, так что вы сможете соедините вместе то, что нужно отсюда.

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

Для вашего второго варианта вы зарегистрировали приложение и дали согласие в целевой 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

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

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

НТН

-Крис

Используя S2S, вы можете использовать AcquireToken для получения носителя

  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);
Другие вопросы по тегам