ASP.NET Core 中的原則型授權

實際上,角色型授權宣告型授權會使用需求、需求處理常式和預先設定的原則。 這些建置區塊支援程式碼中授權評估的運算式。 結果是更豐富、可重複使用、可測試的授權結構。

授權原則是由一或多個需求組成。 在應用程式 Program.cs 的檔案中,將它註冊為授權服務設定的一部分:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AtLeast21", policy =>
        policy.Requirements.Add(new MinimumAgeRequirement(21)));
});

在上述範例中,會建立「AtLeast21」原則。 其具有單一需求 — 最低年齡,會以參數的形式提供給需求。

IAuthorizationService

判斷授權是否成功的主要服務為 IAuthorizationService

/// <summary>
/// Checks policy based permissions for a user
/// </summary>
public interface IAuthorizationService
{
    /// <summary>
    /// Checks if a user meets a specific set of requirements for the specified resource
    /// </summary>
    /// <param name="user">The user to evaluate the requirements against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="requirements">The requirements to evaluate.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// This value is <value>true</value> when the user fulfills the policy; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check 
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, 
                                     IEnumerable<IAuthorizationRequirement> requirements);

    /// <summary>
    /// Checks if a user meets a specific authorization policy
    /// </summary>
    /// <param name="user">The user to check the policy against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="policyName">The name of the policy to check against a specific 
    /// context.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// Returns a flag indicating whether the user, and optional resource has fulfilled 
    /// the policy.    
    /// <value>true</value> when the policy has been fulfilled; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(
                                ClaimsPrincipal user, object resource, string policyName);
}

上述程式碼會反白顯示 IAuthorizationService 的兩種方法。

IAuthorizationRequirement 是沒有方法的標記服務,以及追蹤授權是否成功的機制。

每個 IAuthorizationHandler 都會負責檢查需求是否符合:

/// <summary>
/// Classes implementing this interface are able to make a decision if authorization
/// is allowed.
/// </summary>
public interface IAuthorizationHandler
{
    /// <summary>
    /// Makes a decision if authorization is allowed.
    /// </summary>
    /// <param name="context">The authorization information.</param>
    Task HandleAsync(AuthorizationHandlerContext context);
}

類別 AuthorizationHandlerContext 是處理常式用來標記是否符合需求的內容:

 context.Succeed(requirement)

下列程式碼顯示授權服務的簡化版預設實作 (並以註解批註):

public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, 
             object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
    // Create a tracking context from the authorization inputs.
    var authContext = _contextFactory.CreateContext(requirements, user, resource);

    // By default this returns an IEnumerable<IAuthorizationHandlers> from DI.
    var handlers = await _handlers.GetHandlersAsync(authContext);

    // Invoke all handlers.
    foreach (var handler in handlers)
    {
        await handler.HandleAsync(authContext);
    }

    // Check the context, by default success is when all requirements have been met.
    return _evaluator.Evaluate(authContext);
}

下列程式碼顯示一般授權服務設定:

// Add all of your handlers to DI.
builder.Services.AddSingleton<IAuthorizationHandler, MyHandler1>();
// MyHandler2, ...

builder.Services.AddSingleton<IAuthorizationHandler, MyHandlerN>();

// Configure your policies
builder.Services.AddAuthorization(options =>
      options.AddPolicy("Something",
      policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));

使用 IAuthorizationService[Authorize(Policy = "Something")]RequireAuthorization("Something") 進行授權。

將原則套用至 MVC 控制器

如需使用 Razor Pages 的應用程式,請參閱將原則套用至 Razor Pages 一節。

使用具有原則名稱的 [Authorize] 屬性,將原則套用至控制器:

[Authorize(Policy = "AtLeast21")]
public class AtLeast21Controller : Controller
{
    public IActionResult Index() => View();
}

如果在控制器和動作層級套用多個原則,所有原則都必須通過,才能授予存取權:

[Authorize(Policy = "AtLeast21")]
public class AtLeast21Controller : Controller
{
    public IActionResult Index() => View();
}

將原則套用至 Razor Pages

使用具有原則名稱的 [Authorize] 屬性,將原則套用至 Razor Pages。 例如:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace AuthorizationPoliciesSample.Pages;

[Authorize(Policy = "AtLeast21")]
public class AtLeast21Model : PageModel { }

原則無法在 Razor 頁面處理常式層級套用,必須將原則套用至頁面。

您也可以使用授權慣例,將原則套用至 Razor Pages。

