Microsoft Entra (ME-ID) Группы, Роли Администратора и Роли Приложения

Примечание.

Это не последняя версия этой статьи. См. текущий выпуск в версии .NET 10 этой статьи.

В этой статье объясняется, как настроить Blazor WebAssembly для использования групп и ролей Microsoft Entra ID (ME-ID).

ME-ID предоставляет несколько подходов авторизации, которые можно объединить с ASP.NET Core Identity:

  • Группы
    • Безопасность
    • Microsoft 365
    • Распределение
  • Роли
    • Встроенные роли администратора ME-ID
    • Роли приложения

Руководство, описанное в этой статье, относится к Blazor WebAssembly сценариям развертывания ME-ID, описанным в следующих статьях:

Примеры, приведенные в этой статье, используют новые функции .NET/C#. При использовании примеров с .NET 7 или более ранних версий требуются незначительные изменения. Однако примеры текста и кода, относящиеся к взаимодействию с ME-ID и Microsoft Graph, одинаковы для всех версий ASP.NET Core.

Пример приложения

Перейдите к примеру приложения с именем BlazorWebAssemblyEntraGroupsAndRoles, используя последнюю папку версии из корневого каталога репозитория, используя следующую ссылку. Пример предоставляется для .NET 8 или более поздней версии. Сведения о том, как запустить приложение, см. в файле примера приложения README .

Пример приложения включает UserClaims компонент для отображения утверждений пользователя. Компонент UserData отображает основные свойства учетной записи пользователя.

Просмотреть или скачать образец кода (описание загрузки)

Предварительные требования

Руководство в этой статье реализует API Microsoft Graph в соответствии с рекомендациями пакета SDK Graph, изложенными в разделе Использование API Graph с ASP.NET Core Blazor WebAssembly. Следуйте инструкциям по реализации пакета SDK Graph, чтобы настроить приложение и проверить его, чтобы убедиться, что приложение может получить API Graph данные для тестовой учетной записи пользователя. Кроме того, ознакомьтесь с перекрестными ссылками на статью по безопасности в статье API Graph, чтобы просмотреть концепции безопасности Microsoft Graph.

При локальном тестировании с помощью Graph SDK рекомендуется использовать новый сеанс браузера в режиме in-private/incognito для каждого теста, чтобы предотвратить вмешательство файлов cookie в тесты. Дополнительные сведения см. в разделе Защита автономного приложения ASP.NET Core Blazor WebAssembly с помощью Microsoft Entra ID.

Средства регистрации приложений ME-ID в Интернете

Эта статья ссылается на портал Azure, когда вас просят настроить регистрацию приложения ME-ID, но Microsoft Entra Admin Center тоже является жизнеспособным вариантом для управления регистрацией приложения ME-ID. Любой интерфейс можно использовать, но руководство в этой статье посвящено жестам на портале Azure.

Области видимости

Permissions и scopes означает то же самое и используется взаимозаменяемо в документации по безопасности и на портале Azure. Если текст не ссылается на портал Azure, эта статья использует scope/scopes при обращении к разрешениям Graph.

Области нечувствительны к регистру, поэтому User.Read и user.read — это одно и то же. Вы можете использовать любой формат, но мы рекомендуем согласованный выбор в коде приложения.

Чтобы разрешить вызовы Microsoft API Graph для данных профиля пользователя, назначения ролей и членства в группах, приложение настраивается с помощью области делегированный в портале Azure, поскольку доступ к данным пользователя определяется областями, делегированными отдельным пользователям. Эта область требуется в дополнение к областям, необходимым в сценариях развертывания ME-ID, описанных в статьях, перечисленных ранее (Standalone с учетными записями Майкрософт<>/c0> или Standalone с me-ID).

