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

В этой статье объясняется, как настроить использование 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.

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

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

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

Области

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

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

Чтобы разрешить вызовы API Microsoft Graph для профиля пользователя, назначения ролей и членства в группах, приложение настраивается с делегированной User.Read областью () в портал Azure поскольку доступ к данным пользователя определяется областями, предоставленными (https://graph.microsoft.com/User.Readделегированными) отдельным пользователям. Эта область требуется в дополнение к областям, необходимым в сценариях развертывания ME-ID, описанных в статьях, перечисленных ранее (автономный с учетными записями Майкрософт или автономной с 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 Graph.

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

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

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

  • Предположим, что пользователю назначена роль администратора выставления счетов ME-ID в клиенте портал 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; }
}

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

Примечание.

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

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

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

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

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

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

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

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

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

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 (Microsoft.Graph.Models.DirectoryRole.RoleTemplateId). Идентификаторы шаблонов — это стабильные идентификаторы для создания политик авторизации пользователей в приложениях, описанных далее в этой статье. Не используйте entry.Id для значений утверждений роли каталога, так как они не являются стабильными в клиентах.

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

Убедитесь, что Program файл использует Microsoft.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>();

Убедитесь, что код пакета SDK Graph в файле, описанном Program API Use 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 конфигурация правильна для руководства по пакету SDK Graph.

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 см. в документации по идентификаторам шаблонов ролей. Сведения о безопасности Azure или идентификаторе группы O365 (GUID) см. в идентификаторе объекта для группы в области портал Azure Групп регистрации приложения. Дополнительные сведения о политиках авторизации см. в статье Авторизация на основе политик в 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": "584e483a-7101-404b-9bb1-83bf9463e335",
        "isEnabled": true,
        "lang": null,
        "origin": "Application",
        "value": "Admin"
      },
      {
        "allowedMemberTypes": [
          "User"
        ],
        "description": "Developers write code.",
        "displayName": "Developer",
        "id": "82770d35-2a93-4182-b3f5-3d7bfe9dfe46",
        "isEnabled": true,
        "lang": null,
        "origin": "Application",
        "value": "Developer"
      }
    ],
    

    Примечание.

    Вы можете создавать идентификаторы GUID с помощью веб-программы генератора GUID (результат поиска Google для "генератор guid").

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

  1. Перейдите к корпоративным приложениям в области "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 пакет SDK Graph, как описано ранее в этой статье, и в разделе "Использование API Graph с ASP.NET Core Blazor WebAssembly", должно появиться role утверждение для каждой назначенной роли, назначаемой пользователем (или ролям, назначенным группам, в которых они являются членами). Запустите приложение с тестовой пользователем, чтобы убедиться, что утверждения присутствуют должным образом. При локальном тестировании с помощью пакета SDK Graph рекомендуется использовать новый сеанс браузера in-private/incognito для каждого теста, чтобы предотвратить cookieпомехи тестам. Дополнительные сведения см. в статье "Защита автономного приложения ASP.NET Core Blazor WebAssembly с помощью идентификатора Microsoft Entra.

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

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

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

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

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

    Дополнительные сведения о внутренней AuthorizeViewсреде см. в Context разделе ASP.NET Проверка подлинности и авторизация CoreBlazor.

  • Требовать, чтобы пользователь имел одну из ролей: 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 по умолчанию чувствительны к регистру, соответствующие имена ролей также учитывает регистр. Например, (верхний регистрA) не рассматривается как та же роль, Admin что admin и (строчная).a

Регистр Pascal обычно используется для имен ролей (например, BillingAdministrator), но использование регистра Pascal не является строгим требованием. Разрешены различные схемы регистра, такие как верблюдьи случаи, кебаб и змеиный случай. Использование пробелов в именах ролей также является необычным, но разрешенным. Например, billing administrator это необычный формат имени роли в приложениях .NET, но допустимый.

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