引子 最近不知怎麼的,自從學了WebAPI(為什麼是這個,而不是MVC,還不是因為MVC的Razor語法比較難學,生態不如現有的Vue等框架,webapi很好的結合了前端生態)以後,使用別人的組件一帆風順,但是不知其意,突然很想自己實現一個基於的JWT認證服務,來好好瞭解一下這個內容。 起步 自從S ...
引子
最近不知怎麼的,自從學了WebAPI(為什麼是這個,而不是MVC,還不是因為MVC的Razor語法比較難學,生態不如現有的Vue等框架,webapi很好的結合了前端生態)以後,使用別人的組件一帆風順,但是不知其意,突然很想自己實現一個基於的JWT認證服務,來好好瞭解一下這個內容。
起步
自從Session-Cookie方案逐漸用的越來越少,JWT的使用也變得成為主流的安全方案之一,但是在.NET Core的文檔(這裡的.NET Core指代原來的.Net Core以及之後的版本,文檔是微軟的開發者文檔)並沒有對JWT做詳細的介紹(可能是在微軟看來太簡單了,不值得細說),僅僅略帶一提而已,實例代碼更是少得可憐,根本沒有什麼建設性的幫助作用,更像文檔工程師在水任務(但不得不說微軟的Indentity框架是真的強大,Spring Security的功能基本都實現了)。縱然是費盡心機找資料,鑽研文檔,還是所獲甚少。但是在不斷的努力之下還是找到很多方案的,其中比較有用的就拿幾個,我仔細研究實踐後得到了這幾篇文章,不求它有多大幫助,之希望它能幫更多人少走彎路。
然而這幾個方案大概可以分成兩類:
- 非對稱加密的JWT(常用於外部網路認證)
- 對稱加密的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對象來操作比較簡便,目前這個辦法還可以改進,歡迎各位留言。