認證授權方案之授權初識

艾三元發表於2020-06-28

1.前言

回顧認證授權方案之JwtBearer認證

授權

在上一篇中,我們通過JwtBearer的方式認證,瞭解在認證時,都是基於Claim的,因此我們可以通過使用者令牌獲取到使用者的Claims,在授權過程中對這些Claims進行驗證,從而來判斷是否具有獲取或執行目標資源操作的許可權。本章就來介紹一下 ASP.NET Core 的授權系統的簡單使用。

2.說明

授權與身份認證是相互獨立,但是,授權卻需要一種身份驗證機制,因此,身份驗證可以為當前使用者建立一個或多個標識,是確定使用者真實身份的過程。而授權是根據標識確定使用者可執行的操作的過程,其本質就是具有某種特性的使用者會有許可權訪問某個資源或者執行某個操作。例如:一個擁有管理員身份的使用者有建立人員、刪除人員、編輯人員和刪除人員的操作許可權,而一個非管理身份的使用者僅有讀取自己資訊的許可權。

這時候,你可能會問,究竟怎樣特性的使用者可以被授權訪問某個資源或執行某個操作。由此我們引出了授權策略的方式,可以根據使用者擁有的角色,也可以根據使用者的職位,部門甚至是性別,年齡等等特性進行授權。

通過建立授權策略方式,檢驗認證的使用者所攜帶的身份宣告(ClaimsPrincipal物件)與授權策略是否一致,從而確定使用者可否執行操作。

授權

3.授權

3.1. 基於角色

3.1.1 新增角色

將角色賦予某個控制器或控制器內的操作,指定當前使用者必須是其角色才能訪問請求資源。

可以使用Authorize屬性的Roles特性指定所請求資源的角色。

例如:

  • 分配了“admin”角色使用者進行訪問操作
[Authorize(Roles ="admin")]
public class WeatherForecastController : ControllerBase
{

}
  • 以逗號分隔角色名來允行多個角色訪問操作
[Authorize(Roles ="admin,user")]
public class WeatherForecastController : ControllerBase
{ 


}

其中只要滿足admmin或者user其一就可以進行訪問。

  • 同時滿足指定的多個角色進行的訪問操作
[Authorize(Roles = "admin")]
[Authorize(Roles = "user")]
public class WeatherForecastController : ControllerBase
{ 
}

3.1.2 新增策略的角色

可以建立策略的方式進行訪問控制,在配置授權服務中新增註冊授權服務策略。

在Startup.cs檔案中,通過ConfigureServices()配置服務,建立一個允許具有admin角色的使用者才能進行訪問的策略

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        //新增授權角色策略
        services.AddAuthorization(options =>
        {
            options.AddPolicy("BaseRole", options => options.RequireRole("admin"));
        });
        //或者指定多個允許的角色
        //services.AddAuthorization(options =>
        // {
        //    options.AddPolicy("MoreBaseRole", options => options.RequireRole("admin","user"));
        // });
    }

在控制器方法使用特性Policy的屬性進行策略應用

    [Authorize(Policy = "BaseRole")]
    public class WeatherForecastController : ControllerBase
    {
    
    }

3.2. 基於宣告

3.2.1新增宣告

對當前使用者必須擁有的宣告,並將宣告賦予某個控制器或控制器內的操作,因此,指定宣告必須持有對應的值才能訪問請求資源。

宣告要求基於策略,所以必須進行構建一個表示宣告要求的策略,才能進行授權。

最簡單的型別宣告是將判斷宣告是否存在,而不檢查值。

可以建立策略的方式進行訪問控制,在配置授權服務中新增註冊授權服務策略。

在Startup.cs檔案中,通過ConfigureServices()配置服務,建立一個允許具有宣告的使用者才能進行訪問的策略

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        //新增基於宣告的授權
        services.AddAuthorization(options =>
        {
            options.AddPolicy("BaseClaims", options => options.RequireClaim("name"));
        });
    }

