AspNetCore 成長雜記(一):JWT授權鑑權之生成JWT(其一)

2023-04-19 18:01:42

引子

最近不知怎麼的,自從學了WebAPI(為什麼是這個,而不是MVC,還不是因為MVC的Razor語法比較難學,生態不如現有的Vue等框架,webapi很好的結合了前端生態)以後,使用別人的元件一帆風順,但是不知其意,突然很想自己實現一個基於的JWT認證服務,來好好了解一下這個內容。

起步

自從Session-Cookie方案逐漸用的越來越少,JWT的使用也變得成為主流的安全方案之一,但是在.NET Core的檔案(這裡的.NET Core指代原來的.Net Core以及之後的版本,檔案是微軟的開發者檔案)並沒有對JWT做詳細的介紹(可能是在微軟看來太簡單了,不值得細說),僅僅略帶一提而已,範例程式碼更是少得可憐,根本沒有什麼建設性的幫助作用,更像檔案工程師在水任務(但不得不說微軟的Indentity框架是真的強大,Spring Security的功能基本都實現了)。縱然是費盡心機找資料,鑽研檔案,還是所獲甚少。但是在不斷的努力之下還是找到很多方案的,其中比較有用的就拿幾個,我仔細研究實踐後得到了這幾篇文章,不求它有多大幫助,之希望它能幫更多人少走彎路。
然而這幾個方案大概可以分成兩類:

  1. 非對稱加密的JWT(常用於外部網路認證)
  2. 對稱加密的JWT (通常是內部系統)
    對比之下,非對稱的JWT更安全,更符號系統的安全需求,雖然增加了解密時間,但利大於弊。可是關於非對稱的JWT的文章卻很少,大部分都是關於對稱加密的JWT資料。對於這種情況,我自己也沒有什麼好的辦法,直到我在看一篇文章時,在Nuget上無意找到的一個包改變了我的認知————JWT(名字粗暴直接)。當然,你直接使用.NET的擴充套件庫也可以,這裡面有一個System.IdentityModel.Tokens.Jwt可以同樣使我們更快樂的建立JWT。關於這部分的內容,我也會在之後的時間單獨寫一篇文章來實驗。
    另外,對於API驗證測試工具,一般都是預設的Swagger,如果你喜歡更好用的工具,我推薦使用ApiFox或者EOLink

實施

首先建立一個WebAPI專案,至於是否在啟動後使用HTTPS,根據自己的需要,一般都是需要的。然後用Nuget或者Dotnet安裝JWT這個Nuget包即可開始,如果是ASP.NET Core這樣需要依賴注入環境的,推薦JWT.Extensions.AspNetCore這個包(強力推薦),可以更好的讓你開始,僅僅需要基本功能的只用JWT即可。
由於我這裡使用的是RSA1024bit,所以需要一個HTTPS的PEM或者CRT證書做CA,各位可以自己生成一個。
首先,我們需要為服務注入這個包的依賴,即使用builder.Services.AddAuthentication().AddJwt()來新增相關依賴。那為什麼是要使用這個方法呢?如果你通過物件瀏覽器檢視API會發現一個AddJwtDecoder的方法,同樣可以新增依賴,並且更靈活,如果反編譯就發現——AddJwt方法是對AddJwtDecoder的某個過載的呼叫,後面可以呼叫其他方法達成同樣的效果,所以推薦使用這個方法注入。

服務注入程式碼如下:
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtAuthenticationDefaults.AuthenticationScheme;
})
.AddJwt();

然後在應用認證中介軟體即可。

app.UseAuthentication();

完成這些工作以後,還需要建立一個用來根據使用者資訊生成JWT的控制器,為了防止使用HTTPGet被攻擊,我這裡採用了HTTPPost。
根據這個包的檔案,生成一個JWT字串非常容易,只需要建立一個x509物件或者兩個RSA物件作為公鑰和私鑰即可,我推薦使用這個包裡面提供的FluentApi方式,寫起來非常舒服,最後編碼生成JWT,完成。

生成JWT的程式碼如下:
var token = JwtBuilder.Create()
            .WithAlgorithm(algorithm) // 加密演演算法
            .AddClaim<string>("Account", accountName) //新增使用者資訊
            .AddClaim<string>("Passwd", passwdContext) //新增使用者密碼
            .Encode(); //編碼生成jwt

完整的控制器程式碼如下:
[Route("api/[controller]")]
[ApiController]
public class JwtController : ControllerBase
{
    private RSA publicKey = RSA.Create();
    private RSA privateKey = RSA.Create();
    private RS2048Algorithm? algorithm { get; set; }

    public JwtController()
    {
        algorithm = new RS2048Algorithm(publicKey, privateKey);
    }



    [HttpPost]
    public async Task<string> CreateJwt(string accountName, string passwdContext)
    {
        return await Task<string>.Run<string>(() =>
        {
            var token =
            JwtBuilder.Create()
            .WithAlgorithm(algorithm)
            .AddClaim<string>("Account", accountName)
            .AddClaim<string>("Passwd", passwdContext)
            .Encode();

            return token;
        });
    }
}

總結

JWT.Extensions.AspNetCore這個包是一個整合了常用jwt操作的包,可以讓你不必關心JWT的建立過程,這大大化簡了我們使用JWT的過程,在一定程度上提高了生產力。如果您喜歡這個庫,可以到專案主頁上新增一顆星。

注意:

經過本人的親身經歷,x509在.NET6之後的類庫X509Certificate2不能直接生成私鑰,需要使用該類的成員方法:public System.Security.Cryptography.X509Certificates.X509Certificate2 CopyWithPrivateKey (System.Security.Cryptography.ECDiffieHellman privateKey);建立一個帶有私鑰的副本,否則會出現私鑰在物件構造成功後出現NULL的情況。
如果沒有特殊必要,建議直接使用Rsa的成員方法直接生成一個Rsa物件來操作比較簡便,目前這個辦法還可以改進,歡迎各位留言。