К дополнительным обязательным областям относятся:

  • ДелегированнаяRoleManagement.Read.Directory область (https://graph.microsoft.com/RoleManagement.Read.Directory): позволяет приложению считывать параметры управления доступом на основе ролей (RBAC) для каталога вашей компании от имени вошедшего пользователя. Это включает чтение шаблонов ролей каталога, ролей каталога и членства в них. Членство в роли каталога используется для создания directoryRole утверждений в приложении для встроенных ролей администратора ME-ID. Требуется согласие администратора.
  • ДелегированнаяAdministrativeUnit.Read.All область (https://graph.microsoft.com/AdministrativeUnit.Read.All): позволяет приложению читать административные единицы и членство в административной единице от имени пользователя, вошедшего в систему. Эти членства используются для создания administrativeUnit утверждений в приложении. Требуется согласие администратора.

Для получения дополнительной информации см. Обзор разрешений и согласия в платформе идентификации Microsoft и Обзор разрешений Microsoft Graph.

Настраиваемая учетная запись пользователя

Назначьте пользователей группам безопасности ME-ID и ролям администратора ME-ID на портале Azure.

Примеры в этой статье:

  • Предположим, что пользователю назначена роль администратора ME-ID Billing на портале Azure ME-ID для авторизации доступа к данным API сервера.
  • Используйте политики авторизации для управления доступом в приложении.

Расширьте RemoteUserAccount, чтобы включить свойства для:

CustomUserAccount.cs:

using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

namespace BlazorWebAssemblyEntraGroupsAndRoles;

public class CustomUserAccount : RemoteUserAccount
{
    [JsonPropertyName("roles")]
    public List<string>? Roles { get; set; }

    [JsonPropertyName("oid")]
    public string? Oid { get; set; }
}

Добавьте ссылку на пакет в приложение для Майкрософт.Graph.

Примечание.

Рекомендации по добавлению пакетов в приложения .NET см. в статьях, приведенных в разделе Установка и управление пакетами в потоке потребления пакетов (документация по NuGet). Проверьте правильность версий пакета на сайте NuGet.org.

Добавьте служебные классы и конфигурацию в руководство Graph SDK статьи Использование API Graph с ASP.NET Core Blazor WebAssembly. Укажите области User.Read, RoleManagement.Read.Directory и AdministrativeUnit.Read.All для маркера доступа, как показано в примере файла в статье wwwroot/appsettings.json.

Добавьте в приложение следующую кастомную фабрику пользовательских учетных записей. Эта фабрика пользователей используется для создания:

  • Утверждения роли приложения () (roleрассматриваются в разделе " Роли приложений ").

  • Пример утверждений данных профиля пользователя для номера мобильного телефона пользователя (mobilePhone) и расположения офиса (officeLocation).

  • Требования роли администратора ME-ID (directoryRole).

  • Претензии административной единицы ME-ID (administrativeUnit).

  • Утверждения группы ME-ID (directoryGroup).

  • ILogger(logger) для удобства в случае, если вы хотите регистрировать сведения или ошибки.

CustomAccountFactory.cs:

using System.Security.Claims;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
using Microsoft.Graph;
using Microsoft.Kiota.Abstractions.Authentication;

namespace BlazorWebAssemblyEntraGroupsAndRoles;

public class CustomAccountFactory(IAccessTokenProviderAccessor accessor,
        IServiceProvider serviceProvider, ILogger<CustomAccountFactory> logger,
        IConfiguration config)
    : AccountClaimsPrincipalFactory<CustomUserAccount>(accessor)
{
    private readonly ILogger<CustomAccountFactory> logger = logger;
    private readonly IServiceProvider serviceProvider = serviceProvider;
    private readonly string? baseUrl = string.Join("/",
        config.GetSection("MicrosoftGraph")["BaseUrl"],
        config.GetSection("MicrosoftGraph")["Version"]);

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        CustomUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity is not null &&
            initialUser.Identity.IsAuthenticated)
        {
            var userIdentity = initialUser.Identity as ClaimsIdentity;

            if (userIdentity is not null && !string.IsNullOrEmpty(baseUrl) &&
                account.Oid is not null)
            {
                account?.Roles?.ForEach((role) =>
                {
                    userIdentity.AddClaim(new Claim("role", role));
                });

                try
                {
                    var client = new GraphServiceClient(
                        new HttpClient(),
                        serviceProvider
                            .GetRequiredService<IAuthenticationProvider>(),
                        baseUrl);

                    var user = await client.Me.GetAsync();

                    if (user is not null)
                    {
                        userIdentity.AddClaim(new Claim("mobilephone",
                            user.MobilePhone ?? "(000) 000-0000"));
                        userIdentity.AddClaim(new Claim("officelocation",
                            user.OfficeLocation ?? "Not set"));
                    }

                    var memberOf = client.Users[account?.Oid].MemberOf;

                    var graphDirectoryRoles = await memberOf.GraphDirectoryRole.GetAsync();

                    if (graphDirectoryRoles?.Value is not null)
                    {
                        foreach (var entry in graphDirectoryRoles.Value)
                        {
                            if (entry.RoleTemplateId is not null)
                            {
                                userIdentity.AddClaim(
                                    new Claim("directoryRole", entry.RoleTemplateId));
                            }
                        }
                    }

                    var graphAdministrativeUnits = await memberOf.GraphAdministrativeUnit.GetAsync();

                    if (graphAdministrativeUnits?.Value is not null)
                    {
                        foreach (var entry in graphAdministrativeUnits.Value)
                        {
                            if (entry.Id is not null)
                            {
                                userIdentity.AddClaim(
                                    new Claim("administrativeUnit", entry.Id));
                            }
                        }
                    }

                    var graphGroups = await memberOf.GraphGroup.GetAsync();

                    if (graphGroups?.Value is not null)
                    {
                        foreach (var entry in graphGroups.Value)
                        {
                            if (entry.Id is not null)
                            {
                                userIdentity.AddClaim(
                                    new Claim("directoryGroup", entry.Id));
                            }
                        }
                    }
                }
                catch (AccessTokenNotAvailableException exception)
                {
                    exception.Redirect();
                }
            }
        }

        return initialUser;
    }
}

