什麼是JWT?
JSON Web令牌(JWT)是一個開放標準(RFC 7519),它定義了一種緊湊且自包含的方式,用於在各方之間安全地傳輸資訊作為JSON物件。由於此資訊是經過數字簽名的,因此可以被驗證和信任。可以使用祕密(使用HMAC演算法)或使用RSA或ECDSA的公鑰/私鑰對對JWT進行簽名。
儘管可以對JWT進行加密以提供雙方之間的保密性,但我們將重點關注已簽名的令牌。簽名的令牌可以驗證其中包含的宣告的完整性,而加密的令牌則將這些宣告隱藏在其他方的面前。當使用公鑰/私鑰對對令牌進行簽名時,簽名還證明只有持有私鑰的一方才是對其進行簽名的一方。由於缺乏安全性,所以不能把如密碼等敏感資訊放在令牌中。
什麼時候應該使用JWT?
以下是JSON Web令牌有用的一些情況:
-
授權:這是使用JWT的最常見方案。一旦使用者登入,每個後續請求將包括JWT,從而允許使用者訪問該令牌允許的路由,服務和資源。單一登入是當今廣泛使用JWT的一項功能,因為它的開銷很小並且可以在不同的域中輕鬆使用。
-
資訊交換:JSON Web令牌是在各方之間安全傳輸資訊的好方法。因為可以對JWT進行簽名(例如,使用公鑰/私鑰對),所以您可以確定發件人是他們所說的人。此外,由於簽名是使用標頭和有效負載計算的,因此您還可以驗證內容是否未被篡改。
JWT結構是什麼?
JSON Web令牌以緊湊的形式由三部分組成,這些部分由點(.
)分隔,分別是:
- 標頭
- 有效載荷
- 簽名
因此,JWT通常如下所示。
xxxxx.yyyyy.zzzzz
標頭:通常由兩部分組成,令牌類型和使用的簽名算法。
{ "alg": "HS256", "typ": "JWT" }
有效載荷:有三種說明型別,預定義宣告、公共宣告和私有宣告。宣告名稱僅是三個字元,因為JWT是緊湊的
- 預定義宣告:包括iss(發出者), exp(到期時間), sub(主題), aud(受眾)等,是推薦的但是不是強制的可以沒有。
- 公共宣告:公共宣告,這個部分可以隨便定義,但是要注意和 IANA JSON Web Token 衝突。
- 私有宣告:這個部分是共享被認定資訊中自定義部分。
簽名:要建立簽名部分,您必須獲取編碼的標頭,編碼的有效負載,機密,標頭中指定的演算法,並對其進行簽名。
例如,如果要使用HMAC SHA256演算法,則將通過以下方式建立簽名:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
簽名用於驗證訊息在此過程中沒有更改,並且對於使用私鑰進行簽名的令牌,它還可以驗證JWT的傳送者是它所說的真實身份。
組合在一起如下為輸出是三個由點分隔的Base64-URL字串:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUyMDAiLCJhdWQiOlsiYXBpIiwiYXBpIl0sIkd1aWQiOiIzM2NhZmJkNS1jZWEyLTRjOWMtYWZlYS01MDYyZjM3YWUyOTAiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOlsic3lzdGVtIiwiYWRtaW4iXSwiZXhwIjoxNjA2NjU2NjI0fQ.
JvDHuowbOnWiyxMIFc9gG5rw1LSSc0xx68L31oRfxS0
如何使用JWT
每當使用者想要訪問受保護的路由或資源時,使用者代理都應傳送JWT,通常使用承載模式在Authorization標頭中傳送JWT 。標頭的內容應如下所示:
Authorization: Bearer <token>
在某些情況下,這可以是無狀態授權機制。伺服器的受保護路由將在Authorization
標頭中檢查有效的JWT ,如果存在,則將允許使用者訪問受保護的資源。如果JWT包含必要的資料,則可以減少查詢資料庫中某些操作的需求,儘管這種情況並非總是如此。
如果令牌是在Authorization
標頭中傳送的,則跨域資源共享(CORS)不會成為問題,因為它不使用cookie。
下圖顯示瞭如何獲取JWT並將其用於訪問API或資源:
- 應用程式或客戶端向授權伺服器請求授權。這是通過不同的授權流程之一執行的。例如,典型的符合OpenID Connect的Web應用程式將
/oauth/authorize
使用授權程式碼流通過端點。 - 授予授權後,授權伺服器會將訪問令牌返回給應用程式。
- 該應用程式使用訪問令牌來訪問受保護的資源(例如API)。
請注意,使用簽名的令牌,令牌中包含的所有資訊都會暴露給使用者或其他方,即使他們無法更改它。這意味著您不應將機密資訊放入令牌中。
.net core的JWT驗證授權
我們直接新建一個.net core webapi的專案,我這裡版本是3.1的
1. 先使用nuget安裝:Microsoft.AspNetCore.Authentication.JwtBearer。注意版本和.net core版本的相容。net5的支援5.0.0+的版本,否則就用對應可以用的低版本吧。
2. 在appsettings.json配置檔案中寫好我們的 JWT 的配置引數如下:
"Jwt": {
"Secret": "your-256-bit-secret",
"Iss": "https://localhost:44355",
"Aud": "api"
}
3. 在Startup.cs的ConfigureServices方法中新增授權認證如下:
var jwtConfig = Configuration.GetSection("Jwt"); //生成金鑰 var symmetricKeyAsBase64 = jwtConfig.GetValue<string>("Secret"); var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64); var signingKey = new SymmetricSecurityKey(keyByteArray); //認證引數 services.AddAuthentication("Bearer") .AddJwtBearer(o => { o.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true,//是否驗證簽名,不驗證的畫可以篡改資料,不安全 IssuerSigningKey = signingKey,//解密的金鑰 ValidateIssuer = true,//是否驗證發行人,就是驗證載荷中的Iss是否對應ValidIssuer引數 ValidIssuer = jwtConfig.GetValue<string>("Iss"),//發行人 ValidateAudience = true,//是否驗證訂閱人,就是驗證載荷中的Aud是否對應ValidAudience引數 ValidAudience = jwtConfig.GetValue<string>("Aud"),//訂閱人 ValidateLifetime = true,//是否驗證過期時間,過期了就拒絕訪問 ClockSkew = TimeSpan.Zero,//這個是緩衝過期時間,也就是說,即使我們配置了過期時間,這裡也要考慮進去,過期時間+緩衝,預設好像是7分鐘,你可以直接設定為0 RequireExpirationTime = true, }; });
在Configure方法中新增 app.UseAuthentication() 和 app.UseAuthorization() 注意位置需要放置的位置:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>{ endpoints.MapControllers();});
}
4. 生成jwt令牌,在預設生成的控制器 WeatherForecastController 中新增如下生成令牌的方法:
[HttpPost] public IActionResult Authenticate() { var jwtConfig = Configuration.GetSection("Jwt"); //祕鑰,就是標頭,這裡用Hmacsha256演算法,需要256bit的金鑰 var securityKey = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtConfig.GetValue<string>("Secret"))), SecurityAlgorithms.HmacSha256); //Claim,JwtRegisteredClaimNames中預定義了好多種預設的引數名,也可以像下面的Guid一樣自己定義鍵名. //ClaimTypes也預定義了好多型別如role、email、name。Role用於賦予許可權,不同的角色可以訪問不同的介面 //相當於有效載荷 var claims = new Claim[] { new Claim(JwtRegisteredClaimNames.Iss,jwtConfig.GetValue<string>("Iss")), new Claim(JwtRegisteredClaimNames.Aud,jwtConfig.GetValue<string>("Aud")), new Claim("Guid",Guid.NewGuid().ToString("D")), new Claim(ClaimTypes.Role,"system"), new Claim(ClaimTypes.Role,"admin"), }; SecurityToken securityToken = new JwtSecurityToken( signingCredentials: securityKey, expires: DateTime.Now.AddMinutes(2),//過期時間 claims: claims ); //生成jwt令牌 return Content(new JwtSecurityTokenHandler().WriteToken(securityToken)); }
5. 使用jwt控制介面的訪問,我們在一個介面上新增一個特性 [Authorize(Roles ="admin")],表示需要有admin這個角色的jwt令牌才能訪問,沒有roles引數的話表示只要是可用的令牌就可以訪問,多個role角色可以疊加多個特性:
[HttpGet] [Authorize(Roles = "admin")] [Authorize(Roles = "system")] public IEnumerable<WeatherForecast> Get() { var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); }
6.測試,然後我們用postman就可以測試了,可以看到非常成功有資料。
進階
認證的時候可以新增事件,如下面的認證失敗事件、接收引數事件可以獲取url上的引數作為令牌而不是通過http的請求頭的Authorization。
services.AddAuthentication("Bearer") .AddJwtBearer(o => { o.Events = new JwtBearerEvents() { OnMessageReceived = context => { context.Token = context.Request.Query["access_token"]; return Task.CompletedTask; }, OnAuthenticationFailed = context => { // 如果過期,則把<是否過期>新增到,返回頭資訊中 if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) { context.Response.Headers.Add("Token-Expired", "true"); } return Task.CompletedTask; } }; });