1. 程式人生 > >.NetCore採取JWT方式進行身份認證

.NetCore採取JWT方式進行身份認證

 

驗證與授權

Authentication(身份認證)

認證是系統對請求的使用者進行身份識別的過程。

Authorization (授權)

授權是對認證通過後的使用者進行許可權分配的過程。授權簡單理解就是:識別認證後用戶所擁有哪些許可權,從而開放伺服器相對應的資源;

我們通俗點來解釋身份驗證與授權:驗證確認使用者是否允許訪問,授權給予登入後的使用者指定許可權標識(通過這個標識,服務端可允許使用者進行訪問和操作);

 

在進行講解Jwt身份認證前我們來回一下過去我們在MVC、WebForm這些站點都是怎樣進行身份認證的。

我想應該大多數都是基於Cookie、Form表單的身份驗證方式吧。

Cookie驗證方式的流程圖如下:

 


上面流程就是服務端在接收使用者登入請求後建立Cookie並儲存在伺服器上,然後通過Response.Cookies.Add將Cookie返回客戶端。

客戶端瀏覽器可以記住伺服器返回的Cookie,記住後Cookie值會儲存在客戶端硬碟上,下次訪問站點時候Http請求頭會攜帶Cookie。

攜帶Cookie的請求到服務端後,服務端進行驗證,驗證通過後即可正返回請求的資源,否則會跳轉到登入頁面。

Cookie方式的缺點

瞭解到Cookie方式的驗證流程後可以發現它是對Http請求強加了一層“會話”,讓我們的Http請求攜帶與身份認證相關的標識(Cookie)。

那這種方式有什麼不好的呢?我總結了下面幾點:

1、對有多種客戶端(APP、瀏覽器、Winform應用)請求的場景不好擴充套件;
2、對分散式架構的服務端不好擴充套件,客戶端的請求不一定指向同一臺伺服器,每一臺伺服器都需儲存針對已登入的使用者Cookie;
3、跨域訪問問題;

 

Jwt身份驗證方式

在多終端,分散式架構的服務無所不在的今天,Cookie身份認證的方式顯然是難以滿足我們的,這個時候Jwt(Json Web Token)橫空出世啦~ 

使用Jwt我們可以完美的解決上面的遺留問題(當然後面也會說到jwt本身存在的問題....)

我們先看下Jwt的身份驗證流程:

 

根據流程圖得知jwt是在返回Token給客戶端後要求客戶端在Http請求頭裡攜帶載有Jwt資訊的Authorization物件。

服務端會通過金鑰對Jwt資訊進行解密和驗證,驗證通過後會返回客戶端所請求的資源。

根據流程我們得知jwt至少依賴這幾項:

1、服務端的金鑰;

2、客戶端請求需指定格式(請求頭攜帶Token);

 

其中金鑰是客戶端接觸不到的,客戶端所擁有的其實就是服務端生成的Token,這個Token是由三部分組成:

1、Header(包含演算法和Token的型別:jwt)

2、PayLoad(負載,可配置的一些標識資料,不要將敏感資訊寫入到PayLoad內)

3、驗籤

其中Header和PayLoad只是在服務端進行了base64轉碼,所以如果有人抓取了Http請求是可以輕易擷取裡面的資料,我們

使用過程中千萬不要將一些敏感資訊放置裡面。想詳細瞭解Jwt資訊可以到官網看看,官網地址:https://jwt.io/

 

.Net Core進行Jwt身份認證

.NetCore上使用Jwt身份認證非常簡單,我們在Startup.cs檔案的ConfigureServices里加入以下程式碼:

 1   services.AddAuthentication(x =>
 2             {
 3                 x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
 4                 x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
 5             }).AddJwtBearer(x =>
 6             {
 7                 //獲取許可權是否需要HTTPS
 8                 x.RequireHttpsMetadata = false;
 9                 //在成功的授權之後令牌是否應該儲存在Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties中
10                 x.SaveToken = true;
11                 
12                 x.TokenValidationParameters = new TokenValidationParameters
13                 {
14                     //是否驗證祕鑰
15                     ValidateIssuerSigningKey = true,
16                     IssuerSigningKey = new SymmetricSecurityKey(key),
17                     ValidateIssuer = false,
18                     ValidateAudience = false
19                 };
20             });

 