將原則套用至端點

使用具有原則名稱的 RequireAuthorization,將原則套用至端點。 例如:

app.MapGet("/helloworld", () => "Hello World!")
    .RequireAuthorization("AtLeast21");

需求

授權需求是原則可用來評估目前使用者主體的資料參數集合。 在我們的「AtLeast21」原則中,需求是單一參數,也就是最低年齡。 需求會實作 IAuthorizationRequirement,這是空的標記介面。 參數化的最低年齡需求可以實作如下:

using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Requirements;

public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public MinimumAgeRequirement(int minimumAge) =>
        MinimumAge = minimumAge;

    public int MinimumAge { get; }
}

如果授權原則包含多個授權需求,所有需求都必須通過,原則評估才能成功。 換句話說,會進行 AND 型處理,將多個授權需求新增至單一授權原則。

注意

需求不需要有資料或屬性。

授權處理常式

需求授權處理常式負責評估需求的屬性。 授權處理常式會根據提供的 AuthorizationHandlerContext 評估需求,以判斷是否允許存取。

一個需求可以有多個處理常式。 處理常式可能會繼承 AuthorizationHandler<TRequirement>,其中 TRequirement 是需要處理的需求。 或者,處理常式可以直接實作 IAuthorizationHandler 來處理一種以上的需求。

針對一項需求使用一個處理常式

下列範例顯示一對一關聯性,其中最低年齡處理常式會處理單一需求:

using System.Security.Claims;
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
    {
        var dateOfBirthClaim = context.User.FindFirst(
            c => c.Type == ClaimTypes.DateOfBirth && c.Issuer == "http://contoso.com");

        if (dateOfBirthClaim is null)
        {
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
        int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

上述程式碼會判斷目前的使用者主體是否具有已知且受信任的簽發者所發出的出生日期宣告。 當宣告遺失時,即無法進行授權,在此情況下會傳回已完成的工作。 當宣告存在時,會計算使用者的年齡。 如果使用者符合需求所定義的最低年齡,則會將授權視為成功。 當授權成功時,context.Succeed 會以符合的需求作為唯一參數來叫用。

針對多個需求使用一個處理常式

下列範例顯示一對多關聯性,其中權限處理常式可以處理三種不同類型的需求:

using System.Security.Claims;
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();

        foreach (var requirement in pendingRequirements)
        {
            if (requirement is ReadPermission)
            {
                if (IsOwner(context.User, context.Resource)
                    || IsSponsor(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
            else if (requirement is EditPermission || requirement is DeletePermission)
            {
                if (IsOwner(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
        }

        return Task.CompletedTask;
    }

    private static bool IsOwner(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }

    private static bool IsSponsor(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }
}

上述程式碼會周遊 PendingRequirements — 這個屬性包含未標示為成功之需求的。 針對 ReadPermission 需求,使用者必須是擁有者或贊助者,才能存取要求的資源。 針對 EditPermissionDeletePermission 需求,他們必須是存取要求資源的擁有者。

處理常式註冊

處理常式會在設定期間在服務集合中註冊。 例如:

builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();

上述程式碼會將 MinimumAgeHandler 註冊為 singleton。 處理常式可以使用任何內建服務存留期來註冊。

可以將需求和處理常式組合成實作 IAuthorizationRequirementIAuthorizationHandler 的單一類別。 此組合會在處理常式與需求之間建立緊密結合,而且只建議用於簡單需求和處理常式。 建立實作這兩個介面的類別,就不需要在 DI 中註冊處理常式了,因為內建的 PassThroughAuthorizationHandler 可讓需求自行處理。

如需範例,請參閱 AssertionRequirement 類別 ,了解 AssertionRequirement 是完全獨立類別中的需求和處理常式。

處理常式應該傳回什麼?

請注意,處理常式範例中的 Handle 方法不會傳回任何值。 如何指示成功或失敗的狀態?

  • 處理常式會藉由呼叫 context.Succeed(IAuthorizationRequirement requirement) 來指示成功,並傳遞已成功驗證的需求。

  • 處理常式通常不需要處理失敗,因為同一需求的其他處理常式可能會成功。

  • 若要保證失敗,即使其他需求處理常式成功,仍請呼叫 context.Fail

如果處理常式呼叫 context.Succeedcontext.Fail,則仍會呼叫所有其他處理常式。 這可讓需求產生副作用,例如記錄,即使另一個處理常式已成功驗證或失敗需求也一樣。 當設定為 false 時,InvokeHandlersAfterFailure 屬性會將呼叫 context.Fail 時處理常式的執行短路。 InvokeHandlersAfterFailure 預設為 true,在此情況下會呼叫所有處理常式。

注意

即使驗證失敗,也會呼叫授權處理常式。 此外,處理常式也可以依任何循序執行,因此請不要倚賴以任何特定順序呼叫它們。

為什麼需要多個處理常式?

如果您想要進行 OR 型的評估,請針對單一需求實作多個處理常式。 例如,Microsoft 有只能使用鑰匙卡開啟的門。 如果您把鑰匙卡留在家裡,接待員列印了一個臨時貼紙,並為您打開門。 在此案例中,您會有單一需求 BuildingEntry 和多個處理常式,每個處理常式都會檢查單一需求。

BuildingEntryRequirement.cs

using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Requirements;

public class BuildingEntryRequirement : IAuthorizationRequirement { }

BadgeEntryHandler.cs

using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(
            c => c.Type == "BadgeId" && c.Issuer == "https://microsoftsecurity"))
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

TemporaryStickerHandler.cs

using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(
            c => c.Type == "TemporaryBadgeId" && c.Issuer == "https://microsoftsecurity"))
        {
            // Code to check expiration date omitted for brevity.
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

請確定這兩個處理常式都已註冊。 如果任一個處理常式在原則評估 BuildingEntryRequirement 時成功,原則評估就會成功。

使用 func 來完成原則

在某些情況下,履行原則在程式碼中很容易表示。 使用 RequireAssertion 原則建立器設定原則時,可能會提供 Func<AuthorizationHandlerContext, bool>

例如,先前的 BadgeEntryHandler 可以重寫如下:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("BadgeEntry", policy =>
        policy.RequireAssertion(context => context.User.HasClaim(c =>
            (c.Type == "BadgeId" || c.Type == "TemporaryBadgeId")
            && c.Issuer == "https://microsoftsecurity")));
});

存取處理常式中的 MVC 要求內容

HandleRequirementAsync 方法有兩個參數:AuthorizationHandlerContext 和正在處理的 TRequirement。 MVC 或 SignalR 之類的架構可以自由地將任何物件新增至 AuthorizationHandlerContext 上的 Resource 屬性,以傳遞額外的資訊。

使用端點路由時,授權通常會由授權中介軟體處理。 在此情況下,Resource 屬性是 HttpContext 的實例。 內容可用來存取目前的端點,並可用於探查您要路由的基礎資源。 例如:

if (context.Resource is HttpContext httpContext)
{
    var endpoint = httpContext.GetEndpoint();
    var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
    ...
}

透過傳統路由,或當授權是 MVC 授權篩選條件的一部分時,Resource 的值為 AuthorizationFilterContext 的實例。 此屬性可讓您存取 MVC 和 Razor Pages 所提供的 HttpContextRouteData 和其他所有內容。

Resource 屬性的使用是架構特定的。 使用 Resource 屬性中的資訊會將授權原則限制為特定架構。 使用 is 關鍵字轉換 Resource 屬性,然後確認轉換成功,以確保您的程式碼在其他架構上執行時不會與 InvalidCastException 衝突:

// Requires the following import:
//     using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
    // Examine MVC-specific things like routing data.
}

全域要求所有使用者都經過驗證

如需有關如何在全域範圍內要求所有使用者進行驗證的資訊,請參閱需要驗證的使用者

使用外部服務範例進行授權

AspNetCore.Docs.Samples 上的範例程式碼示範如何使用外部授權服務實作其他授權需求。 範例 Contoso.API 專案會受到 Azure AD 保護。 Contoso.Security.API 專案的其他授權檢查會傳回承載,描述 Contoso.API 用戶端應用程式是否可以叫用 GetWeather API。

設定範例

  1. Azure Active Directory (Azure AD) 租用戶 中建立應用程式註冊
  • 將 AppRole 指派給它。
  • 在 [API 權限] 下,將 AppRole 新增為權限,並授予管理員同意。 請注意,在此設定中,此應用程式註冊代表 API 和叫用 API 的用戶端。 如果您想要,您可以建立兩個應用程式註冊。 如果您使用此設定,請務必只執行 API 權限,請只將 AppRole 新增為用戶端的權限步驟。 只有用戶端應用程式註冊需要產生用戶端密碼。
  1. 使用下列設定來設定 Contoso.API 專案:
{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "<Tenant name from AAD properties>.onmicrosoft.com">,
    "TenantId": "<Tenant Id from AAD properties>",
    "ClientId": "<Client Id from App Registration representing the API>"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
  1. 使用下列設定來設定 Contoso.Security.API
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AllowedClients": [
    "<Use the appropriate Client Id representing the Client calling the API>"
  ]
}
  1. ContosoAPI.postman_collection.json 檔案匯入 Postman,並使用下列各項設定環境:

    • ClientId:來自應用程式註冊的用戶端識別碼,代表呼叫 API 的用戶端。
    • clientSecret:來自應用程式註冊的用戶端密碼,代表呼叫 API 的用戶端。
    • TenantId:來自 AAD 屬性的租用戶識別碼
  2. 執行解決方案,並使用 Postman 叫用 API。 您可以在 Contoso.Security.API.SecurityPolicyController 中新增中斷點,並觀察傳入的用戶端識別碼,用來判斷是否允許取得天氣。

其他資源

實際上,角色型授權宣告型授權會使用需求、需求處理常式和預先設定的原則。 這些建置區塊支援程式碼中授權評估的運算式。 結果是更豐富、可重複使用、可測試的授權結構。

授權原則是由一或多個需求組成。 原則會在 Startup.ConfigureServices 方法中註冊為授權服務組態的一部分:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("AtLeast21", policy =>
            policy.Requirements.Add(new MinimumAgeRequirement(21)));
    });
}