BaseClaims宣告策略會檢查name當前標識是否存在宣告。

在控制器方法使用特性Policy的屬性進行策略應用

    [Authorize(Policy = "BaseClaims")]
    public class WeatherForecastController : ControllerBase
    {
    
    }

但是,大多時候,我們需要宣告包含值,只有指定允許值的列表,才能授權成功。所以,可以新增指定值。

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        //新增基於宣告的授權,指定允許值列表。
        services.AddAuthorization(options =>
        {
            options.AddPolicy("BaseClaims", options => options.RequireClaim("name","i3yuan"));
        });
    }

3.3 基於策略

上面介紹的基於角色和基於宣告的授權,都使用了要求、要求處理程式和預配置的策略。這些在構建上提供了便捷,但是最終都是生成授權策略。ASP.NET Core,設計了另一種靈活的授權方式,一種更豐富的可重複使用的授權結構,基於策略的授權,同時這也是授權的核心。

這節會先講一下授權策略的應用,在下一節中,會對授權策略的核心進行一步步的詳解。

在上面我們簡單的介紹了基於策略的角色授權,但是這種方式無非基於角色或者宣告多一些。

因此,這裡我們基於自定義策略授權的方式,實現授權。

自定義授權,就要我們自己寫策略提供器,自己根據不同的引數來生成不同的策略,重新實現策略的方式。策略要求由以下兩種元素組成:僅保留資料的要求類,以及對使用者驗證資料的授權處理程式。建立自定義要求,還可以進一步表達特定策略。

3.3.1. 定義許可權策略PermissionRequirement

定義一個許可權策略,這個策略幷包含一些屬性。

public class PermissionRequirement: IAuthorizationRequirement
{
    public string _permissionName { get; }

    public PermissionRequirement(string PermissionName)
    {
        _permissionName = PermissionName;
    }
}

3.3.2. 再定義一個策略處理類

public class PermissionRequirementHandler : AuthorizationHandler<PermissionRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
    {
        var role = context.User.FindFirst(c => c.Type == ClaimTypes.Role);
        if (role != null)
        {
            var roleValue = role.Value;
            if (roleValue==requirement._permissionName)
            {
                context.Succeed(requirement);
            }
        }
        return Task.CompletedTask;

授權處理程式讀取與角色使用者關聯的宣告,並檢查自定義的角色,如果角色匹則成功,否則無法返回成功。

這裡的自定義宣告是寫固定了,但是也可以通過資料庫或外部服務的方式進行執行查詢獲取使用者相關角色資訊相對應的判斷條件,從而在處理程式中進行判斷處理。

授權處理程式呼叫方法 Succeed,同時傳遞當前要求,以通知此要求已成功得到驗證。如果沒有傳遞要求,處理程式無需執行任何操作,可以直接返回內容。不過,如果處理程式要確定是否不符合要求(無論其他處理程式是否已成功驗證同一要求),將會對授權上下文物件呼叫方法 Fail

3.3.3. 下面展示瞭如何將自定義要求新增到策略

(請注意,由於這是自定義要求,因此沒有擴充套件方法,而必須繼續處理策略物件的整個 Requirements 集合):

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        //基於自定義策略授權
        services.AddAuthorization(options =>
        {
            options.AddPolicy("customizePermisson",
              policy => policy
                .Requirements
                .Add(new PermissionRequirement("admin")));
        });
        //此外,還需要在 IAuthorizationHandler 型別的範圍內向 DI 系統註冊新的處理程式:
        services.AddScoped<IAuthorizationHandler, PermissionRequirementHandler>();
        // 如前所述,要求可包含多個處理程式。如果為授權層的同一要求向 DI 系統註冊多個處理程式,有一個成功就足夠了。

    }

3.3.4. 應用自定義的策略的特性

指定當前使用者必須是應用對控制器或控制器內的操作,如

   [Authorize(Policy = "customizePermisson")]
    public class WeatherForecastController : ControllerBase
    { 
    }

4.場景

