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再次訪問