C#分散式登入——jwt

yswenli發表於2020-08-17

一、傳統的session登入

在伺服器儲存一份使用者登入的資訊,這份登入資訊會在響應時傳遞給瀏覽器,告訴其儲存為cookie,以便下次請求時傳送給我們的應用,這樣我們的應用就能識別請求來自哪個使用者了,這就是傳統的基於session認證。

 

在asp.net core中可以簡單實現:

 1   // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
 2         public void Configure(IApplicationBuilder app, IHostingEnvironment env)
 3         {
 4             if (env.IsDevelopment())
 5             {
 6                 app.UseDeveloperExceptionPage();
 7             }
 8             else
 9             {
10                 app.UseExceptionHandler("/Home/Error");
11                 app.UseHsts();
12             }
13  
14             app.UseHttpsRedirection();
15             //使用靜態檔案
16             app.UseStaticFiles();
17             //Cookie策略
18             //app.UseCookiePolicy();
19             //Session
20             app.UseSession();
21  
22             app.UseMvc(routes =>
23             {
24                 routes.MapRoute(
25                     name: "default",
26                    // template: "{controller=Home}/{action=Index}/{id?}");
27                    //template: "{controller=Home}/{action=Server}/{id?}");
28                    template: "{controller=Login}/{action=SignIn}/{id?}");
29             });
30         }
 1         public IActionResult SignIn(UserModel userModel)
 2         {
 3             if (ModelState.IsValid)
 4             {
 5                 //檢查使用者資訊
 6                 if (userModel.Username.Equals("yswenli") && userModel.Password.Equals("yswenli"))
 7                 {
 8                     //記錄Session
 9                     HttpContext.Session.Set("User", ByteConvertHelper.Object2Bytes(userModel));
10                     //跳轉到系統首頁
11                     return RedirectToAction("Server", "Home");
12                 }
13                 ViewBag.ErrorInfo = "使用者名稱或密碼錯誤";
14                 return View(userModel);
15             }
16             ViewBag.ErrorInfo = ModelState.Values.First().Errors[0].ErrorMessage;
17             return View(userModel);

 

但是這種基於session的認證使應用本身很難得到擴充套件,隨著不同客戶端使用者的增加,獨立的伺服器已無法承載更多的使用者,而這時候基於session認證應用的問題就會暴露出來。

傳統session的主要問題如下:

1.伺服器壓力: 每個使用者經過我們的應用認證之後,我們的應用都要在服務端做一次記錄,以方便使用者下次請求的鑑別,通常而言session都是儲存在記憶體中,而隨著認證使用者的增多,服務端的開銷會明顯增大。

2.擴充套件性: 使用者認證之後,服務端做認證記錄,如果認證的記錄被儲存在記憶體中的話,這意味著使用者下次請求還必須要請求在這臺伺服器上,這樣才能拿到授權的資源,這樣在分散式的應用上,相應的限制了負載均衡器的能力。這也意味著限制了應用的擴充套件能力。

3.CSRF: 因為是基於cookie來進行使用者識別的, cookie如果被截獲,使用者就會很容易受到跨站請求偽造的攻擊。

二、基於token的鑑權機制

基於token的鑑權機制是無狀態的,它不需要在服務端去保留使用者的認證資訊或者會話資訊,而是基於token去運算而實現鑑權。這就意味著基於token認證機制的應用不需要去考慮使用者在哪一臺伺服器登入了,這就為服務實現大規模分散式提供了基礎。

 

上圖是一種用token登入的實現方式,類似的還有很多,雖然實現了分散式的登入處理,但是由於不同的系統之間的不同實現,導致開發量劇增。

三、Json web token

這裡推薦使用JWT——Json web token(官網連結)。一個典型的JWT看起來如下圖:

 

 

 

jwt為一個字串,字元之間通過"."分隔符分為三個子串。注意JWT物件為一個長字串,各字串之間也沒有換行符,此處為了演示需要,特意分行並用不同顏色表示了。每一個子串表示了一個功能塊,總共有以下三個部分:JWT頭、有效載荷和簽名,將它們寫成一行如下:

 1 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJpYXQiOjE1OTM5NTU5NDMsInVpZCI6MTAsImV4cCI6MTU5Mzk1NTk3Mywic2NvcGVzIjpbImFkbWluIiwidXNlciJdfQ.VHpxmxKVKpsn2Iytqc_6Z1U1NtiX3EgVki4PmA-J3Pg 

一般是將它放入HTTP請求的Header Authorization欄位中

 1 Authorization: Bearer 

這裡可以開啟nuget:https://www.nuget.org/packages/JWT.Standard/,或者在vs中使用

 

 

輸入jwt.standard找到nuget包下載

1.生成jwt資料

在需要使用的地方輸入如下C#程式碼:

var jwtp = new JWTPackage<UserModel>(new UserModel()
{
    Id = "1",
    Name = "yswenli",
    Role = "Admin"
}, 180, _pwd);
var keyValuePair = jwtp.GetAuthorizationBearer();
context.HttpContext.Response.Headers[keyValuePair.Key] = keyValuePair.Value;

這樣就將需要的jwt內容資訊加入到Http頭部中,當然可以使用如下方式,以引數資料的方式傳遞,從而避免跨域問題

 1     var password = Guid.NewGuid().ToString("N");
 2 
 3     var jwtp1 = new JWTPackage<User>(new User()
 4     {
 5         Id = "1",
 6         Name = "yswenli",
 7         Role = "Admin"
 8     }, 180, password);
 9 
10     var sign = jwtp1.Signature;    

2.jwt解析驗證

JWTPackage<T>中使用JWTPackage<T>.Parse方法解析jwt的內容,如果內容是header中的引數,則快捷解析驗證程式碼如下:

 1 var result = string.Empty;
 2 try
 3 {
 4     if (context.HttpContext.Request.Headers.ContainsKey(keyValuePair.Key))
 5     {
 6         var val = context.HttpContext.Request.Headers[keyValuePair.Key].ToString();
 7 
 8         val = val.Replace(JWTPackage.Prex, "");
 9 
10         var jwt = JWTPackage<UserModel>.Parse(val, pwd);
11 
12         result = "OK";
13     }
14 }
15 catch (IllegalTokenException iex)
16 {
17     result = $"解析失敗:{iex.Message}";
18 }
19 catch (TokenExpiredException tex)
20 {
21     result = $"解析失敗:{tex.Message}";
22 }
23 catch (SignatureVerificationException sex)
24 {
25     result = $"解析失敗:{sex.Message}";
26 }
27 catch (Exception ex)
28 {
29     result = $"解析失敗:{ex.Message}";
30 }
31 return result;

四、JWT的問題

經過上述的簡單介紹,JWT不僅可用於認證,還可用於資訊交換,善用JWT有助於減少伺服器請求資料的次數。但是如果不正確的使用JWT也會造成安全問題,主要幾點如下:

1.保護好secret私鑰,加密的密碼不能洩漏,否則就失去了簽名的意義了

2.Replay Attacks,JWT的訊息體中最好加入生成時間,在後端中進行時間判定,小於規定時間的直接攔截

3.不應該在JWT的payload部分存放敏感資訊,因為該部分是客戶端可解密的部分

4.建議的方式是通過SSL加密的傳輸(https協議),從而避免敏感資訊被嗅探

 


轉載請標明本文來源:https://www.cnblogs.com/yswenli/p/13510050.html
更多內容歡迎star我的的github:https://github.com/cnwenli/JWT.Net
如果發現本文有什麼問題和任何建議,也隨時歡迎交流~

 

 

相關文章