Предыдущий код:

  • Не включает транзитивное членство. Если приложению требуются прямые и транзитивные утверждения членства в группах, замените свойство MemberOf (IUserMemberOfCollectionWithReferencesRequestBuilder) на TransitiveMemberOf (IUserTransitiveMemberOfCollectionWithReferencesRequestBuilder).
  • Задает значения GUID в утверждениях directoryRole роли администратора ME-ID Template IDs (Майкрософт.Graph.Models.DirectoryRole.RoleTemplateId). Идентификаторы шаблонов — это стабильные идентификаторы для создания политик авторизации пользователей в приложениях, описанных далее в этой статье. Не используйте entry.Id для значений утверждений роли каталога, поскольку они нестабильны для тенантов.

Затем настройте проверку подлинности MSAL для использования фабрики пользовательских учетных записей пользователей.

Убедитесь, что файл Program использует пространство имен Майкрософт.AspNetCore.Components.WebAssembly.Authentication:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

Обновите вызов AddMsalAuthentication следующим образом. Обратите внимание, что Blazor фреймворк RemoteUserAccount заменяется приложением CustomUserAccount для системы аутентификации MSAL и фабрики главных утверждений учетной записи.

builder.Services.AddMsalAuthentication<RemoteAuthenticationState,
    CustomUserAccount>(options =>
    {
        builder.Configuration.Bind("AzureAd",
            options.ProviderOptions.Authentication);
        options.UserOptions.RoleClaim = "role";
    })
    .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, CustomUserAccount,
        CustomAccountFactory>();

Подтвердите наличие кода Graph SDK в файле Program, описанном в статье Использование API Graph с ASP.NET CoreBlazor WebAssembly:

var baseUrl =
    string.Join("/",
        builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"] ??
            "https://graph.microsoft.com",
        builder.Configuration.GetSection("MicrosoftGraph")["Version"] ??
            "v1.0");
var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
    .Get<List<string>>() ?? [ "user.read" ];

builder.Services.AddGraphClient(baseUrl, scopes);

Внимание

Убедитесь, что в регистрации приложения на портале Azure предоставлены следующие разрешения:

  • User.Read
  • RoleManagement.Read.Directory (Требуется согласие администратора)
  • AdministrativeUnit.Read.All (Требуется согласие администратора)

