共用方式為


ASP.NET Core 中的基於權利要求的授權

當應用程式使用者登入應用程式時建立身份時,身份提供者可能會為使用者的身份指派一項或多項 聲明 。 理賠是一組名稱值對,代表主體(使用者、應用程式或服務,或裝置/電腦)是什麼,而非主體能做什麼。 申請申請可在授權過程中評估,以確定對資料及其他安全資源的存取權,並可用於對主體做出或表達身份驗證決策。 同一身份可以包含多個具有多個值的權利要求,也可以包含多個相同類型的權利要求。 本文說明如何在 ASP.NET Core 應用程式中新增授權的理賠檢查。

本文使用 Razor 元件範例,並聚焦於 Blazor 授權情境。 如需更多 Blazor 指引,請參閱 「附加資源 」章節。 關於 Razor 頁面與MVC指引,請參閱以下資源:

範例應用程式

本文的 Blazor Web App 範例是 BlazorWebAppAuthorization 範例應用程式(dotnet/AspNetCore.Docs.Samples GitHub 倉庫)how to download)。 範例應用程式使用預設帳號並預先設定的宣告,用於展示本文中的大部分示例。 欲了解更多資訊,請參閱範例的 README 檔案(README.md)。

謹慎

這個範例應用程式使用記憶體內資料庫來儲存使用者資訊,這並不適合生產環境。 範例應用程式僅供示範用途,不應作為生產應用程式的起點。

新增理賠檢查

宣告型授權檢查:

AuthorizeView該組件(AuthorizeView文件中的Blazor組件)支援基於政策的授權,當保單需要一個或多個理賠時。 或者,也可以透過一個或多個策略檢查,利用[Authorize]元件中的Razor屬性來設定基於聲明的授權。 開發商必須建立並登記一份表達理賠要求的保單。 本節涵蓋基本概念。 完整內容請參見 ASP.NET Core Blazor 認證與授權

最簡單的宣告策略類型會檢查宣告是否存在,而不會檢查其值。

政策註冊會在應用程式 Program 檔案中的授權服務設定中進行:

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));

備註

WebApplicationBuilder.ConfigureApplicationreference source)在註冊 UseAuthorization 時自動新增 IAuthorizationHandlerProvider 呼叫,這也是自 .NET 8 發布以來 ASP.NET Core 的行為。 因此,在 .NET 8 或更高版本中,明確呼叫 UseAuthorization 用於伺服器端 Blazor 應用程式,在技術上是多餘的,但這樣的呼叫並無害處。 在框架已經呼叫過之後,在開發人員程式碼中再次呼叫它,只是無操作 (no-ops),即不會產生任何效果。

備註

文件連結指向.NET參考來源通常會載入倉庫的預設分支,代表下一版 .NET 的當前開發進度。 若要選取特定發行版本的標籤,請使用「切換分支或標籤」下拉式清單。 欲了解更多資訊,請參閱 如何選擇 ASP.NET Core原始碼版本標籤(dotnet/AspNetCore.Docs #26205)

政策註冊會在應用程式 Program 檔案中的授權服務設定中進行:

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));

在Blazor Server應用程式中,請在呼叫UseAuthentication之後呼叫UseAuthorization(如果有的話):

app.UseAuthentication(); // Only present if not called internally
app.UseAuthorization();

註冊政策作為授權服務配置的一部分,在 )中進行。

services.AddAuthorization(options =>
{
    options.AddPolicy("EmployeeOnly", policy =>
        policy.RequireClaim("EmployeeNumber"));
});

在Blazor Server應用程式中,如果存在UseAuthentication,請在呼叫UseAuthentication的語句之後呼叫UseAuthorizationStartup.Configure

app.UseAuthentication(); // Only present if not called internally
app.UseAuthorization();

Blazor WebAssembly 應用程式呼叫 AddAuthorizationCore 檔案 Program 以新增授權服務:

builder.Services.AddAuthorizationCore();

利用Policy屬性上的[Authorize]屬性來指定政策名稱,套用該政策。 以下範例中, EmployeeOnly 政策檢查是否存在 EmployeeNumber 對當前身份的主張:

對於使用 AuthorizeView 元件進行基於政策的授權,使用帶有單一政策名稱的 AuthorizeView.Policy 參數。

Pages/PassEmployeeOnlyPolicyWithAuthorizeView.razor

@page "/pass-employeeonly-policy-with-authorizeview"

<h1>Pass 'EmployeeOnly' policy with AuthorizeView</h1>

<AuthorizeView Policy="EmployeeOnly">
    <Authorized>
        <p>You satisfy the 'EmployeeOnly' policy.</p>
    </Authorized>
    <NotAuthorized>
        <p>You <b>don't</b> satisfy the 'EmployeeOnly' policy.</p>
    </NotAuthorized>
</AuthorizeView>

或者,利用Policy屬性上的[Authorize]屬性指定政策名稱以套用政策。 以下範例中, EmployeeOnly 政策檢查是否存在 EmployeeNumber 對當前身份的主張:

Pages/PassEmployeeOnlyPolicyWithAuthorizeAttribute.razor

@page "/pass-employeeonly-policy-with-authorize-attribute"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "EmployeeOnly")]

<h1>Pass 'EmployeeOnly' policy with [Authorize] attribute</h1>

<p>You satisfy the 'EmployeeOnly' policy.</p>

