JSON Web Token(JWT)是一個開放標準(RFC 7519),它定義了一種緊湊且自包含的方式,用於在各方之間作為JSON物件安全地傳輸資訊。由於此資訊是經過數字簽名的,因此可以被驗證和信任。可以使用祕密(使用HMAC演算法)或使用RSA或ECDSA的公用/專用金鑰對對JWT進行簽名。
傳統token
當用登入成功後,服務端會根據使用者的資訊生成一個token,然後將token儲存到redis中。當使用者再次訪問時會攜帶這個token訪問,這時服務端會先去查一下redis,看有沒有這個token或者是檢視這個token是否過期,當redis中沒有這個token時,登入驗證失敗,提示使用者登入失敗,否則直接放行。當然傳統的token也有其一定的優勢,比如說返回token能遮蔽使用者的真實資訊,臨時且唯一。但當併發數量大的時候,這種模式存在的問題就是,使用者一訪問就去查redis,會增加redis的壓力。
JWT
jwt通常由三部分組成分別是:
- Header
- Payload
- Signature
Header通常由兩部分組成,令牌的型別(即JWT)和所使用的簽名演算法,例如HMAC SHA256或RSA。
{ "alg": "HS256", "typ": "JWT" }
Payload 令牌的第二部分是有效負載,其中包含宣告。宣告是有關實體(通常是使用者)和其他資料的宣告。有以下三種型別:註冊宣告,公共宣告和私人宣告。
標準註冊宣告
iss:jwt的發行方
sub:jwt宣告的主題
aud:jwt所面向的群體
exp:到期時間
nbf:(不早於)宣告標識了JWT之前的時間不得接受處理
公共宣告
使用JWT的人可以隨意定義這些宣告。主要包括使用者的各種資訊但要避免私密的資訊
私密宣告
私有宣告是釋出者和麵曏者所共同定義的宣告
{ "phone": "1234567890", "name": "John Doe", "admin": true }
Signature
signature是一個簽名資訊,是對前兩部分進行base64加密和secret一起進行組合加密,這裡的secret就相當於一個加鹽的操作
例如,如果要使用HMAC SHA256演算法,則將通過以下方式建立簽名:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
最後生成的token就是下面的這種格式
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoid293byIsInN1YiI6InN1Yk5hbWUiLCJuYmYiOiIxNTk5MzY3NjUwIiwiZXhwIjoxNTk5MzY3OTUwLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQ5OTk5IiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo0OTk5OSJ9.cqn55T-VFKkKuG2hSdQ_WNqjBhYiK9o3LvK-E9a893Y
通過jwt的工作原理,我們會發現jwt與傳統的token相比,jwt不用去redis中查詢對應的token資訊而是通過定義的加密演算法去進行校驗,這一塊算是對token的重大改進吧
.Net Core中使用JWT
1.通過nuget安裝 Microsoft.AspNetCore.Authentication.JwtBearer
2.在ConfigureServices中進行相應的註冊
//使用jwt進行認證 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, //是否驗證超時 當設定exp和nbf時有效 ValidateIssuerSigningKey = true, ////是否驗證金鑰 ValidAudience = "http://localhost:49999",//Audience ValidIssuer = "http://localhost:49998",//Issuer,這兩項和登陸時頒發的一致 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("123456888jdijxhelloworldprefect")), //拿到SecurityKey //緩衝過期時間,總的有效時間等於這個時間加上jwt的過期時間,如果不配置,預設是5分鐘 //注意這是緩衝過期時間,總的有效時間等於這個時間加上jwt的過期時間,如果不配置,預設是5分鐘 ClockSkew = TimeSpan.FromMinutes(5) //設定過期時間 }; });
2.在Configure中新增認證授權中介軟體
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseRouting(); app.UseAuthentication(); //認證 app.UseAuthorization(); //授權 app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
3.登入成功後生成token並返回客戶端
[HttpPost] public IActionResult Login(string username,string password) { var user = bll.GetUser(username, password); if (user != null) { var claims = new[] { new Claim(ClaimTypes.Name,username), new Claim(JwtRegisteredClaimNames.Sub, "subName"), new Claim(JwtRegisteredClaimNames.Nbf, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"), //NotBefore token生效時間 new Claim(JwtRegisteredClaimNames.Exp, $"{new DateTimeOffset(DateTime.Now.AddMilliseconds(1)).ToUnixTimeSeconds()}") //Expiration 到期時間,按秒數計算 }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("123456888jdijxhelloworldprefect")); //key的長度要超過16個字元,不然回丟擲異常 var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: "http://localhost:49999", //頒發token的web應用程式 audience: "http://localhost:49998", claims: claims, expires: DateTime.Now.AddMinutes(5), signingCredentials: creds); return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token),success=true,message="登入成功" }); } else { return BadRequest(new { success = false, message = "登入失敗,請重試" }); } }
用postman進行測試
1.呼叫登入介面生成token
2.不加token去訪問保護的資源
3.帶上生成的token再次訪問