Проверка подлинности с помощью 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 для чтения и записи данных?
Что я пробовал
- Xrm Tooling
Простейшим способом проверки подлинности будет использование CrmServiceClient из Microsoft.Xrm.Tooling.Connector.dll. Тем не менее, у меня не обязательно есть имя пользователя и пароль для предоставления конструктора CrmServiceClient. Возможно, учетные данные можно было бы безопасно передать через запрос HTTP POST?
- Пользователь приложения
Я пытался зарегистрировать пользователя приложения в динамике. Я предоставляю идентификатор клиента и секрет клиента для своих функций 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", а не к "Собственному". Измените настройки зарегистрированного приложения и убедитесь, что:
- Не берите идентификатор приложения, который я буду называть позже appId.
- В разделе "Доступ к API - необходимые разрешения" добавьте Dynamics CRM Online (Microsoft.CRM) и NOT Dynamics 365.
- В разделе "Доступ к 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?
Когда они дадут согласие, ваше зарегистрированное приложение будет добавлено в список приложений 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);