歡迎光臨
每天分享高質量文章

ASP.NET Core 實戰:基於 Jwt Token 的權限控制全揭露

一、前言

  在涉及到後端專案的開發中,如何實現對於用戶權限的管控是需要我們首先考慮的,在實際開發過程中,我們可能會運用一些已經成熟的解決方案幫助我們實現這一功能,而在 Grapefruit.VuCore 這個專案中,我將使用 Jwt 的方式實現對於用戶的權限管控,在本章中,我將演示如何使用 Jwt 實現對於用戶的授權、鑒權。

  系列目錄地址:ASP.NET Core 專案實戰
倉儲地址:https://github.com/Lanesra712/Grapefruit.VuCore

 二、Step by Step

  1、一些概念

  Jwt(json web token),是一種基於 Json 的無狀態授權令牌,因為 Jwt 是一種標準的資料傳輸規範,並不是某家所獨有的技術規範,因此非常適用於構建單點登錄服務,為 web、client、app 等等各種接口使用方提供授權服務。

  在使用 Jwt 進行權限控制的過程中,我們需要先請求授權服務器獲取到 token 令牌,將令牌儲存到客戶端本地(在 web 專案中,我們可以將 token 儲存到 localstorage 或是 cookie 中),之後,對於服務端的每一次請求,都需要將獲取到的 token 信息添加到 http 請求的 essay-header 中。

 

$.ajax({
    url: url,
    method: "POST",
    data: JSON.stringify(data),
    beforeSend: function (xhr) {        /* Authorization essay-header */
        xhr.setRequestHeader("Authorization", "Bearer " + token);
    },
    success: function (data) {}
});

 

  當用戶擁有令牌後是否就可以訪問系統的所有功能了呢?答案當然否定的。對於一個系統來說可能會有多種用戶角色,每一個用戶角色可以訪問的資源也是不同的,所以,當用戶已經擁有令牌後,我們還需要對用戶角色進行鑒定,從而做到對用戶進行進一步的權限控制。

  在 Grapefruit.VuCore 這個專案中,我採用的是基於策略的授權方式,通過定義一個授權策略來完善 Jwt 鑒權,之後將這個自定義策略註入到 IServiceCollection 容器中,對權限控製做進一步的完善,從而達到對於用戶訪問權限的管控。

  基於策略的授權是微軟在 ASP.NET Core 中添加的一種新的授權方式,通過定義好策略(policy)的一個或多個要求(requirements),將這個自定義的授權策略在 Startup.ConfigureServices 方法中作為授權服務配置的一部分進行註冊之後即可按照我們的策略處理程式進行權限的控制。

就像我在後面的代碼中一樣,我定義了一個名叫 Permission 的授權策略,它包含了一個叫做 PolicyRequirement 的鑒權要求,在實現了授權策略後,將基於這個要求的鑒權方法 PolicyHandler 以單例(AddSingleton)的形式註入到服務集合中,此時,我們就可以在需要添加驗證的 controller 上添加 attribute 即可。

[Authorize(Policy = "Permission")]
public class SecretController : ControllerBase
{}

  2、授權

  在 Grapefruit.VuCore 這個專案中,涉及到授權相關的代碼所在的位置我已在下圖中進行標示。在之前系列開篇文章(ASP.NET Core 實戰:使用 ASP.NET Core Web API 和 Vue.js,搭建前後端分離框架)進行介紹整個專案框架時曾說到, Grapefruit.Application 是專案的應用層,顧名思義,就是為了實現我們專案中的實際業務功能所劃分的類庫。因此,我們實現 Jwt 的相關業務代碼應該位於此層中。同時,因為對於 Jwt 的令牌頒發與鑒權,採用的是微軟的 JwtBearer 組件,所以我們在使用前需要先通過 Nuget 將取用添加到 Grapefruit.Application 上。

Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
Install-Package System.IdentityModel.Tokens.Jwt

  在 Grapefruit.Application 這個類庫下我創建了一個  Authorization 解決方案檔案夾用來儲存授權相關的代碼。在 Authorization 這個解決方案檔案夾中包含了兩個子檔案夾 Jwt 和 Secret。Jwt 檔案夾中主要包含我們對於 Jwt 的操作,而 Secret 檔案夾下則是對於用戶的相關操作。

  每個子應用檔案夾(Jwt、Secret)都包含了相同的結構:Dto 資料傳輸物件、功能接口,以及功能接口的實現類,這裡接口的繼承採用單繼承的方式。

  在 Jwt 檔案夾下創建一個 IJwtAppService 接口檔案,在這裡定義我們對於 Jwt 的相關操作。因為對於 Jwt 的授權、鑒權是採用微軟的 JwtBearer 組件,我們只需要進行配置即可,所以這裡只定義對於 token 的生成、掃清、停用,以及判斷這個 token 是否有效這幾個方法。同時,我們需要創建 JwtAppService 這個類檔案,去繼承 IJwtAppService 從而實現接口功能。

  JwtAuthorizationDto 是一個 token 信息的傳輸物件,包含我們創建好的 token 相關信息,用來將 token 信息傳回給前臺進行使用。而 UserDto 則是用戶登錄獲取 token 時的資料傳輸物件,用來接收登錄時的引數值。

  在創建 token 或是驗證 token 時,像 token 的頒發者、接收者之類的信息,因為會存在多個地方呼叫的可能性,這裡我將這些信息存放在了配置檔案中,後面當我們需要使用的時候,只需要通過註入 IConfiguration 進行獲取即可。關於 Jwt 的配置檔案主要包含了四項:token 的頒發者,token 的接收者,加密 token 的 key 值,以及 token 的過期時間,你可以根據你自己的需求進行調整。

