Asp.Net Core MVC RC2 授權(Authorization) 機制

  • 2180
  • 0

Asp.Net Core MVC RC2 授權(Authorization) 機制

  • 參考網址: https://docs.asp.net/en/latest/security/authorization/index.html
  • 第一種-Simple Authorization(簡單授權)
    • 只需要在Controller或Action加上[Authorize] Attribute
    • 效果~加上[Authorize]的Controller或Action, 只有己登入的使用者可以存取.
       
  • 第二種-Role based Authorization(以角色授權)
    • 使用方式1:單一角色~
      • [Authorize(Roles = "Administrator")]
      • 這樣寫代表必需要是Administrator角色的使用者才可存取
         
    • 使用方式2:多角色OR~
      • [Authorize(Roles = "HRManager,Finance")]
      • 這樣寫代表使用者屬於 HRManager  Finance 可以存取.
         
    • 使用方式3:多角色AND~
      • [Authorize(Roles = "PowerUser")]
        [Authorize(Roles = "ControlPanelUser")]
        public class ControlPanelController : Controller
        {
        }
      • 這樣寫,代表必須是PowerUser 而且是ControlPanelUser角色的使用者才可以存取
         
    • 使用方式4:Controller及Action給定不同角色~
      • 例1:
        • [Authorize(Roles = "Administrator, PowerUser")]
          public class ControlPanelController : Controller
          {
             public ActionResult SetTime()
             {
             }

             [Authorize(Roles = "Administrator")]
             public ActionResult ShutDown()
             {
             }
          }
        • 這樣寫,代表整個ControlPanelController Controller 只有是Administrator或是PowerUser的角色的使用者才能存取,而ShutDown() Action 只能是屬於Administrator的角色的使用者才能存取,猜Attribute程式上可能有做類似繼承覆寫的方式來達成
      • 例2:
        • [Authorize]
          public class ControlPanelController : Controller
          {
             public ActionResult SetTime()
             {
             }
             [AllowAnonymous]
             public ActionResult Login()
             {
             }
          }
        • 這樣寫,代表整個ControlPanelController Controller 只有己登入的使用者才能存取,而Login() Action 則不需登入都可以存取,猜Attribute程式上可能有做類似繼承覆寫的方式來達成
           
    • 使用方式5:政策式角色檢查(Policy based role checks)~
      • 在Startup.cs 的ConfigureServices()中增加『政策』(一個政策包含1到多個角色)
      • public void ConfigureServices(IServiceCollection services)
        {
           services.AddMvc();
           services.AddAuthorization(options =>
           {
               //一政策一角色 
               options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Administrator"));

               //一政策多角色
               options.AddPolicy("ElevatedRights", policy =>
                         policy.RequireRole("Administrator", "PowerUser", "BackupAdministrator"));   }
        }
      • 使用方式~~
        [Authorize(Policy = "RequireAdministratorRole")]
        public IActionResult Shutdown()
        {
           return View();
        }
         
  • 第三種-Claims-Based Authorization(使用者屬性方式授權)
    • 用法1~『是否含有屬性』的授權方式:
      • 『是否含有屬性』的新政策:在Startup.cs 的ConfigureServices()中增加『政策』(一個政策包含1到多個屬性 )
        以下的寫法指定了EmployeeOnly 政策中包含了一個EmployeeNumber屬性 
        public void ConfigureServices(IServiceCollection services)
        {
           services.AddMvc();

           services.AddAuthorization(options =>
           {
               options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
           });
        }
      • 例1:
        • [Authorize(Policy = "EmployeeOnly")]
          public IActionResult VacationBalance()
          {
             return View();
          }
        • 此寫法表示必須要符合EmployeeOnly政策(有EmployeeNumber屬性)的使用者, 才能存取 VacationBalance() Action
      • 例2:
        • [Authorize(Policy = "EmployeeOnly")]
          public class VacationController : Controller
          {
             public ActionResult VacationBalance()
             {
             }

             [AllowAnonymous]
             public ActionResult VacationPolicy()
             {
             }
          }
        • 此寫法表示必須符合EmployeeOnly政策的使用者才能存取VacationController Controller, 但VacationPolicy()例外,  VacationPolicy()在不登入的狀況下都可以存取
    • 用法2~『屬性值是否等於特定值』的授權方式:
      • 『屬性值是否等於特定值』的新政策:在Startup.cs 的ConfigureServices()中增加『政策』(一個政策包含1到多個屬性 )
        public void ConfigureServices(IServiceCollection services)
        {
           services.AddMvc();
           services.AddAuthorization(options =>
           {
               options.AddPolicy("Founders", policy =>
                                 policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
           }
        }
    • 用法3~多政策評估(Multiple Policy Evaluation)的授權方式:
      • [Authorize(Policy = "EmployeeOnly")]
        public class SalaryController : Controller
        {
           public ActionResult Payslip()
           {
           }

           [Authorize(Policy = "HumanResources")]
           public ActionResult UpdateSalary()
           {
           }
        }
      • 執行SalaryController必須是符合EmployeeOnly政策的使用者, 而執行UpdateSalary() Action 必須是同時符合EmployeeOnly及HumanResources政策的使用者
  • 第四種:Custom Policy-Based Authorization(客製化政策式授權)
    • 相關會使用到的類的介紹:
      • Requirements類別:
        • 是一個可以用来檢查目前使用者授權的政策資料參數的集合
        • 必需實作IAuthorizationRequirement
        • 此例為實作IAuthorizationRequirement界面的MinimumAgeRequirement~~
          public class MinimumAgeRequirement : IAuthorizationRequirement
          {
             public MinimumAgeRequirement(int age)
             {
                 MinimumAge = age;
             }
             protected int MinimumAge { get; set; }
          }
          (一個requirement類中,沒有任何資料或屬性(properites))
      • AuthorizationHandler<T>類別:=>授權處理器
        • 一個授權處理器是負責評測任何一個需求物件(Requirement)的屬性及評估AuthorizationContext物件,去做出是否被授權的決定。
        • 一個需求(Requirement)能有多個授權處理器
        • 授權處理器必需繼承AuthorizationHandler<T>類別, T是需要被處理的Requirement物件.
        • minimum age 授權處理器 應該看起來像以下這樣~~
          public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
          {
             protected override void Handle(AuthorizationContext context, MinimumAgeRequirement requirement)
             {
                 if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
                                            c.Issuer == "http://contoso.com"))
                 {
                     return;
                 }
                 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);
                 }
             }
          }
    • 使用方式~~
      • 在ConfigureServices()中註冊一個包含某Requirement的政策policy , 例如~~
        public void ConfigureServices(IServiceCollection services)
        {
           services.AddMvc();
           services.AddAuthorization(options =>
           {
               options.AddPolicy("Over21",
                                 policy => policy.Requirements.Add(new MinimumAgeRequirement(21)));
           });
           services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
        }
      • 一個授權控制器該回傳什麼?(What should a handler return?)
        • 不需回傳任何東西
        • 當授權控制器類中判定成功通過授權檢查時, 呼叫context.Succeed(IAuthorizationRequirement requirement)
        • 當授權控制器類中判定失敗時, 不呼叫任何method(), 直接return;
        • 一個Requirement可能有多個授權控制器, 所以多個授權控制器都會被執行, 每個授權控制器都有自己的授權判斷結果, 如果需要『針對此Requirement在某個最重要的授權控制器中, 判斷的結果為失敗時, 不管其他授權控制器結果為何, 希望直接判定此Requirement為失敗』時,在此重要的授權控制器中,直接呼叫context.Fail()
      • 為什麼我們要為一個requirement提供多個授權控制器類?(Why would I want multiple handlers for a requirement?)
        • 針對一個Requirement, 有多個OR的判斷條件(每種判斷條件分別由不同的授權控制器處理)
        • 舉例:
          她有一堆只能使用『門卡』開啟的『門』,或者當招待會為你開啟那些門, 因為你把『門卡』放在家忘了帶, 而且她把你貼上了一日健忘的耻辱的標籤, 在這個例子上, 有一個Requirement(進入建築物), 但是有兩個授權控制器來查驗是否可授權進入建築物(這兩個授權控制器間是OR的關係, 只要其中一個條件吻合(有帶門卡自己刷卡進入, 或是請她幫你開門), 就能進入建築物)
        • public class EnterBuildingRequirement : IAuthorizationRequirement{}

          public class BadgeEntryHandler : AuthorizationHandler<EnterBuildingRequirement>{
              protected override void Handle(AuthorizationContext context, EnterBuildingRequirement requirement)
              {
                  if (context.User.HasClaim(c => c.Type == ClaimTypes.BadgeId &&
                                                 c.Issuer == "http://microsoftsecurity"))
                  {
                      context.Succeed(requirement);
                  }
              }}

          public class HasTemporaryStickerOfShameHandler : AuthorizationHandler<EnterBuildingRequirement>{
              protected override void Handle(AuthorizationContext context, EnterBuildingRequirement requirement)
              {
                  if (context.User.HasClaim(c => c.Type == ClaimTypes.TemporaryBadgeId &&
                                                 c.Issuer == "http://microsoftsecurity"))
                  {
                      // We'd also check the expiration date on the sticker.
                      context.Succeed(requirement);
                  }
              }}

      • 如何在授權控制器中存取Request Context (Accessing Request Context In Handlers)
        • Asp.net MVC 框架可以在 AuthorizationContext物件的Resource屬性上任意的加入物件, 用來傳遞訊息給授權控制器.
        • 例如可以利用AuthorizationContext物件的Resource屬性來存取HttpContext, RouteData...等等物件.
          (下面的as語法~~as 運算子的用法就像是轉型作業。 不過,如果無法轉換的話, as 則會傳回 null ,而不會引發例外狀況。)
          (http://chshman310222.blogspot.tw/2015/01/csharp-as-operator.html)
        • var mvcContext = context.Resource as Microsoft.AspNet.Mvc.Filters.AuthorizationContext;

          if (mvcContext != null){
              // Examine MVC specific things like routing data.}

      • 可以使用DI(Dependent Injection)技術, 將授權控制器需要的物件傳入授權控制器中, 以下例為傳入一個LoggerFactory物件~~

        public class LoggingAuthorizationHandler : AuthorizationHandler<MyRequirement>{
            ILogger _logger;

            public LoggingAuthorizationHandler(ILoggerFactory loggerFactory)
            {
                _logger = loggerFactory.CreateLogger(this.GetType().FullName);
            }

            protected override void Handle(AuthorizationContext context, MyRequirement requirement)
            {
               _logger.LogInformation("Inside my handler");
               // Check if the requirement is fulfilled.
            }}

  • 第五種:資源式授權(Resource Based Authorization)
    • 一般我們所需要授權給某使用者的標的物是一些『資源』(如某個物件的某個屬性), 而非只是單純是針對Controller 或是 某個Action, 而這些『資源』都是在執行Action()中產生的, 所以無法使用Attribute的方式進行授權管控, 取而代之的是使用『imperative(迫切) authorization(授權)』的方式, 在Action()程式碼中呼叫一個授權function。
    • 在程式碼中進行授權~~
      • 授權物件己實作了IAuthorizationService, 被註冊在service collection 中, 且可以使用DI技巧, 將之注入Controller中...

        public class DocumentController : Controller{
            IAuthorizationService authorizationService;

            public DocumentController(IAuthorizationService authorizationService)
            {
                this.authorizationService = authorizationService;
            }}

      • IAuthorizationService有兩個Method~
        •  一個用來傳遞『resource(資源) and the policy name(政策名稱) 』另一個用來傳遞『resource(資源) and a list of requirements (Requirement物件清單)』, 以用來檢核policy(使用者相關屬性條件) 或requirement(自訂的使用者相關屬性條件)是否允許對resource(資源)取得授權。

          Task<bool> AuthorizeAsync(ClaimsPrincipal user,
                                    object resource,
                                    IEnumerable<IAuthorizationRequirement> requirements);

        • Task<bool> AuthorizeAsync(ClaimsPrincipal user,
                                    object resource,
                                    string policyName);

      • 先在Action()中載入需被授權的標的物物件, 再使用AuthorizeAsync() Method來進行授權檢查, 範例如下:

        public async Task<IActionResult> Edit(Guid documentId){
            Document document = documentRepository.Find(documentId);

            if (document == null)
            {
                return new HttpNotFoundResult();
            }

            if (await authorizationService.AuthorizeAsync(User, document, "EditPolicy"))
            {
                return View(document);
            }
            else
            {
                return new ChallengeResult();
            }}

      • 如何撰寫以資源為基礎的授權控制器?(Writing a resource based handler )
        • 撰寫以『資源』為基礎的授權控制器與 撰寫單純針對Requirement物件的授權控制器沒有什麼太大不同, 只是多傳入『資源』物件而己, 例如下例傳入了一個類型為Document的資源物件, 至於『資源』物件是什麼類型的, 依程式設計師設定這個授權控制器需要針對什麼類型的資源做管控而定~~

          public class DocumentAuthorizationHandler : AuthorizationHandler<MyRequirement, Document>{
              protected override void Handle(AuthorizationContext context,
                                             OperationAuthorizationRequirement requirement,
                                             Document resource)
              {
                  // Validate the requirement against the resource and identity.
              }}

        • 另外別忘了要在 Startup.cs的ConfigureServices() 將以資源為基礎的授權控制器做註冊

          services.AddInstance<IAuthorizationHandler>(
              new DocumentAuthorizationHandler());

      • 如果是要對『資源』做『增刪改查Requirement』的授權管控, 己有現成的Requirement類別可使用~~Microsoft.AspNet.Authorization.Infrastructure.OperationAuthorizationRequirement

        public static class Operations{
            public static OperationAuthorizationRequirement Create =
                new OperationAuthorizationRequirement { Name = "Create" };
            public static OperationAuthorizationRequirement Read =
                new OperationAuthorizationRequirement   { Name = "Read" };
            public static OperationAuthorizationRequirement Update =
                new OperationAuthorizationRequirement { Name = "Update" };
            public static OperationAuthorizationRequirement Delete =
                new OperationAuthorizationRequirement { Name = "Delete" };}

      • 在任何Action中可呼叫AuthorizeAsync() Method , 將傳入的requirement物件參數中設為Operations.Create或Operations.Read或Operations.Update或Operations.Delete, 舉例如下:

        if (await authorizationService.AuthorizeAsync(User, document, Operations.Read))
        {
            return View(document);
        }
        else
        {
            return new ChallengeResult();
        }

  • 第六種:View式授權
    • 常常我們會需要針對View中的UI元件,依使用者不同的權限, 決定是否要顯示或隱藏, 或依不同的使用者改變UI, 所以可以在View中加入授權控制器的呼叫, 來決定這些事情.
    • 將這句@inject IAuthorizationService AuthorizationService加入View中就可以在View中加入授權控制器的呼叫語法, 如果所有的View都要能使用授權控制器, 把這句放入Views資料夾下的_ViewImports.cshtml這個檔案中.
    • 單純Requrement或Policy的授權控制器呼叫範例~~

      @if (await AuthorizationService.AuthorizeAsync(User, "PolicyName"))
      {
          <p>This paragraph is displayed because you fulfilled PolicyName.</p>
      }

    • 以資源式的授權控制器呼叫範例~~

      @if (await AuthorizationService.AuthorizeAsync(User, Model, Operations.Edit))
      {
          <p><a class="btn btn-default" role="button"
              href="@Url.Action("Edit", "Document", new {id= Model.Id})">Edit</a></p>
      }

  • 尚有其他授權方式, 未完待續...
  • 到目前為止的結論:
    • 一個政策可同時包含 『1~多個角色(role)』 及 『1~多個屬性(claim)』
    • 在一個Controller中, 同時針對Controller及Action指定『角色(role)』時, 是以『Action』指定的角色為授權準則。
    • 在一個Controller中, 同時針對Controller及Action指定『政策(policy)』時, 是將Controller及Action的政策相加為授權準則。