Убедитесь, что конфигурация wwwroot/appsettings.json правильна согласно руководству по Graph SDK.

wwwroot/appsettings.json:

{
  "AzureAd": {
    "Authority": "https://login.microsoftonline.com/{TENANT ID}",
    "ClientId": "{CLIENT ID}",
    "ValidateAuthority": true
  },
  "MicrosoftGraph": {
    "BaseUrl": "https://graph.microsoft.com",
    "Version": "v1.0",
    "Scopes": [
      "User.Read",
      "RoleManagement.Read.Directory",
      "AdministrativeUnit.Read.All"
    ]
  }
}

Укажите значения для следующих заполнителей из ME-ID регистрации приложения на портале Azure:

  • {TENANT ID}: значение GUID идентификатора каталога (клиента).
  • {CLIENT ID}: значение GUID идентификатора приложения (клиента).

Настройка авторизации

Создайте политику для каждой роли приложения (по имени роли), встроенной роли администратора ME-ID (по идентификатору шаблона роли или GUID) или группе безопасности (по идентификатору объекта или GUID) в Program файле. В следующем примере создается политика для встроенной роли Администратора выставления счетов ME-ID.

builder.Services.AddAuthorizationCore(options =>
{
    options.AddPolicy("BillingAdministrator", policy => 
        policy.RequireClaim("directoryRole", 
            "b0f54661-2d74-4c50-afa3-1ec803f12efe"));
});

Полный список идентификаторов (GUID) для ролей администратора ME-ID см. в разделе Идентификаторы шаблонов ролей документации ME-ID. Сведения о безопасности Azure или идентификаторе группы O365 (GUID) см. в области Object Id для группы на портале Azure Groups области регистрации приложения. Дополнительные сведения о политиках авторизации см. в разделе Авторизация на основе политики в ASP.NET Core.

В следующих примерах приложение использует предыдущую политику для авторизации пользователя.

Компонент AuthorizeView работает с политикой:

<AuthorizeView Policy="BillingAdministrator">
    <Authorized>
        <p>
            The user is in the 'Billing Administrator' ME-ID Administrator Role
            and can see this content.
        </p>
    </Authorized>
    <NotAuthorized>
        <p>
            The user is NOT in the 'Billing Administrator' role and sees this
            content.
        </p>
    </NotAuthorized>
</AuthorizeView>

Доступ ко всему компоненту может основываться на политике, использующей директиву атрибута [Authorize] (AuthorizeAttribute):

@page "/"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "BillingAdministrator")]

Если пользователь не авторизован, он перенаправляется на страницу входа ME-ID.

Проверку политики можно также выполнять в коде с помощью процедурной логики.

CheckPolicy.razor:

@page "/checkpolicy"
@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

<h1>Check Policy</h1>

<p>This component checks a policy in code.</p>

<button @onclick="CheckPolicy">Check 'BillingAdministrator' policy</button>

<p>Policy Message: @policyMessage</p>

@code {
    private string policyMessage = "Check hasn't been made yet.";

    [CascadingParameter]
    private Task<AuthenticationState> authenticationStateTask { get; set; }

    private async Task CheckPolicy()
    {
        var user = (await authenticationStateTask).User;

        if ((await AuthorizationService.AuthorizeAsync(user, 
            "BillingAdministrator")).Succeeded)
        {
            policyMessage = "Yes! The 'BillingAdministrator' policy is met.";
        }
        else
        {
            policyMessage = "No! 'BillingAdministrator' policy is NOT met.";
        }
    }
}

Используя предыдущие подходы, вы также можете создать доступ на основе политик для групп безопасности, где GUID, используемый для политики, соответствует

Роли приложения

Сведения о настройке приложения в портале Azure для предоставления утверждений о членстве в ролях приложений см. в статье Добавление ролей приложений в ваше приложение и получение их в токене в документации по ME-ID.

В следующем примере предполагается, что приложение настроено с двумя ролями, и роли назначены тестовому пользователю.

  • Admin
  • Developer

