ASP.NET Core Identity 實戰(4)受權過程

你們好,我是rocket robin, 這篇文章咱們將一塊兒來學習 Asp.Net Core 中的(注:這樣描述不許確,稍後你會明白)受權過程html

前情提要

在以前的文章裏,咱們有提到認證和受權是兩個分開的過程,並且認證過程不屬於Identity。一樣受權過程也不屬於Identity,受權過程放在Identity系列中將的緣由和認證過程同樣——和成員系統放在一塊兒容易理解。編程

動手作

在弄清的是受權過程在哪裏發生的以前,咱們先來動手寫一寫受權的代碼,若是瞭解策略受權,那麼你能夠快速瀏覽過這部分json

打開以前建立的項目,添加一個名爲Demo的控制器,控制器代碼以下:api

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

namespace IdentityDemo.Controllers
{
    [Produces("application/json")]
    [Route("api/demo")]
    public class DemoController : Controller
    {
        [Authorize]
        [HttpGet]
        public object Get()
        {
            return new
            {
                User.Identity.Name,
                User.Identity.IsAuthenticated
                略...

用以前註冊的帳戶登陸系統,
訪問/api/demo,你將獲得以下結果:mvc

{
    "name": "jbl-2011@163.com",
    "isAuthenticated": true
}

而後退出登陸,再次訪問/api/demo,那麼將會跳轉到登錄頁面,在這個過程當中Authorize特性起到了相當重要的做用,接下來去掉Authorize特性,重複上兩個操做,未登陸的結果將是:app

{
    "name": null,
    "isAuthenticated": false
}

經過這兩個小例子,咱們很容易就能推斷出Authorize特性攔截了沒有登錄的用戶,等等,是Authorize特性攔截了請求嗎?async

受權過程的發生地

很顯然,不是Authorize特性攔截了請求,特性只是標記了這個方法須要被受權才能訪問,而真正攔截了請求的是——「Mvc 中間件」。Action是由Mvc執行的,Mvc執行時會確認Action上的Authorize特性,來肯定是否要進行受權操做(成功受權能夠訪問,失敗了會被阻止(好比跳轉到登錄)),以及如何受權(動物園例子中,第二個門衛根據切實的狀況決定),也就是自定義受權(角色等等)。ide

另外,若是咱們只是簡單的爲 Action方法打上[Authorize]標記,那麼它的默認行爲就是驗證IsAuthenticated是不是true,也就是在認證環節(Authentication 中間件)是否經過了認證函數

如今,咱們知道了兩個點學習

  • 認證過程 Authentication 發生在 Authentication 中間件中
  • 受權過程 Authorization 發生在 Mvc中間件中

基於策略的靈活受權

在企業應用中最爲常見的就是基於角色的受權,實現角色受權的方式有兩種,一種是直接寫在Authorize特性上:

[Authorize(Roles = "admin,super-admin,")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Test()

不過這種方式,不推薦,由於這樣的話咱們就將「角色」和「Uri」的綁定「硬編碼在代碼裏了」,在不少場景這顯然不合適,因此接下來咱們要介紹的基於策略的受權就容許咱們自定義受權邏輯,這樣就靈活多了

基於策略Policy的受權

咱們假設咱們的受權規則是要求和上方代碼片斷實現相同效果,即用戶具備角色「admin」或者角色「super-admin」,咱們來逐步實現這個目標:

第一步在 DI 中註冊一個用於咱們須要的 policy

services.AddAuthorization(options =>
{
    options.AddPolicy("role-policy", policy =>
    {
        policy.AddRequirements(new RoleRequirement("admin","super-admin"));
    });
});

咱們爲該策略指定了一個名字role-policy,而且指定了這個策略的需求條件,需求條件主要是爲了設置策略的初始值,咱們能夠在策略註冊時更改需求條件從而靈活控制受權。

接下來咱們來編寫 RoleRequirement

public class RoleRequirement : IAuthorizationRequirement
{
    public IEnumerable<string> Roles { get;   }
    public RoleRequirement(params string[] roles)
    {
        Roles = roles ?? throw new ArgumentNullException(nameof(roles));
        略...

那咱們的 RoleRequirement 主要實現的功能就是肯定要包含的角色,由於要包含的角色是在構造函數中肯定的,那麼咱們就將角色受權的邏輯(稍後介紹的Handler)和具體受權的數據分開了。

而後咱們來實現RoleRequirement對應的處理程序:

public class RoleHandler : AuthorizationHandler<RoleRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
    {

        foreach (var item in requirement.Roles)
        {
            if (context.User.IsInRole(item))
            {
                context.Succeed(requirement);
                return Task.CompletedTask;
            }
        }
        context.Fail();
        return Task.CompletedTask;
        略...

這個處理器的工做十分簡單就是驗證當前用戶是否在任意一個由RoleRequirement指定的角色中。在這裏context.Succeed(requirement);指示受權成功,而受權失敗通常不須要調用 context.Fail();由於對於這個需求還可能有其它處理器進行處理,而此例中調用 context.Fail();能夠確保受權失敗,由於RoleRequirement的處理器只有一個,因此這樣作是沒有問題的。

要注意的是剛剛提到的,咱們已經將角色受權的邏輯(稍後介紹的Handler)和具體受權的數據分開了。

由於RoleHandler並不清楚要求用戶有哪些角色,RoleHandler只知道如何去驗證用戶含有哪些角色,而具體要求用戶含有哪些角色,是由 RoleRequirement 來決定的,這符合關注點分離和單一職責這兩個編程概念。

再而後,咱們要將剛剛寫好的RoleHandler註冊進Di

services.AddSingleton<IAuthorizationHandler, RoleHandler>();

最後一步,更換原來的Attribute:

// [Authorize(Roles = "admin,super-admin,")]
[Authorize(Policy ="role-policy")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Test()

如今,一個最基本的基於策略的受權就完成了。

本文中的示例較爲簡單,也並無使用所有的受權特性,更詳細的使用方法參考資料不少,本文也就很少作介紹。
另外你能夠參考ASP.NET Core中基於策略的受權來學習更過關於策略受權的內容

受權時指定AuthenticationScheme

指定AuthenticationScheme的代碼相似這樣:

// [Authorize(Roles = "admin,super-admin,")]
[Authorize(AuthenticationSchemes ="jwt"/*注意,這裏的名字取決於你添加AuthenticationHandler時的名字*/, Policy ="role-policy")]     [HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Test()

在上一篇博客ASP.NET Core Identity 實戰(3)認證過程中提到,在Authentication中間件中能夠放置多個Handler,而有一個是默認激活的,那麼剩下的是被動調用的,如今咱們的狀況就是由咱們在Authorize特性中去挑選一個Handler來執行,例如咱們在Authentication中間件上放置兩個Handler——CookieAuthenticationHandler和JwtAuthenticationHandler,並經CookieAuthenticationHandler指定爲默認,那麼咱們想經由Jwt認證時怎麼辦?

這裏有一個重要問題就是:當HttpContext流過Authentication中間件後纔到Mvc中間件,而Mvc在確認Action指定的AuthenticationHandler時,Authentication過程已經結束了

那這是怎麼作到的呢?

還記的HttpContext中有一個擴展方法叫AuthenticateAsync,做爲HttpContext的擴展方法也就意味着,咱們能夠在任什麼時候候調用它進行認證操做。

namespace Microsoft.AspNetCore.Authentication
{
    public static class AuthenticationHttpContextExtensions
    {
        public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context);
        public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme);
        略...

看它的第二個重載,它是指定了 AuthenticationScheme的名字的,因此在Mvc中間件探查到Attribute指定了AuthenticationScheme時,就會從新挑選指定的AuthenticationHandler再次對請求進行認證

受權的發生地——AuthorizationFilter

在舊的Asp.Net時代,咱們知道MvcFilter這個東西,如今它仍然在,若是你不瞭解它,我建議你稍做了解,建議參考官方文檔

正如這一節的標題,受權發生在Microsoft.AspNetCore.Mvc.Authorization.AuthorizationFilter中,受權的邏輯相似這樣:

先進行認證

若是指定了scheme,那麼從新認證,若是沒有,則使用以前 Authentication中間件的受權結果:

public virtual async Task<AuthenticateResult> Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator.AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
    {
        if (policy.AuthenticationSchemes != null && policy.AuthenticationSchemes.Count > 0)
        {
            ClaimsPrincipal newPrincipal = null;
            foreach (var scheme in policy.AuthenticationSchemes)
            {
                var result = await context.AuthenticateAsync(scheme);
                if (result != null && result.Succeeded)
                {
                    newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, result.Principal);
                }
            }

            if (newPrincipal != null)
            {
                context.User = newPrincipal;
                return AuthenticateResult.Success(new AuthenticationTicket(newPrincipal, string.Join(";", policy.AuthenticationSchemes)));
            }
            else
            {
                context.User = new ClaimsPrincipal(new ClaimsIdentity());
                return AuthenticateResult.NoResult();
            }
        }

        return (context.User?.Identity?.IsAuthenticated ?? false) 
            ? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User"))
            : AuthenticateResult.NoResult();
    }

這裏面值得再次深刻探討的是 context.AuthenticateAsync(scheme),這是在 HttpAbstractions項目中的擴展方法,它的實現是:

public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) =>
        context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, scheme);

IAuthenticationService咱們在 Authentication中間件中也見過,Authentication中間件也是使用了IAuthenticationService,以前的文章有提到過,這也再次證實了單一原則職責,身份認證中間件負責在管道中認證,而認證自己並不是是和身份認證中間件捆綁的,上一篇博客ASP.NET Core Identity 實戰(3)認證過程的最後有認證的源代碼

再進行受權

受權總共分三步

  1. 拿到IAuthorizeHandler的實例(前面咱們寫了一個)(可能一個或者多個
  2. 執行受權(每一個Handler都會進行受權)
  3. 沒了

這部分代碼仍是很簡單的:

public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements)
    {
        // 第一步
        var authContext = _contextFactory.CreateContext(requirements, user, resource);
        var handlers = await _handlers.GetHandlersAsync(authContext);
        // 第二部
        foreach (var handler in handlers)
        {
            await handler.HandleAsync(authContext);
            if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed)
            {
                break;
            }
        }

        // 沒了(這主要是對結果進行處理)
        var result = _evaluator.Evaluate(authContext);
        if (result.Succeeded)
        {
            _logger.UserAuthorizationSucceeded(GetUserNameForLogging(user));
        }
        else
        {
            _logger.UserAuthorizationFailed(GetUserNameForLogging(user));
        }
        return result;
    }

這裏面和咱們在項目中寫的代碼有關就是IAuthorizeHandler的實例,在本文中,咱們寫了一個RoleHandler

到此,受權過程就結束了,另一些就是邊邊角角的知識點,好比受權以後如何操做,這些不難,就再也不文中贅述了

相關文章
相關標籤/搜索