Независимо от применяемой технологии SOAP / JSON отдельной задачей стоит аутенфикация внешнего приложения при подключении к D365FO.
Для локальной (On premise) версии аутенфикация осуществляется через логин / пароль, которые проверяются службой ADFS (Active Directory Federation Services)
Для облачной (Cloud) версии аутенфикация осуществляется через токен, т.е. большую текстовую строку, которая возвращается в приложение службой аутенфикации. Токен вычисляется на основе 4-х параметров:
- Tenant: Тенант (а точнее конкретно в данном случае - домен учетной записи пользователя, т.е. если пользователь D365FO, под которым будет обращение к веб-сервису - ассоциирован с учетной записью web-service@ivan-petrov.ru, то в данном случае потребуется значение "ivan-petrov.ru")
- Resource: Ресурс, т.е. URL системы; для OneBox - это "https://usnconeboxax1aos.cloud.onebox.dynamics.com"
- AppId: идентификатор приложения в Azure Active Directory (AAD)
- AppSecret: секретный идентификатор, который генерируется в приложении в AAD
Образец кода для аутенфикации лежит в
https://github.com/microsoft/Dynamic...OAuthHelper.cs в методе GetAuthenticationHeader.
X++:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AuthenticationUtility
{
public class OAuthHelper
{
/// <summary>
/// The header to use for OAuth authentication.
/// </summary>
public const string OAuthHeader = "Authorization";
/// <summary>
/// Retrieves an authentication header from the service.
/// </summary>
/// <returns>The authentication header for the Web API call.</returns>
public static string GetAuthenticationHeader(bool useWebAppAuthentication = false)
{
string aadTenant = ClientConfiguration.Default.ActiveDirectoryTenant;
string aadClientAppId = ClientConfiguration.Default.ActiveDirectoryClientAppId;
string aadClientAppSecret = ClientConfiguration.Default.ActiveDirectoryClientAppSecret;
string aadResource = ClientConfiguration.Default.ActiveDirectoryResource;
AuthenticationContext authenticationContext = new AuthenticationContext(aadTenant, false);
AuthenticationResult authenticationResult;
if (useWebAppAuthentication)
{
if (string.IsNullOrEmpty(aadClientAppSecret))
{
Console.WriteLine("Please fill AAD application secret in ClientConfiguration if you choose authentication by the application.");
throw new Exception("Failed OAuth by empty application secret.");
}
try
{
// OAuth through application by application id and application secret.
var creadential = new ClientCredential(aadClientAppId, aadClientAppSecret);
authenticationResult = authenticationContext.AcquireTokenAsync(aadResource, creadential).Result;
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Failed to authenticate with AAD by application with exception {0} and the stack trace {1}", ex.ToString(), ex.StackTrace));
throw new Exception("Failed to authenticate with AAD by application.");
}
}
else
{
// OAuth through username and password.
string username = ClientConfiguration.Default.UserName;
string password = ClientConfiguration.Default.Password;
if (string.IsNullOrEmpty(password))
{
Console.WriteLine("Please fill user password in ClientConfiguration if you choose authentication by the credential.");
throw new Exception("Failed OAuth by empty password.");
}
try
{
// Get token object
var userCredential = new UserPasswordCredential(username, password); ;
authenticationResult = authenticationContext.AcquireTokenAsync(aadResource, aadClientAppId, userCredential).Result;
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Failed to authenticate with AAD by the credential with exception {0} and the stack trace {1}", ex.ToString(), ex.StackTrace));
throw new Exception("Failed to authenticate with AAD by the credential.");
}
}
// Create and get JWT token
return authenticationResult.CreateAuthorizationHeader();
}
}
}
Его вызов с параметром true авторизует внешнее приложение по токену. Параметр false - по логину и паролю. Параметры подключения прописываются в файле
https://github.com/microsoft/Dynamic...nfiguration.cs
X++:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AuthenticationUtility
{
public partial class ClientConfiguration
{
public static ClientConfiguration Default { get { return ClientConfiguration.OneBox; } }
public static ClientConfiguration OneBox = new ClientConfiguration()
{
// You only need to populate this section if you are logging on via a native app. For Service to Service scenarios in which you e.g. use a service principal you don't need that.
UriString = "https://usnconeboxax1aos.cloud.onebox.dynamics.com/",
UserName = "tusr1@TAEOfficial.ccsctp.net",
// Insert the correct password here for the actual test.
Password = "",
// You need this only if you logon via service principal using a client secret. See: [url]https://docs.microsoft.com/en-us/dynamics365/unified-operations/dev-itpro/data-entities/services-home-page[/url] to get more data on how to populate those fields.
// You can find that under AAD in the azure portal
ActiveDirectoryResource = "https://usnconeboxax1aos.cloud.onebox.dynamics.com", // Don't have a trailing "/". Note: Some of the sample code handles that issue.
ActiveDirectoryTenant = "https://login.windows-ppe.net/TAEOfficial.ccsctp.net", // Some samples: [url]https://login.windows.net/yourtenant.onmicrosoft.com[/url], [url]https://login.windows.net/microsoft.com[/url]
ActiveDirectoryClientAppId = "d8a9a121-b463-41f6-a86c-041272bdb340",
// Insert here the application secret when authenticate with AAD by the application
ActiveDirectoryClientAppSecret = "",
// Change TLS version of HTTP request from the client here
// Ex: TLSVersion = "1.2"
// Leave it empty if want to use the default version
TLSVersion = "",
};
public string TLSVersion { get; set; }
public string UriString { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string ActiveDirectoryResource { get; set; }
public String ActiveDirectoryTenant { get; set; }
public String ActiveDirectoryClientAppId { get; set; }
public string ActiveDirectoryClientAppSecret { get; set; }
}
}
Поскольку OneBox - виртуалка предполагает подключение к AAD, а не к ADFS - я опишу ниже схему настройки AAD для подключения через токен (авторизацию по логину и паролю с ходу у меня не получилось сделать).
Я изначально предполагаю, что имеется виртуалка OneBox, пользователь Admin которой сопоставлен с какой-нибудь учетной записью типа
vasya@ivan-petrov.ru и есть возможность (в т.ч. права) под какой-нибудь учетной записью из домена @ivan-petrov.ru сделать настройки в Azure Active Directory.
Итак, заходим на
https://portal.azure.com/ и выбираем ярлык Azure Active Directory
Слева выбираем пункт Регистрация приложений, жмем кнопку Новая регистрация и указываем имя приложения. Впоследствии мы свяжем данное приложение с пользователем, под которым внешнее приложение будет подключаться к нашему экземпляру системы D365FO, поэтому я указываю в качестве названия приложения - название нашего веб-сервиса

Больше ничего указывать не обязательно, поэтому можно нажать на кнопку Зарегистрировать.
После этого у нас откроется страничка, где уже будет нашему приложению присвоен Идентификатор приложения, он же Application Id (AppId), который мы впоследствии укажем в "четверке" параметров подключения в поле AppId
Далее этому приложению нужно добавить секретный идентификатор, тот - который у нас будет указан в поле AppSecretId. Для этого, не уходя со странички приложения выбираем слева пункт "Сертификаты и секреты"

И нажимаем кнопку "Новый секрет клиента"

Указываем описание (любое) и срок действия - 1, 2 года или бессрочно и жмем кнопку Добавить

Мы получаем секретный идентификатор, который впоследствии и нужно будет подставить в поле AppSecret. Его желательно сразу сохранить, т.к. потом на этой странице он уже отображаться не будет и его придется заново генерировать (в то время, как идентификатор приложения можно будет увидеть при повторном открытии страницы).

Теперь нам нужно ассоциировать наше приложение с пользователем в D365FO. Для этого нужно зайти в систему в модуль Администрирование и выбрать пункт меню Настройка - Приложения Azure Active Directory

И настроить соответствие между идентификатором приложения (AppId, код клиента) и пользователем системы (пользователь может и должен быть ограничен в правах)
Дело осталось за малым - подготовить проект на C#, используя заготовки Microsoft и полученные идентификаторы AppId и AppSecret.
На всякий случай отмечу, что в данном примере у меня получились следующие значения:
Цитата:
AppId: "d4a93f6d-1d46-4e33-a258-3aa3ae7cb819"
AppSecretId: "J6Z86h8.JhXxYH.43TM5Bap7~_UZ29wI_R"
Tenant: "ivan-petrov.ru"
Resource: "https://usnconeboxax1aos.cloud.onebox.dynamics.com"
Итоговый файл ClientConfiguration у нас получится такой:
X++:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AuthenticationUtility
{
public partial class ClientConfiguration
{
public static ClientConfiguration Default { get { return ClientConfiguration.OneBox; } }
public static ClientConfiguration OneBox = new ClientConfiguration()
{
// You only need to populate this section if you are logging on via a native app. For Service to Service scenarios in which you e.g. use a service principal you don't need that.
UriString = "",
UserName = "",
// Insert the correct password here for the actual test.
Password = "",
// You need this only if you logon via service principal using a client secret. See: [url]https://docs.microsoft.com/en-us/dynamics365/unified-operations/dev-itpro/data-entities/services-home-page[/url] to get more data on how to populate those fields.
// You can find that under AAD in the azure portal
ActiveDirectoryResource = "https://usnconeboxax1aos.cloud.onebox.dynamics.com", // Don't have a trailing "/". Note: Some of the sample code handles that issue.
ActiveDirectoryTenant = "https://login.windows.net/ivan-petrov.ru", // Some samples: [url]https://login.windows.net/yourtenant.onmicrosoft.com[/url], [url]https://login.windows.net/microsoft.com[/url]
ActiveDirectoryClientAppId = "d4a93f6d-1d46-4e33-a258-3aa3ae7cb819",
// Insert here the application secret when authenticate with AAD by the application
ActiveDirectoryClientAppSecret = "J6Z86h8.JhXxYH.43TM5Bap7~_UZ29wI_R",
// Change TLS version of HTTP request from the client here
// Ex: TLSVersion = "1.2"
// Leave it empty if want to use the default version
TLSVersion = "",
};
public string TLSVersion { get; set; }
public string UriString { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string ActiveDirectoryResource { get; set; }
public String ActiveDirectoryTenant { get; set; }
public String ActiveDirectoryClientAppId { get; set; }
public string ActiveDirectoryClientAppSecret { get; set; }
}
}
Он отличается от оригинального:
- Отсутствием значений переменных UserName, Password и UriString
- Заполненными значениями переменных ActiveDirectoryResource (URL нашей системы D365FO), ActiveDirectoryTenant (строка "https://login.windows.net/", к которой добавлен наш домен ivan-petrov.ru), ActiveDirectoryClientAppId и ActiveDirectoryClientAppSecret, полученных при регистрации приложения в Azure AD