Хотя вы не можете назначать роли группам без учетной записи ME-ID Premium, вы можете назначать роли пользователям и получать запросы на роли для пользователей со стандартной учетной записью Azure. Для получения инструкции в этом разделе не требуется учетная запись ME-ID Premium.

Используйте любой из следующих подходов, чтобы добавить роли приложения в ME-ID:

  • При работе с каталогом по умолчанию следуйте инструкциям в статье "Добавление ролей приложения и их получение в токене" для создания ролей ME-ID.

  • Если вы не работаете с каталогом по умолчанию, измените манифест приложения на портале Azure, чтобы вручную установить роли приложения в appRoles записи файла манифеста. Ниже приведен пример appRoles записи, которая создает Admin и Developer роли. Эти примеры ролей используются позже на уровне компонента для реализации ограничений доступа:

    Внимание

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

    "appRoles": [
      {
        "allowedMemberTypes": [
          "User"
        ],
        "description": "Administrators manage developers.",
        "displayName": "Admin",
        "id": "{ADMIN GUID}",
        "isEnabled": true,
        "lang": null,
        "origin": "Application",
        "value": "Admin"
      },
      {
        "allowedMemberTypes": [
          "User"
        ],
        "description": "Developers write code.",
        "displayName": "Developer",
        "id": "{DEVELOPER GUID}",
        "isEnabled": true,
        "lang": null,
        "origin": "Application",
        "value": "Developer"
      }
    ],
    

    {ADMIN GUID} и {DEVELOPER GUID} для заполнителей, приведенных в предыдущем примере, можно сгенерировать GUID с помощью онлайн-генератора GUID (введите в Google "генератор guid").

Чтобы назначить роль пользователю (или группе, если у вас есть учетная запись уровня Premium Azure):

  1. Перейдите к приложениям Enterprise в области ME-ID портала Azure.
  2. Выберите приложение. Выберите "Управление пользователями и группами>" на боковой панели.
  3. Установите флажок для одной или нескольких учетных записей пользователей.
  4. В меню над списком пользователей выберите "Изменить назначение".
  5. Для записи "Выбор роли" выберите "Нет".
  6. Выберите роль из списка и нажмите кнопку "Выбрать ", чтобы выбрать ее.
  7. Нажмите кнопку "Назначить " в нижней части экрана, чтобы назначить роль.

Несколько ролей назначаются на портале Azure через повторное добавление пользователя для каждого дополнительного назначения роли. Нажмите кнопку "Добавить пользователя или группу " в верхней части списка пользователей, чтобы повторно добавить пользователя. Используйте описанные выше действия, чтобы назначить пользователю другую роль. Этот процесс можно повторять столько раз, сколько необходимо для добавления дополнительных ролей пользователю (или группе).

CustomAccountFactory, как показано в разделе Настраиваемая учетная запись пользователя, настроен, чтобы работать с утверждением role с JSON-массивом в качестве значения. Добавьте и зарегистрируйте CustomAccountFactory в приложении, как показано в разделе Настройка учетной записи пользователя. Нет необходимости предоставлять код для удаления исходного утверждения role, поскольку оно автоматически удаляется платформой.

В файле Program добавьте или подтвердите утверждение под именем "role" как утверждение роли для проверок ClaimsPrincipal.IsInRole:

builder.Services.AddMsalAuthentication(options =>
{
    ...

    options.UserOptions.RoleClaim = "role";
});

Примечание.

Если вы предпочитаете использовать утверждение directoryRoles (роли администратора ME-ID), назначьте directoryRoles для RemoteAuthenticationUserOptions.RoleClaim.

После выполнения предыдущих шагов по созданию ролей и их назначению пользователям (или группам, если у вас есть учетная запись Azure уровня "Премиум") и реализации CustomAccountFactory с помощью Graph SDK, как было ранее описано в этой статье и в разделе Использование API Graph с ASP.NET CoreBlazor WebAssembly, вы должны увидеть для каждого вошедшего в систему пользователя права role на каждую назначенную ему роль (или роли, назначенные группам, в состав которых они входят). Запустите приложение с тестовым пользователем, чтобы убедиться, что утверждения присутствуют как ожидается. При локальном тестировании с помощью Graph SDK рекомендуется использовать новый сеанс браузера в режиме in-private/incognito для каждого теста, чтобы предотвратить вмешательство файлов cookie в тесты. Дополнительные сведения см. в разделе Защита автономного приложения ASP.NET Core Blazor WebAssembly с помощью Microsoft Entra ID.