"Jwt": {    "Issuer": "yuiter.com",    "Audience": "yuiter.com",    "SecurityKey": "a48fafeefd334237c2ca207e842afe0b",    "ExpireMinutes": "20"}

  在 token 的創建過程中可以簡單拆分為三個部分:根據配置信息和用戶信息創建一個 token,將加密後的用戶信息寫入到 HttpContext 背景關係中,以及將創建好的 token 信息添加到靜態的 HashSet 集合中。

  在 token 創建、校驗的整個生命周期中,都涉及到了  Scheme、Claim、ClaimsIdentity、ClaimsPrincipal 這些概念,如果你之前有使用過微軟的 Identity 權限驗證,對於這幾個名詞就會比較熟悉,可能某些小伙伴之前並沒有使用過 Identity,我來簡單介紹下這幾個名詞的含義。

  Scheme 樣式,這個與其餘的名詞相對獨立,它主要是指明我們是以什麼授權方式進行授權的。例如,你是以 cookie 的方式授權或是以 OpenId 的方式授權,或是像這裡我們使用 Jwt Bearer 的方式進行授權。

  Claim 宣告,以我們的現實生活為例,我們每個人都會有身份證,上面會包含我們的姓名、性別、民族、出生日期、家庭住址、身份證號,每一項資料的都可以看成是 type-value(資料型別-資料值),例如,姓名:張三。身份證上的每一項的信息就是我們的 Claim 宣告,姓名:張三,是一個 Claim;性別:男,也是一個 Claim。而對於 ClaimsIdentity,就像這一項項的信息最終組成了我們的身份證,這一項項的 Claim 最終組成了我們的 ClaimsIdentity。而 ClaimsPrincipal 則是 ClaimsIdentity 的持有者,就像我們擁有身份證一樣。

  從上面的文字可以總結出,Claim(每一項的證件信息)=》ClaimsIdentity(證件)=》ClaimsPrincipal(證件持有者)。其中,一個 ClaimsIdentity 可以包含多個的 Claim,而一個 ClaimsPrincipal 可以包含多個的 ClaimsIdentity。

   如果想要深入瞭解 ASP.NET Core 的授權策略的可以看看園子里這篇文章 =》ASP.NET Core 運行原理解剖[5]:Authentication,或是國外的這篇介紹 ASP.NET Core 授權的文章 =》Introduction to Authentication with ASP.NET Core。

  實現 token 生成的最終代碼實現如下所示,可以看到,在創建 ClaimsIdentity “證件”信息時,我添加了用戶的角色信息,並把加密後的用戶信息寫入到 HttpContext 背景關係中,這樣,我們在後面驗證的時候就可以通過 HttpContext 獲取到用戶的角色信息,從而判斷用戶是否可以訪問當前請求的地址。

  當創建好 token 之後,客戶端就可以在 Http 請求的 essay-header 中添加 token 信息,從而訪問受保護的資源。不過,在某些情況下,比如說,用戶修改了密碼之後,雖然當前的 token 信息可能還未過期,但我們也不能允許用戶再使用當前的 token 信息進行接口的訪問,這時,就涉及到了對於 token 信息的停用以及掃清。

  這裡我採用是當我們停用 token 信息時,將停用的 token 信息添加到 Redis 快取中,之後,在用戶請求時判斷這個 token 是不是存在於 Redis 中即可。

  當然,你也可以在停用當前用戶的 token 信息時,將 HashSet 中的這個 token 信息進行刪除,之後,通過判斷訪問時的 token 信息是否在 HashSet 集合中,判斷 token 是否有效。

  方法很多,看你自己的需求了。

  對於 Redis 的讀寫操作,我是使用微軟的 Redis 組件進行的,你可以按照你的喜好進行修改。如果你和我一樣,採用這個組件,你需要在 Grapefruit.Application 這個類庫中通過 Nuget 添加微軟的分佈式快取抽象接口 dll 的取用,以及在 Grapefruit.WebApi 專案中添加微軟的 Redis 實現。

