驗證與授權
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時間);
3、Token的加密與解密是與開發者自定義的payload的大小有關,如果存儲的東西越多自然執行的效率也會越低(建議存儲少量信息);
不過上面問題也不大,撤銷令牌可以采取黑名單方式,至於第2點其實問題可以與前端約定好流程即可;