在上述範例中,會建立「AtLeast21」原則。 其具有單一需求 — 最低年齡,會以參數的形式提供給需求。

IAuthorizationService

判斷授權是否成功的主要服務為 IAuthorizationService

/// <summary>
/// Checks policy based permissions for a user
/// </summary>
public interface IAuthorizationService
{
    /// <summary>
    /// Checks if a user meets a specific set of requirements for the specified resource
    /// </summary>
    /// <param name="user">The user to evaluate the requirements against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="requirements">The requirements to evaluate.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// This value is <value>true</value> when the user fulfills the policy; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check 
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, 
                                     IEnumerable<IAuthorizationRequirement> requirements);

    /// <summary>
    /// Checks if a user meets a specific authorization policy
    /// </summary>
    /// <param name="user">The user to check the policy against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="policyName">The name of the policy to check against a specific 
    /// context.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// Returns a flag indicating whether the user, and optional resource has fulfilled 
    /// the policy.    
    /// <value>true</value> when the policy has been fulfilled; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(
                                ClaimsPrincipal user, object resource, string policyName);
}

上述程式碼會反白顯示 IAuthorizationService 的兩種方法。

IAuthorizationRequirement 是沒有方法的標記服務,以及追蹤授權是否成功的機制。

每個 IAuthorizationHandler 都會負責檢查需求是否符合:

/// <summary>
/// Classes implementing this interface are able to make a decision if authorization
/// is allowed.
/// </summary>
public interface IAuthorizationHandler
{
    /// <summary>
    /// Makes a decision if authorization is allowed.
    /// </summary>
    /// <param name="context">The authorization information.</param>
    Task HandleAsync(AuthorizationHandlerContext context);
}

類別 AuthorizationHandlerContext 是處理常式用來標記是否符合需求的內容:

 context.Succeed(requirement)

下列程式碼顯示授權服務的簡化版預設實作 (並以註解批註):

public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, 
             object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
    // Create a tracking context from the authorization inputs.
    var authContext = _contextFactory.CreateContext(requirements, user, resource);

    // By default this returns an IEnumerable<IAuthorizationHandlers> from DI.
    var handlers = await _handlers.GetHandlersAsync(authContext);

    // Invoke all handlers.
    foreach (var handler in handlers)
    {
        await handler.HandleAsync(authContext);
    }

    // Check the context, by default success is when all requirements have been met.
    return _evaluator.Evaluate(authContext);
}

下列程式碼顯示典型的 ConfigureServices

public void ConfigureServices(IServiceCollection services)
{
    // Add all of your handlers to DI.
    services.AddSingleton<IAuthorizationHandler, MyHandler1>();
    // MyHandler2, ...

    services.AddSingleton<IAuthorizationHandler, MyHandlerN>();

    // Configure your policies
    services.AddAuthorization(options =>
          options.AddPolicy("Something",
          policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));


    services.AddControllersWithViews();
    services.AddRazorPages();
}

