asp.net core系列 50 Identity 受權(中)

1.5 基於策略的受權

  在上篇中,已經講到了受權訪問(authorization)的四種方式。其中Razor Pages受權約定和簡單受權二種方式更像是身份認證(authentication) ,由於只要是合法用戶登陸就能訪問資源。 而角色受權和聲明受權二種方式是真正的受權訪問(authorization)。git

  下面繼續講authorization的第五種方式--策略受權。策略受權由一個或多個需求(也能夠稱"要求")組成(需求:TRequirement)。它在程序啓動時註冊爲受權服務配置的一部分。在ConfigureServices方法中註冊。github

 

  (1) 註冊策略受權mvc

    建立了一個名爲"AtLeast21"的策略受權,這個策略的需求是最小年齡需求,策略經過參數對象(IAuthorizationRequirement)提供,它要求最低年齡是21歲。ide

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

                services.AddAuthorization(options =>
                 {
                     options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
                    // MinimumAgeRequirement參數對象 實現了IAuthorizationRequirement
                     options.AddPolicy("AtLeast21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21)));
                 });
}

  

  (2) 策略受權應用到mvc的控制器或Razor Pagesui

//用戶購買酒業務, 策略受權應用到控制器,要求用戶年齡不能低於21歲
[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseController : Controller
{
    public IActionResult Index() => View();
}
//策略受權到razor pages的PageModel類
[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseModel : PageModel
{
}

     在razor pages中,策略還能夠應用到razor page受權約定中。this

    public static PageConventionCollection AuthorizeFolder(this PageConventionCollection conventions, string folderPath, string policy);

 

  (3) Requirement策略受權需求編碼

    策略受權需求實現IAuthorizationRequirement接口,用於策略需求對象參數傳遞。MinimumAgeRequirement就是一個需求參數對象。spa

using Microsoft.AspNetCore.Authorization;
public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public int MinimumAge { get; }

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

 

  (4) 策略受權處理程序類code

    受權處理程序負責評估要求的屬性(指策略受權邏輯處理,把當前用戶的年齡與策略要求年齡進行驗證)。 受權處理程序會針對提供的AuthorizationHandlerContext 來評估要求,肯定是否容許訪問或拒絕。對象

         實現策略受權處理程序,須要繼承AuthorizationHandler<TRequirement>,其中TRequirement就是參數對象。另外,一個處理程序也能夠經過實現 IAuthorizationHandler 來處理多個類型的要求。

    下面是一對一關係的示例(一個Handler處理一個TRequirement對象),評估最低年齡要求:

   public class MinimumAgeHandler: AuthorizationHandler<MinimumAgeRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                        MinimumAgeRequirement requirement)
        {
            if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
                                            c.Issuer == "LOCAL AUTHORITY"))
            {
                //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 == "LOCAL AUTHORITY").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;
        }
    }

     上面代碼是當前用戶主休是否有一個由已知的受信任頒發者(Issuer)頒發的出生日期聲明(ClaimTypes.DateOfBirth)。當前用戶缺乏聲明時,沒法進行受權,這種狀況下會返回已完成的任務。若是存在聲明時,會計算用戶的年齡。 若是用戶知足此要求所定義的最低年齡,則能夠認爲受權成功。 受權成功後,會調用 context.Succeed,使用知足的要求做爲其惟一參數。

    

  (5) 處理程序注入到服務集合,採用單例

    services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();

    在UserClaim用戶聲明表中,保存一條符合該策略受權的數據,當啓動程序,訪問AlcoholPurchase資源時,進入受權處理程序MinimumAgeHandler中, 執行context.Succeed(requirement)後, 受權成功。

    

  

  1.5.1 多個需求使用一個處理程序

     下面是多個需求(TRequirement)使用一個處理程序,Handler實現IAuthorizationHandler接口,下面示例是一個對多關係的權限處理程序,能夠在其中處理三種不一樣類型的需求:

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
         //獲取策略中的多個需求,返回IEnumerable<IAuthorizationRequirement>類型
        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;
    }

     具體詳細代碼,查看官方示例, Github

  

  1.5.2 處理程序應返回什麼? (有三種返回)

                   (1) 處理程序經過調用 context.Succeed(IAuthorizationRequirement requirement) 並傳遞已成功驗證的要求來表示成功。

                   (2) 處理程序一般不須要處理失敗(顯示加context.Fail()),由於同一要求的其餘處理程序(1.5.3)可能會成功。

                   (3) 若要保證受權失敗,即便其它要求處理程序會成功,也會失敗,請調用context.Fail();   

    

  1.5.3  一個需求應用在多個處理程序

     這裏受權處理正好與15.1相反,下面這個示例是門禁卡受權策略需求, 你公司的門禁卡丟在家中,去公司後要求前臺給個臨時門禁卡來開門。這種狀況下,只有一個需求,但有多個處理程序,每一個處理程序針對單個要求進行檢查。

     //策略受權需求,這裏沒有參數需求, 參數是硬編碼在處理程序中
     public class BuildingEntryRequirement : IAuthorizationRequirement
      {
      }
//門禁卡處理程序
public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   BuildingEntryRequirement requirement)
    {
            //需求參數硬編碼 BadgeId 
        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;
    }
}
//臨時門禁卡處理程序
public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, 
                                                   BuildingEntryRequirement requirement)
    {
        //需求參數硬編碼 TemporaryBadgeId
        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;
    }
}
      // 註冊策略 
       services.AddAuthorization(options =>
            {
                  options.AddPolicy("BadgeEntry", policy =>policy.Requirements.Add(new BuildingEntryRequirement()));           
           });
    // 注入服務
     services.AddSingleton<IAuthorizationHandler, BadgeEntryHandler>();
     services.AddSingleton<IAuthorizationHandler, TemporaryStickerHandler>();

    當[Authorize(Policy = " BadgeEntry ")]應用到控制器後,只要有一個處理程序成功,則策略受權成功。須要在UserClaim用戶聲明表中維護好ClaimType。

 

  1.5.4 使用 func 知足策略

    有些狀況下,策略很容易用代碼實現。 能夠在經過 Func<AuthorizationHandlerContext, bool> 策略生成器配置策略時提供須要聲明 (RequireAssertion),例如上一個 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")));
    }); 

      

  總結:經過這二篇,熟悉了受權的五種方式,包括: 

    Razor Pages受權約定
    簡單受權
    角色受權
    聲明受權
    策略受權

    其中聲明受權包含了角色受權,經過ClaimTypes.Role 能夠在聲明中使用角色受權。

   public const string Role = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"

 

 參考文獻

    基於策略的受權

相關文章
相關標籤/搜索