共用方式為


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 或更早版本中使用這些範例時,需要略為修改。 不過,對於所有 ASP.NET Core 版本,與 ME-ID 和 Microsoft Graph 互動相關的文字和程式碼範例都相同。

範例應用程式

請使用下列連結,透過存放庫根目錄中的最新版本資料夾來存取名為 BlazorWebAssemblyEntraGroupsAndRoles 的範例應用程式。 此範例是針對 .NET 8 或更新版本來提供的。 如需如何執行應用程式的步驟,請參閱範例應用程式的 README 檔案。

範例應用程式包含用來顯示使用者宣告的 UserClaims 元件。 UserData 元件會顯示使用者的基本帳戶屬性。

檢視或下載範例程式碼 \(英文\) (如何下載)

必要條件

本文中的指引會根據 使用圖形 API 搭配 ASP.NET Core 中的 Blazor WebAssembly Graph SDK 指引,實作 Microsoft Graph API。 請遵循 Graph SDK 實作指引來設定應用程式並進行測試,以確認應用程式可以取得測試使用者帳戶的圖形 API 資料。 此外,請參閱 Graph API 文章的安全性文章交叉連結,以檢閱 Microsoft Graph 安全性概念。

在本機使用 Graph SDK 進行測試時,我們建議針對每個測試使用新的私人/incognito 瀏覽器工作模式,以防止徘徊的 cookie 干擾測試。 如需詳細資訊,請參閱使用 Microsoft Entra ID 保護 ASP.NET Core Blazor WebAssembly 獨立應用程式

ME-ID 應用程式註冊連線工具

本文是指在提示您設定應用程式的 ME-ID 應用程式註冊時的 Azure 入口網站,但 Microsoft Entra 系統管理中心也是管理 ME-ID 應用程式註冊的可行選項。 您可以使用任一介面,但本文中的指引特別涵蓋 Azure 入口網站的手勢。

範圍

權限範圍代表相同的事項,在安全性文件和 Azure 入口網站中交換使用。 除非文字指代 Azure 入口網站,否則本文會在指代 Graph 權限時使用範圍/範圍

範圍不區分大小寫,因此 User.Readuser.read 相同。 請隨意使用任一格式,但建議在應用程式碼之間選擇一致的格式。

為了允許 Microsoft Graph API 呼叫使用者設定檔、角色指派和群組成員資料,應用程式會在 Azure 入口網站中設定為委派的User.Read 範圍 (https://graph.microsoft.com/User.Read),因為讀取使用者資料的存取權是取決於授與給個別使用者的範圍。 除了先前所列文章 (「使用 Microsoft 帳戶獨立」「使用 ME-ID 獨立」) 描述的 ME-ID 部署案例中所需的範圍之外,還需要此範圍。

其他必要範圍包括:

  • 委派的RoleManagement.Read.Directory 範圍 (https://graph.microsoft.com/RoleManagement.Read.Directory):允許應用程式代表登入的使用者讀取公司目錄的角色型存取控制 (RBAC) 設定。 這包括讀取目錄角色範本、目錄角色和成員資格。 目錄角色成員資格可用來在應用程式中建立 ME-ID 內建系統管理員角色的 directoryRole 宣告。 需要管理員同意。
  • 委派的AdministrativeUnit.Read.All 範圍 (https://graph.microsoft.com/AdministrativeUnit.Read.All):允許應用程式代表登入的使用者讀取系統管理單位和系統管理單位成員資格。 這些成員資格可用來在應用程式中建立 administrativeUnit 宣告。 需要管理員同意。

如需詳細資訊,請參閱 Microsoft identity 平台的權限和同意概觀 以及 Microsoft Graph 權限概觀

自訂使用者帳戶

在 Azure 入口網站中,將使用者指派給 ME-ID 安全性群組和 ME-ID 管理員角色。

本文中的範例:

  • 假設使用者已指派給 Azure 入口網站 ME-ID 租用戶中的 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.org 確認正確的套件版本。

使用圖形 API 搭配 ASP.NET Core Blazor WebAssembly 一文的 Graph SDK 指引新增 Graph SDK 公用程式類別和設定。 指定存取權杖的 User.ReadRoleManagement.Read.DirectoryAdministrativeUnit.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)。
  • directoryRole 宣告中的 GUID 值是 ME-ID 管理員角色範本識別碼 (Microsoft.Graph.Models.DirectoryRole.RoleTemplateId)。 範本標識碼是穩定標識符,可用來在應用程式中建立使用者授權原則,稍後會在本文中說明。 請勿將 entry.Id 用作目錄角色宣告值,因為它們在租用戶之間不穩定。

下一步,將 MSAL 驗證設定為使用自訂使用者帳戶處理站。

確認 Program 檔案使用的是 Microsoft.AspNetCore.Components.WebAssembly.Authentication 命名空間:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

更新 AddMsalAuthentication 對下列項目的呼叫。 請注意,對於 MSAL 驗證和帳戶宣告主體處理站,Blazor 架構的 RemoteUserAccount 會被應用程式的 CustomUserAccount 所取代:

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

確認使用圖形 API 搭配 ASP.NET Core Blazor WebAssembly 一文所述 Program 檔案中的 Graph SDK 程式碼是否存在:

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(需要管理員同意)

根據 Graph SDK 指導確認 wwwroot/appsettings.json 設定正確無誤。

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"
    ]
  }
}