Install-Package Microsoft.Extensions.Caching.Abstractions 
## 分佈式快取抽象接口
Install-Package Microsoft.Extensions.Caching.Redis
## Redis 實現

  當我們停用 token 時,通過 HttpContext 背景關係獲取到 HTTP Header 中的 token 信息,將該 token 信息儲存到 Redis 快取中,這樣,我們就完成了對於 token 的停用。

  對於 token 的掃清,其實我們就可以看成重新生成了一個 token 信息,只不過我們需要將之前的 token 信息進行停用。

  至此,我們對於 token 的創建、掃清、停用的代碼就已經完成了,接下來,我們來實現對於 token 信息的驗證。PS:下麵的代碼如無特殊說明外,均位於 Startup 類中。

  3、鑒權

  在 ASP.NET Core 應用中,依賴註入隨處可見,而我們對於我們的功能方法的使用,也是採用依賴註入到容器,通過功能接口進行呼叫的方式。因此,我們需要將我們的接口與其實現類註入到 IServiceCollection 容器中。這裡,我們採用反射的方式,批量的將程式集內的接口與其實現類進行註入。

 因為基礎的權限驗證我們是採用的微軟的 JwtBearer 權限驗證組件進行的授權和鑒權,因此對於 token 信息的基礎鑒權操作,只需要我們在中間件中進行配置即可。同時,我們也在 IJwtAppService 接口中定義了對於 token 信息的一些操作,而對於我們自定義的權限驗證策略,則需要通過基於策略的授權方式進行實現。

  首先,我們需要先定義一個繼承於 IAuthorizationRequirement 的自定義授權要求類 PolicyRequirement。在這個類中,你可以定義一些屬性,通過有參建構式的方式進行構造,這裡我不定義任何的屬性,僅是創建這個類。

public class PolicyRequirement : IAuthorizationRequirement
{ }

  當我們創建好 PolicyRequirement 這個權限要求類後,我們就可以通過繼承 AuthorizationHandler 來實現我們的授權邏輯。這裡實現權限控制的代碼邏輯,主要是通過重寫 HandleRequirementAsync 方法來實現的。

  在判斷用戶是否可以訪問當前的請求地址時,首先需要獲取到用戶角色與其允許訪問的地址串列,這裡我使用的是模擬的資料。通過判斷當前登錄用戶的角色是否包含請求的地址,當用戶的角色並不包含對於訪問地址的權限時,傳回 403 Forbidden 狀態碼。

  這裡需要註意,如果你準備採取 RESTful 風格的 API,因為請求的地址是相同的,你需要添加一個 HTTP 謂詞引數用來指明所請求的方法,從而達到訪問權限管控的目的。。

  包含 token 的基礎驗證的授權配置的代碼如下所示。在中間件進行 Jwt 驗證的過程中,會驗證授權方式是不是 Bearer 以及通過 token 的屬性解密之後與生成時用戶資料進行比對,從而判斷這個 token 是否有效。

  因為我們是使用 Swagger 進行的 API 文件的可視化,這裡,我們繼續配置 Swagger 從而使 Swagger 可以支持我們的權限驗證方式。

 

  在停用 token 的代碼中,我們使用了 Redis 去儲存停用的 token 信息,因此,我們需要配置我們的 Redis 連接。

  現在,整個業務相關的代碼已經完成了,我們可以創建前端訪問的接口了。這裡我是在 Controllers 下的 V1 檔案夾下創建了一個 SecretController 用來構建前端訪問的接口。控制器中主要有三個方法,分別為 CancelAccessToken(停用 token)、Login(獲取 token)以及 RefreshAccessTokenAsync(掃清 token)。

 現在,讓我們測試一下,從下圖中可以看到,當我們未獲取 token 時,訪問接口提示我們 401 Unauthorized,當我們模擬登錄獲取到 token 信息後,再次訪問受保護的資源時,已經可以獲取到響應的資料。之後,當我們掃清 token,此時再用原來的 token 信息訪問時,已經無法訪問,提示 403 Forbidden,同時,可以看到我們的 Redis 中已經存在了停用的 token 信息,此時,使用新的 token 信息又可以訪問了。

  至此,整個的 Jwt 授權鑒權相關的代碼就已經完成了,因為篇幅原因,完整的代碼請到 Github 上進行查看(電梯直達)。PS:因為博客園允許上傳的圖片限制最大尺寸為 10M,所以這裡上傳的 gif 是壓縮後的,見諒見諒,如果有需要查看清晰的圖片,歡迎到我的個人博客上查看(電梯直達)。

、總結

   本章,主要是使用 Jwt 完成對於用戶的授權與鑒權,實現了對於用戶 token 令牌的創建、掃清、停用以及校驗。在實際的開發中,採用成熟的輪子可能是更好的方案,如果你有針對 Jwt 進行用戶授權、鑒權更好的解決方案的話,歡迎你在評論區留言指出。拖了很久,應該是年前的最後一篇了,提前祝大家新年快樂哈~~~

赞(1)

分享創造快樂