在上一篇認證授權方案之JwtBearer認證中,我們已經實現了獲取token的方式,這一次,我們實現一個以基於角色場景為例的認證授權。

在原來生成token的方式中,新增多一個宣告角色的Claim,如下:

new Claim(JwtClaimTypes.Role,"admin")

    [HttpGet]
    public IActionResult GetToken()
    {
        try
        {
            //定義發行人issuer
            string iss = "JWTBearer.Auth";
            //定義受眾人audience
            string aud = "api.auth";
            //定義許多種的宣告Claim,資訊儲存部分,Claims的實體一般包含使用者和一些後設資料
            IEnumerable<Claim> claims = new Claim[]
            {
                new Claim(JwtClaimTypes.Id,"1"),
                new Claim(JwtClaimTypes.Name,"i3yuan"),
                new Claim(JwtClaimTypes.Role,"admin"),
            };
            //notBefore  生效時間
            // long nbf =new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds();
            var nbf = DateTime.UtcNow;
            //expires   //過期時間
            // long Exp = new DateTimeOffset(DateTime.Now.AddSeconds(1000)).ToUnixTimeSeconds();
            var Exp = DateTime.UtcNow.AddSeconds(1000);
            //signingCredentials  簽名憑證
            string sign = "q2xiARx$4x3TKqBJ"; //SecurityKey 的長度必須 大於等於 16個字元
            var secret = Encoding.UTF8.GetBytes(sign);
            var key = new SymmetricSecurityKey(secret);
            var signcreds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            //String issuer = default(String), String audience = default(String), IEnumerable<Claim> claims = null, Nullable<DateTime> notBefore = default(Nullable<DateTime>), Nullable<DateTime> expires = default(Nullable<DateTime>), SigningCredentials signingCredentials = null
            var jwt = new JwtSecurityToken(issuer: iss, audience: aud, claims:claims,notBefore:nbf,expires:Exp, signingCredentials: signcreds);
            var JwtHander = new JwtSecurityTokenHandler();
            var token = JwtHander.WriteToken(jwt);
            return Ok(new
            {
                access_token = token,
                token_type = "Bearer",
            });
        }
        catch (Exception ex)
        {
            throw;
        }
    }

對控制器或控制器內的操作,指定當前使用者必須是其角色才能訪問請求資源,如WeatherForecastController.cs

[ApiController]
[Route("[controller]")]
[Authorize(Roles ="admin")]
public class WeatherForecastController : ControllerBase
{ 
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    private readonly ILogger<WeatherForecastController> _logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        var rng = new Random();
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        })
        .ToArray();
    }
}

5.執行

5.1. 獲取token

分別獲取role為admin和role為user的情況下頒發的token,只有在角色為admin的情況下才能授權通過。

5.2. 授權資源介面訪問

在role為admin的情況下

授權

授權

在role為user的情況下

授權

授權

由上可知,只有在角色為admin的情況下,才能訪問目標資源進行操作。

6.總結

  1. 從上一篇的認證到這一篇的授權階段,簡單的介紹了Asp.net Core的認證授權系統,對授權有了初步的認識以及使用,對授權進行劃分為兩種,一種是基於角色的授權,但隨著角色的增加會對處理授權產生限制,不適合表達複雜的授權邏輯。另一種是基於策略的身份驗證,策略包含一系列基於宣告的要求,以及基於可從 HTTP 上下文或外部源注入的其他任何資訊的自定義邏輯。這些要求各自與一個或多個處理程式相關聯,這些處理程式負責要求的實際計算。
  2. 可以發現,asp.net core提供的授權策略是一個非常強大豐富且靈活的認證授權方案,能夠滿足大部分的授權場景。
  3. 如果有不對的或不理解的地方,希望大家可以多多指正,提出問題,一起討論,不斷學習,共同進步。
  4. 因此,在後續的篇章中,會繼續探索授權系統,對授權策略的核心進行一步步的詳解。
  5. 本示例原始碼地址

參考文獻文件

相關文章