在Startup裡的Configure方法必須新增下面這行程式碼,且要求寫在 app.UseMvc();前面。

 app.UseAuthentication(); //引用身份認證服務

 

上面的關鍵程式碼是Startup裡的程式碼。 它明確告知系統採取Jwt驗證方式為預設的身份認證方式、祕鑰的

生成方式,以及一些驗證相關的引數配置。

OK,現在我們已經明確了驗證方式了,那我們建立Token是在哪裡建立的呢?

我們來看看下面程式碼:

 1         private TokenDto CreateToken(User user)
 2         {
 3             // JwtSecurityTokenHandler可以建立Token
 4             var tokenHandler = new JwtSecurityTokenHandler();
 5             var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
 6             DateTime tokenExpires = DateTime.Now.AddMinutes(3); //過期時間這裡寫死
 7             DateTime refRefreshTokenExpires = tokenExpires.AddMinutes(-1);
 8             var tokenDescriptor = new SecurityTokenDescriptor
 9             {
10                 Subject = new ClaimsIdentity(new Claim[]
11                 {
12                     //新增申明,申明可以自定義,可以無限擴充套件,對於後續的身份驗證通過後的授權特別有用...
13                     new Claim(ClaimTypes.Name, user.Id.ToString()),
14                     new Claim("refRefreshTokenExpires",refRefreshTokenExpires.ToString()),
15                     new Claim("tokenExpires",tokenExpires.ToString())
16                 }),
17                 Expires = tokenExpires,
18                 IssuedAt = DateTime.Now, //Token釋出時間
19                 Audience = "AuthTest", //接收令牌的受眾
20                 //根據配置檔案的私鑰值設定Token
21                 SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
22             };
23             var token = tokenHandler.CreateToken(tokenDescriptor);
24             user.Token = tokenHandler.WriteToken(token);
25             TokenDto output = Mapper.Map<TokenDto>(user);
26             output.RefRefreshToken = Guid.NewGuid().ToString();
27             output.RefRefreshTokenExpires = refRefreshTokenExpires.ToString("yyyy-MM-dd HH:mm:ss");
28             output.Token = user.Token;
29             UserRefreshTokenData(user, output);
30             return output;
31         }

上面程式碼是包含了 重新整理Token和重新整理Token時間的建立Token方法。 

我在驗證使用者賬號密碼正確後即呼叫上面方法將Token返回給客戶端,要求客戶端在後續的API裡必須攜帶Token來進行訪問。

Token的有效時間我在上面寫死了為三分鐘,即三分鐘後訪問需授權的介面都會報http響應碼為401的錯誤,如果需要解決則

要求客戶端在重新整理時間內,攜帶重新整理Token值、重新整理時間來呼叫 重新整理Token介面獲取 重新整理後的Token值。

重新整理Token方法;

 1    public TokenDto RefreshToken(TokenDto oldTokenDto)
 2         {
 3             TokenDto output = new TokenDto();
 4             //如果有重新整理Token值對應的使用者則重新整理Token以及RefRefreshToken
 5             var user = _users.FirstOrDefault(p => p.RefRefreshToken == oldTokenDto.RefRefreshToken
 6                                             && Convert.ToDateTime(oldTokenDto.RefRefreshTokenExpires) > DateTime.Now);
 7             if (user != null)
 8             {
 9                 output = CreateToken(user);
10             }
11             return output;
12         }

 

至此,.NetCore採取JWT驗證身份方式就寫好了,包含了重新整理Token。

對了,還沒有總結JWT的劣勢,劣勢我捋了下面幾點:

1、沒有一個很方便的撤銷已頒發的JWT令牌方法;

2、續簽(重新整理Token)增加了客戶端的工作量(需要客戶端在請求前驗證重新整理Token時間);

 

不過上面兩個問題也不大,撤銷令牌可以採取黑名單方式,至於第2點其實問題也不大

與前端約定好流程即可;