На этом этапе можно применять подходы с авторизацией компонентов. Любой из механизмов авторизации в компонентах приложения может использовать Admin роль для авторизации пользователя:

Поддерживается тестирование с использованием нескольких ролей:

  • Требовать, чтобы пользователь имел одну из ролей: AdminилиDeveloper при использовании компонента AuthorizeView:

    <AuthorizeView Roles="Admin, Developer">
        ...
    </AuthorizeView>
    
  • Требовать, чтобы пользователь имел обе роли: AdminиDeveloper при использовании компонента AuthorizeView:

    <AuthorizeView Roles="Admin">
        <AuthorizeView Roles="Developer" Context="innerContext">
            ...
        </AuthorizeView>
    </AuthorizeView>
    

    Дополнительные сведения о Context для внутреннего AuthorizeView см. в разделе ASP.NET Core Blazor аутентификации и авторизации.

  • Требовать, чтобы пользователь имел одну из ролей: AdminилиDeveloper при использовании атрибута [Authorize]:

    @attribute [Authorize(Roles = "Admin, Developer")]
    
  • Требовать, чтобы пользователь имел обе роли: AdminиDeveloper при использовании атрибута [Authorize]:

    @attribute [Authorize(Roles = "Admin")]
    @attribute [Authorize(Roles = "Developer")]
    
  • Требовать, чтобы пользователь имел одну из ролей: AdminилиDeveloper при использовании процедурного кода:

    @code {
        private async Task DoSomething()
        {
            var authState = await AuthenticationStateProvider
                .GetAuthenticationStateAsync();
            var user = authState.User;
    
            if (user.IsInRole("Admin") || user.IsInRole("Developer"))
            {
                ...
            }
            else
            {
                ...
            }
        }
    }
    
  • Требовать, чтобы пользователь имел обе роли: AdminиDeveloper при использовании процедурного кода, изменив условное ИЛИ (||) на условное И (&&) в предыдущем примере:

    if (user.IsInRole("Admin") && user.IsInRole("Developer"))
    

Поддерживается тестирование с использованием нескольких ролей:

  • Требовать, чтобы пользователь имел одну из ролей: AdminилиDeveloper при использовании атрибута [Authorize]:

    [Authorize(Roles = "Admin, Developer")]
    
  • Требовать, чтобы пользователь имел обе роли: AdminиDeveloper при использовании атрибута [Authorize]:

    [Authorize(Roles = "Admin")]
    [Authorize(Roles = "Developer")]
    
  • Требовать, чтобы пользователь имел одну из ролей: AdminилиDeveloper при использовании процедурного кода:

    static readonly string[] scopeRequiredByApi = new string[] { "API.Access" };
    
    ...
    
    [HttpGet]
    public IEnumerable<ReturnType> Get()
    {
        HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
    
        if (User.IsInRole("Admin") || User.IsInRole("Developer"))
        {
            ...
        }
        else
        {
            ...
        }
    
        return ...
    }
    
  • Требовать, чтобы пользователь имел обе роли: AdminиDeveloper при использовании процедурного кода, изменив условное ИЛИ (||) на условное И (&&) в предыдущем примере:

    if (User.IsInRole("Admin") && User.IsInRole("Developer"))
    

Сопоставление ролей обычно чувствительно к регистру, так как имена ролей хранятся и сравниваются с использованием строковых сравнений .NET. Например, Admin(верхний регистр) не рассматривается как та же роль, что и A(строчная admin). Дополнительные сведения см. в разделе Авторизация на основе заявок в ASP.NET Core. Напротив, в ASP.NET Core поиск имен политики обычно регистронезависимый, поэтому RequireAdministratorRole и requireadministratorrole ссылаются на ту же политику.

Дополнительные ресурсы