你可以在建立政策時指定允許的值清單。 以下政策僅適用於員工編號為1、2、3、4或5的員工:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("Founders", policy =>
        policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthentication();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Founders", policy =>
                      policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthentication();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();
services.AddAuthorization(options =>
{
    options.AddPolicy("Founder", policy =>
        policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});

Pages/PassFounderPolicyWithAuthorizeView.razor

@page "/pass-founder-policy-with-authorizeview"

<h1>Pass 'Founder' policy with AuthorizeView</h1>

<AuthorizeView Policy="Founder">
    <Authorized>
        <p>You satisfy the 'Founder' policy.</p>
    </Authorized>
    <NotAuthorized>
        <p>You <b>don't</b> satisfy the 'Founder' policy.</p>
    </NotAuthorized>
</AuthorizeView>

新增泛型宣告檢查

如果理賠金額不是單一值,或你需要更靈活的理賠評估邏輯,例如模式匹配、檢查理賠發件人,或解析複雜的理賠金額,請使用 RequireAssertionHasClaim 例如,以下政策要求使用者 email 的主張必須以特定網域結尾:

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("ContosoOnly", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "email" &&
                c.Value.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase))));
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ContosoOnly", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "email" &&
                c.Value.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase))));
});
services.AddAuthorization(options =>
{
    options.AddPolicy("ContosoOnly", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "email" &&
                c.Value.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase))));
});

欲了解更多資訊,請參閱 ASP.NET Core 中的基於策略的授權

評估多項政策

多重政策透過多個 AuthorizeView 元件套用。 內部元件要求使用者通過其政策以及父元件的所有政策 AuthorizeView

下列範例:

  • 需要一項 CustomerServiceMember 規範,這表明使用者屬於組織的客服部門,因為他們擁有一項 Department 價值為 Customer Service 的索賠。
  • 同時也需要一個 HumanResourcesMember 政策,表示使用者屬於組織的人力資源部門,因為他們有一個 Department 值為 Human Resources的主張。

在應用程式的 Program 檔案中:

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("CustomerServiceMember", policy =>
        policy.RequireClaim("Department", "Customer Service"))
    .AddPolicy("HumanResourcesMember", policy =>
        policy.RequireClaim("Department", "Human Resources"));

在應用程式的 Program 檔案中:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("CustomerServiceMember", policy =>
        policy.RequireClaim("Department", "Customer Service"));
    options.AddPolicy("HumanResourcesMember", policy =>
        policy.RequireClaim("Department", "Human Resources"));
});

Startup.ConfigureServices (Startup.cs) 中:

services.AddAuthorization(options =>
{
    options.AddPolicy("CustomerServiceMember", policy =>
        policy.RequireClaim("Department", "Customer Service"));
    options.AddPolicy("HumanResourcesMember", policy =>
        policy.RequireClaim("Department", "Human Resources"));
});

以下範例使用元件 AuthorizeView

Pages/PassCustomerServiceMemberAndHumanResourcesMemberPoliciesWithAuthorizeViews.razor

@page "/pass-customerservicemember-and-humanresourcesmember-policies-with-authorizeviews"

<h1>Pass 'CustomerServiceMember' and 'HumanResourcesMember' policies with AuthorizeViews</h1>

<AuthorizeView Policy="CustomerServiceMember">
    <Authorized>
        <p>User: @context.User.Identity?.Name</p>
        <AuthorizeView Policy="HumanResourcesMember" Context="innerContext">
            <Authorized>
                <p>
                    You satisfy the 'CustomerServiceMember' and 'HumanResourcesMember' policies.
                </p>
            </Authorized>
            <NotAuthorized>
                <p>
                    You satisfy the 'CustomerServiceMember' policy, but you <b>don't</b> satisfy 
                    the 'HumanResourcesMember' policy.
                </p>
            </NotAuthorized>
        </AuthorizeView>
    </Authorized>
    <NotAuthorized>
        <p>
            You <b>don't</b> satisfy the 'CustomerServiceMember' policy.
        </p>
    </NotAuthorized>
</AuthorizeView>

以下範例使用屬性[Authorize]

Pages/PassCustomerServiceMemberAndHumanResourcesMemberPoliciesWithAuthorizeAttributes.razor

@page "/pass-customerservicemember-and-humanresourcesmember-policies-with-authorize-attributes"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "CustomerServiceMember")]
@attribute [Authorize(Policy = "HumanResourcesMember")]

<h1>
    Pass 'CustomerServiceMember' and 'HumanResourcesMember' policies with [Authorize] attributes
</h1>

<p>
    You satisfy the 'CustomerServiceMember' and 'HumanResourcesMember' policies.
</p>

對於較複雜的保單,例如申請出生日期,計算年齡,再確認年齡是否為21歲或以上,則可使用RequireAssertion或撰寫自訂保單處理工具。 當你需要存取依賴注入服務或想要一個可重用、可測試的授權元件時,自訂政策處理器非常有用。

理賠案件敏感性

理賠金額會使用 StringComparison.Ordinal進行比較。 這表示 Admin (大寫 A)與 admin (小寫 a)總是被視為不同角色,無論身份由哪位認證處理者建立。

另外,理賠 類型 比較(用於依理賠類型定位角色理賠,例如 http://schemas.microsoft.com/ws/2008/06/identity/claims/role),可能根據實作方式而異,或不區分 ClaimsIdentity 大小寫。 在 ASP.NET Core 8.0 或更新版本中(AddJwtBearerAddOpenIdConnectAddWsFederationAddMicrosoftIdentityWebApp/AddMicrosoftIdentityWebApi 中使用),在代幣驗證時會產生 CaseSensitiveClaimsIdentity,該驗證使用大小寫敏感的宣告類型匹配。

.NET執行時提供的預設ClaimsIdentity(用於大多數情況,包括所有基於cookie的流程)仍然使用不區分大小寫的宣告類型匹配。

實務上,這種區分對角色授權來說很少有影響,因為角色主張類型在身份建立時只設定一次,且會一致匹配。 角色名稱和權限類型務必使用一致的大小寫,以避免細微問題。

其他資源