在 Azure 入口網站中,從應用程式的 ME-ID 註冊中提供下列預留位置的值:

  • {TENANT ID}:目錄 (租用戶) 識別碼 GUID 值。
  • {CLIENT ID}:應用程式 (用戶端) 識別碼 GUID 值。

授權設定

為每個應用程式角色 (依角色名稱)、ME-ID 內建的系統管理員角色 (依角色範本識別碼/GUID) 或 Program 檔案中的安全性群組 (依對象識別碼/GUID) 建立原則。 下列範例會建立 ME-ID 內建「計費管理員」角色的原則:

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

如需 ME-ID 管理員角色識別碼 (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 進階帳戶。

採取下列其中一種方法,在 ME-ID 中新增應用程式角色:

  • 使用預設目錄時,請依照指導將應用程式角色新增至應用程式,在權杖中接收這些角色,並建立 ME-ID 角色。

  • 如果您未使用預設目錄,請在 Azure 入口網站中編輯應用程式的資訊清單,以在資訊清單檔的 appRoles 輸入中,手動建立應用程式的角色。 以下是建立 AdminDeveloper 角色的範例 appRoles 項目。 這些範例角色稍後會作為元件層級,用於實作存取限制:

    重要

    只有在 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"
      }
    ],
    

若要將角色指派給使用者 (或群組,如果您有進階層 Azure 帳戶的話):

  1. 瀏覽至 Azure 入口網站ME-ID 區域中的 企業應用程式
  2. 選取 app。 從資訊看板中選取 [管理]>[使用者和群組]
  3. 選取一或多個使用者帳戶的核取方塊。
  4. 從使用者清單上方的功能表中,選取 [編輯指派]
  5. 針對 [選取角色] 項目,選取 [未選取任何項目]
  6. 從清單中選擇角色,並使用 [選取] 按鈕來選取該角色。
  7. 使用畫面底部的 [指派] 按鈕來指派角色。

您可以在 Azure 入口網站中指派多個角色,方法是針對每個額外的角色指派「重新新增一個使用者」。 使用使用者清單頂端的 [新增使用者/群組] 按鈕來重新新增使用者。 使用上述步驟將另一個角色指派給使用者。 您可以視需要重複此流程多次,將額外的角色新增至使用者 (或群組)。

自訂使用者帳戶 區段中顯示的 CustomAccountFactory 會設定為針對具有 JSON 陣列值的 role宣告採取行動。 在應用程式中新增並註冊 CustomAccountFactory,如 [自訂使用者帳戶] 章節所示。 不需要提供程式碼來移除原始 role 宣告,因為架構會自動將其移除。

Program 檔案中,新增或確認名為「role」的宣告做為角色宣告以進行 ClaimsPrincipal.IsInRole 檢查:

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

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

注意

如果您偏好使用 directoryRoles 宣告 (ME-ID 管理員角色),請將「directoryRoles」指派給 RemoteAuthenticationUserOptions.RoleClaim

在完成了上述步驟,以建立角色並將其指派給使用者 (或群組,如果您有進階層 Azure 帳戶的話),然後使用 Graph SDK 實作 CustomAccountFactory,如本文稍早所述和使用圖形 API 搭配 ASP.NET Core Blazor WebAssembly 中所述之後,對於已登入使用者獲指派的每個指派角色 (或指派給其所屬群組的角色),您應該會看到其 role 宣告。 使用測試使用者執行應用程式,以確認宣告如預期般存在。 在本機使用 Graph SDK 進行測試時,我們建議針對每個測試使用新的私人/incognito 瀏覽器工作模式,以防止徘徊的 cookie 干擾測試。 如需詳細資訊,請參閱使用 Microsoft Entra ID 保護 ASP.NET Core Blazor WebAssembly 獨立應用程式

元件授權方法目前正常運作。 應用程式元件中的任何授權機制都可以使用 Admin 角色來授權使用者:

支援多個角色測試:

  • 使用 AuthorizeView 元件要求使用者具有 Admin Developer 角色:

    <AuthorizeView Roles="Admin, Developer">
        ...
    </AuthorizeView>
    
  • 使用 AuthorizeView 元件要求使用者同時具有 Admin Developer 角色:

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

    如需內部 AuthorizeViewContext 詳細資訊,請參閱 ASP.NET Core Blazor 驗證和授權

  • 使用 [Authorize] 屬性要求使用者具有 Admin Developer 角色:

    @attribute [Authorize(Roles = "Admin, Developer")]
    
  • 使用 [Authorize] 屬性要求使用者同時具有 Admin Developer 角色:

    @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 角色,方法是在上述範例中,將條件式 OR (||) 變更為條件式 AND (&&)

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

支援多個角色測試:

  • 使用 [Authorize] 屬性要求使用者具有 Admin Developer 角色:

    [Authorize(Roles = "Admin, Developer")]
    
  • 使用 [Authorize] 屬性要求使用者同時具有 Admin Developer 角色:

    [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 角色,方法是在上述範例中,將條件式 OR (||) 變更為條件式 AND (&&)

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

因為 .NET 字串比較會區分大小寫,所以比對角色名稱也會區分大小寫。 例如,Admin (大寫 A) 不會被視為與 admin (小寫 a) 相同的角色。

Pascal 大小寫通常用於角色名稱 (例如 BillingAdministrator),但使用 Pascal 大小寫並非嚴格的需求。 允許不同的大小寫方案,如駝峰式大小寫、肉串式大小寫和蛇式大小寫。 在角色名稱中使用空格也是不尋常的,但允許。 例如, billing administrator 在 .NET 應用程式中是不尋常的角色名稱格式,但有效。

其他資源