使用 IAuthorizationService[Authorize(Policy = "Something")] 進行授權。

將原則套用至 MVC 控制器

如果您使用 Razor Pages,請參閱本文件中將原則套用至 Razor Pages

原則會套用至控制器,方法是使用具有原則名稱的 [Authorize] 屬性。 例如:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseController : Controller
{
    public IActionResult Index() => View();
}

將原則套用至 Razor Pages

原則會套用至 Razor Pages,方法是使用具有原則名稱的 [Authorize] 屬性。 例如:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseModel : PageModel
{
}

原則無法在 Razor 頁面處理常式層級套用,必須將原則套用至頁面。

您也可以使用授權慣例,將原則套用至 Razor Pages。

需求

授權需求是原則可用來評估目前使用者主體的資料參數集合。 在我們的「AtLeast21」原則中,需求是單一參數,也就是最低年齡。 需求會實作 IAuthorizationRequirement,這是空的標記介面。 參數化的最低年齡需求可以實作如下:

using Microsoft.AspNetCore.Authorization;

public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public int MinimumAge { get; }

    public MinimumAgeRequirement(int minimumAge)
    {
        MinimumAge = minimumAge;
    }
}

如果授權原則包含多個授權需求,所有需求都必須通過,原則評估才能成功。 換句話說,會進行 AND 型處理,將多個授權需求新增至單一授權原則。

注意

需求不需要有資料或屬性。

授權處理常式

需求授權處理常式負責評估需求的屬性。 授權處理常式會根據提供的 AuthorizationHandlerContext 評估需求,以判斷是否允許存取。

一個需求可以有多個處理常式。 處理常式可能會繼承 AuthorizationHandler<TRequirement>,其中 TRequirement 是需要處理的需求。 或者,處理常式可以直接實作 IAuthorizationHandler 來處理一種以上的需求。

針對一項需求使用一個處理常式

下列範例示範一對一關聯性,其中最低年齡處理常式會利用單一需求:

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   MinimumAgeRequirement requirement)
    {
        if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
                                        c.Issuer == "http://contoso.com"))
        {
            //TODO: Use the following if targeting a version of
            //.NET Framework older than 4.6:
            //      return Task.FromResult(0);
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(
            context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth && 
                                        c.Issuer == "http://contoso.com").Value);

        int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

上述程式碼會判斷目前的使用者主體是否具有已知且受信任的簽發者所發出的出生日期宣告。 當宣告遺失時,即無法進行授權,在此情況下會傳回已完成的工作。 當宣告存在時,會計算使用者的年齡。 如果使用者符合需求所定義的最低年齡,則會將授權視為成功。 當授權成功時,context.Succeed 會以符合的需求作為唯一參數來叫用。

針對多個需求使用一個處理常式

下列範例顯示一對多關聯性,其中權限處理常式可以處理三種不同類型的需求:

using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();

        foreach (var requirement in pendingRequirements)
        {
            if (requirement is ReadPermission)
            {
                if (IsOwner(context.User, context.Resource) ||
                    IsSponsor(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
            else if (requirement is EditPermission ||
                     requirement is DeletePermission)
            {
                if (IsOwner(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }

    private bool IsOwner(ClaimsPrincipal user, object resource)
    {
        // Code omitted for brevity

        return true;
    }

    private bool IsSponsor(ClaimsPrincipal user, object resource)
    {
        // Code omitted for brevity

        return true;
    }
}

上述程式碼會周遊 PendingRequirements — 這個屬性包含未標示為成功之需求的。 針對 ReadPermission 需求,使用者必須是擁有者或贊助者,才能存取要求的資源。 針對 EditPermissionDeletePermission 需求,使用者必須是擁有者,才能存取要求的資源。

處理常式註冊

處理常式會在設定期間在服務集合中註冊。 例如:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("AtLeast21", policy =>
            policy.Requirements.Add(new MinimumAgeRequirement(21)));
    });

    services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
}

上述程式碼會藉由叫用 services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>(); 來註冊為 singleton MinimumAgeHandler。 處理常式可以使用任何內建服務存留期來註冊。

在實作 IAuthorizationRequirementIAuthorizationHandler 的單一類別中,可以同時組合需求和處理常式。 此組合會在處理常式與需求之間建立緊密結合,而且只建議用於簡單需求和處理常式。 建立實作這兩個介面的類別,就不需要在 DI 中註冊處理常式了,因為內建的 PassThroughAuthorizationHandler 可讓需求自行處理。

如需範例,請參閱 AssertionRequirement 類別 ,瞭解 AssertionRequirement 是完全獨立類別中的需求和處理常式。

處理常式應該傳回什麼?

請注意,處理常式範例中的 Handle 方法不會傳回任何值。 如何指示成功或失敗的狀態?

  • 處理常式會藉由呼叫 context.Succeed(IAuthorizationRequirement requirement) 來指示成功,並傳遞已成功驗證的需求。

  • 處理常式通常不需要處理失敗,因為同一需求的其他處理常式可能會成功。

  • 若要保證失敗,即使其他需求處理常式成功,仍請呼叫 context.Fail

如果處理常式呼叫 context.Succeedcontext.Fail,則仍會呼叫所有其他處理常式。 這可讓需求產生副作用,例如記錄,即使另一個處理常式已成功驗證或失敗需求也一樣。 當設定為 false 時,InvokeHandlersAfterFailure 屬性會將呼叫 context.Fail 時處理常式的執行短路。 InvokeHandlersAfterFailure 預設為 true,在此情況下會呼叫所有處理常式。

注意

即使驗證失敗,也會呼叫授權處理常式。

為什麼需要多個處理常式?

如果您想要進行 OR 型的評估,請針對單一需求實作多個處理常式。 例如,Microsoft 有只能使用鑰匙卡開啟的門。 如果您把鑰匙卡留在家裡,接待員列印了一個臨時貼紙,並為您打開門。 在此案例中,您會有單一需求 BuildingEntry 和多個處理常式,每個處理常式都會檢查單一需求。

BuildingEntryRequirement.cs

using Microsoft.AspNetCore.Authorization;

public class BuildingEntryRequirement : IAuthorizationRequirement
{
}

BadgeEntryHandler.cs

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => c.Type == "BadgeId" &&
                                       c.Issuer == "http://microsoftsecurity"))
        {
            context.Succeed(requirement);
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

TemporaryStickerHandler.cs

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, 
                                                   BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => c.Type == "TemporaryBadgeId" &&
                                       c.Issuer == "https://microsoftsecurity"))
        {
            // We'd also check the expiration date on the sticker.
            context.Succeed(requirement);
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

請確定這兩個處理常式都已註冊。 如果任一個處理常式在原則評估 BuildingEntryRequirement 時成功,原則評估就會成功。

使用 func 來完成原則

在某些情況下,履行原則在程式碼中很容易表示。 使用 RequireAssertion 原則建立器設定原則時,可能會提供 Func<AuthorizationHandlerContext, bool>

例如,先前的 BadgeEntryHandler 可以重寫如下:

services.AddAuthorization(options =>
{
     options.AddPolicy("BadgeEntry", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                (c.Type == "BadgeId" ||
                 c.Type == "TemporaryBadgeId") &&
                 c.Issuer == "https://microsoftsecurity")));
});

存取處理常式中的 MVC 要求內容

您在授權處理常式中實作的 HandleRequirementAsync 方法有兩個參數:AuthorizationHandlerContext 和您正在處理的 TRequirement。 MVC 或 SignalR 之類的架構可以自由地將任何物件新增至 AuthorizationHandlerContext 上的 Resource 屬性,以傳遞額外的資訊。

使用端點路由時,授權通常會由授權中介軟體處理。 在此情況下,Resource 屬性是 HttpContext 的實例。 內容可用來存取目前的端點,並可用於探查您要路由的基礎資源。 例如:

if (context.Resource is HttpContext httpContext)
{
    var endpoint = httpContext.GetEndpoint();
    var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
    ...
}

透過傳統路由,或當授權是 MVC 授權篩選條件的一部分時,Resource 的值為 AuthorizationFilterContext 的實例。 此屬性可讓您存取 MVC 和 Razor Pages 所提供的 HttpContextRouteData 和其他所有內容。

Resource 屬性的使用是架構特定的。 使用 Resource 屬性中的資訊會將授權原則限制為特定架構。 使用 is 關鍵字轉換 Resource 屬性,然後確認轉換成功,以確保您的程式碼在其他架構上執行時不會與 InvalidCastException 衝突:

// Requires the following import:
//     using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
    // Examine MVC-specific things like routing data.
}

全域要求所有使用者都經過驗證

如需有關如何在全域範圍內要求所有使用者進行驗證的資訊,請參閱需要驗證的使用者