Получение членства в группах для пользователей из Azure AD требует совсем немного, чем просто «пары строк кода», поэтому я решил поделиться тем, что в конечном итоге сработало для меня, чтобы сохранить других на несколько дней, потянув и забивая головой.
Давайте начнем с добавления следующих зависимостей в project.json:
"dependencies": {
...
"Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.8",
"Microsoft.Azure.ActiveDirectory.GraphClient": "2.0.2"
}
Первый необходимо, как нам нужно, чтобы проверить подлинность нашего приложения для того, чтобы иметь возможность получить доступ к AAD Graph API. Вторая - это клиентская библиотека Graph API, которую мы будем использовать для запросов пользователей. Само собой разумеется, что версии действительны только на момент написания этой статьи и могут измениться в будущем.
Далее в методе Configure() класса запуска, возможно, прежде чем мы настроить аутентификацию OpenID Connect, мы создаем клиента API Graph следующим образом:
var authContext = new AuthenticationContext("https://login.microsoftonline.com/<your_directory_name>.onmicrosoft.com");
var clientCredential = new ClientCredential("<your_b2c_app_id>", "<your_b2c_secret_app_key>");
const string AAD_GRAPH_URI = "https://graph.windows.net";
var graphUri = new Uri(AAD_GRAPH_URI);
var serviceRoot = new Uri(graphUri, "<your_directory_name>.onmicrosoft.com");
this.aadClient = new ActiveDirectoryClient(serviceRoot, async() => await AcquireGraphAPIAccessToken(AAD_GRAPH_URI, authContext, clientCredential));
ВНИМАНИЕ: НЕ жестко прописать секретный ключ приложения, но вместо этого сохраните его в надежном месте. Ну, ты уже это знал, да? :)
Асинхронный метод AcquireGraphAPIAccessToken(), который мы передали конструктору клиента AD, будет вызываться по мере необходимости, когда клиенту необходимо получить токен аутентификации. Вот что метод выглядит следующим образом:
private async Task<string> AcquireGraphAPIAccessToken(string graphAPIUrl, AuthenticationContext authContext, ClientCredential clientCredential)
{
AuthenticationResult result = null;
var retryCount = 0;
var retry = false;
do
{
retry = false;
try
{
// ADAL includes an in-memory cache, so this will only send a request if the cached token has expired
result = await authContext.AcquireTokenAsync(graphAPIUrl, clientCredential);
}
catch (AdalException ex)
{
if (ex.ErrorCode == "temporarily_unavailable")
{
retry = true;
retryCount++;
await Task.Delay(3000);
}
}
} while (retry && (retryCount < 3));
if (result != null)
{
return result.AccessToken;
}
return null;
}
Обратите внимание, что он имеет встроенный механизм повтора для обработки переходных условий, которые вы можете захотеть адаптировать к потребностям вашего приложения.
Теперь, когда мы позаботились о проверке подлинности приложения и настройке клиента AD, мы можем перейти к событиям OpenIdConnect, чтобы, наконец, использовать его. Назад в методе Configure(), где мы обычно называем app.UseOpenIdConnectAuthentication()
и создать экземпляр OpenIdConnectOptions, мы добавим обработчик для события OnTokenValidated:
new OpenIdConnectOptions()
{
...
Events = new OpenIdConnectEvents()
{
...
OnTokenValidated = SecurityTokenValidated
},
};
Событие вызывается при маркер доступа для signing- в пользователе было получено, подтверждено и установлена идентификация пользователя. (Не путать с собственным токеном доступа приложения, необходимым для вызова API AAD Graph!) Это выглядит как хорошее место для запроса API-интерфейса Graph для членства в группах пользователей и добавления этих групп в личность в виде дополнительных требований:
private Task SecurityTokenValidated(TokenValidatedContext context)
{
return Task.Run(async() =>
{
var oidClaim = context.SecurityToken.Claims.FirstOrDefault(c => c.Type == "oid");
if (!string.IsNullOrWhiteSpace(oidClaim?.Value))
{
var pagedCollection = await this.aadClient.Users.GetByObjectId(oidClaim.Value).MemberOf.ExecuteAsync();
do
{
var directoryObjects = pagedCollection.CurrentPage.ToList();
foreach (var directoryObject in directoryObjects)
{
var group = directoryObject as Group;
if (group != null)
{
((ClaimsIdentity)context.Ticket.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, group.DisplayName, ClaimValueTypes.String));
}
}
pagedCollection = pagedCollection.MorePagesAvailable ? await pagedCollection.GetNextPageAsync() : null;
}
while (pagedCollection != null);
}
});
}
Используемый здесь тип заявки на роль, однако вы можете использовать пользовательский.
Сделав выше, если вы используете ClaimType.Role, все, что вам нужно сделать, это украсить свой класс контроллера или метод, как так:
[Authorize(Role = "Administrators")]
То есть, конечно, при условии, у вас есть назначенная группа, сконфигурированная в B2C с отображаемым именем «Администраторы».
Если, однако, вы решили использовать нестандартный тип претензии, вам нужно определить политики авторизации на основе типа претензии, добавляя что-то вроде этого в ConfigureServices() метод, например:
services.AddAuthorization(options => options.AddPolicy("ADMIN_ONLY", policy => policy.RequireClaim("<your_custom_claim_type>", "Administrators")));
, а затем декорировать привилегированный класс контроллера или метод следующим образом:
[Authorize(Policy = "ADMIN_ONLY")]
Хорошо, мы сделали еще? - Ну, не совсем.
Если вы запустили приложение и попытались войти в систему, вы получите исключение из API графики, требующего «Недостаточно привилегий для завершения операции». Это может быть не очевидно, но пока ваше приложение успешно аутентифицируется с помощью AD с помощью app_id и app_key, у него нет привилегий, необходимых для чтения сведений о пользователях из вашего AD. Чтобы предоставить такой доступ приложения к, я решил использовать Azure Active Directory Module for PowerShell
Следующий скрипт сделал трюк для меня:
$tenantGuid = "<your_tenant_GUID>"
$appID = "<your_app_id>"
$userVal = "<admin_user>@<your_AD>.onmicrosoft.com"
$pass = "<admin password in clear text>"
$Creds = New-Object System.Management.Automation.PsCredential($userVal, (ConvertTo-SecureString $pass -AsPlainText -Force))
Connect-MSOLSERVICE -Credential $Creds
$msSP = Get-MsolServicePrincipal -AppPrincipalId $appID -TenantID $tenantGuid
$objectId = $msSP.ObjectId
Add-MsolRoleMember -RoleName "Company Administrator" -RoleMemberType ServicePrincipal -RoleMemberObjectId $objectId
И теперь мы, наконец, сделали! Как это для «пары строк кода»? :)
Возможно, вы можете показать эти 5-6 строк? Я пытаюсь собрать ответ на этот вопрос в течение нескольких дней, и у меня уже более 100 строк кода (и он еще не работает!). Если его так же просто, как 5 или 6 строк, чтобы подключить уведомление, запросите график для данных групп пользователей и добавьте группы в роли ClaimsPrincipal, я, очевидно, лаяю неправильное дерево. Я бы очень признателен за перенаправление! – reidLinden