今天我們來聊一聊關於JWT授權的事情。
JWT:Json Web Token。顧名思義,它是一種在Web中,使用Json來進行Token授權的方案。
既然沒有找好密碼,token是如何解決信任問題的呢?
解決信任問題,只需要解決兩個問題即可:
token是不是來自我信任的機構頒發
token中的資訊是否被篡改
對於第一個問題而言,確認token確實是由被信任的第三方頒發的,一般都是通過加密演演算法來建立信任,頒發時使用金鑰進行加密,如果能夠對加密內容進行正常解密說明token來自信任方。常用的加密演演算法分為:
對稱可逆加密:使用同一個祕鑰來加密解密,如果token能解密就能證明來源,祕鑰不對外公開
非對稱可逆加密:使用一組祕鑰對(私鑰加密+公鑰解密),如果token能使用公鑰進行解密就能證明來源,公鑰與私鑰之間互相不可推算
優缺點:
對稱可逆加密效率高,速度快,但是由於對稱可逆加密使用的是同一個祕鑰,所以必須向解密的應用提供祕鑰,相對而言不安全,所以一般只用於內部應用之間。
非對稱可逆加密速度相對慢一些,但是加密時通過私鑰加密而解密時只需要提供公鑰即可,所以用於對外提供加密機制更加安全可靠,所以多用於向第三方提供加密服務時使用。
演演算法舉例:
HS256
HS256 (帶有 SHA-256 的 HMAC 是一種對稱演演算法, 雙方之間僅共用一個 金鑰。由於使用相同的金鑰生成簽名和驗證簽名, 因此必須注意確保金鑰不被洩密。
2 RS256
RS256 (採用SHA-256 的 RSA 簽名) 是一種非對稱演演算法, 它使用公共/私鑰對: 標識提供方採用私鑰生成簽名, JWT 的使用方獲取公鑰以驗證簽名。由於公鑰 (與私鑰相比) 不需要保護, 因此大多數標識提供方使其易於使用方獲取和使用 (通常通過一個後設資料URL)。
JWT格式說明
JWT令牌格式 | 作用 | |
Header | 頭 | { "alg": "HS256", "typ": "JWT"} |
Payload | 有效載荷 | 使用base64進行序列化,任何人都可以讀到,所以不要包含敏感資訊 |
Signature | 簽名 |
防止抵賴-防止篡改,一旦頭和有效載荷有內容被篡改,則生成簽名部分必將與原內容不同 =HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) |
JWT如何解決信任問題的呢?
將簽名部分使用祕鑰進行解密,如果可以正常解開,說明令牌來自信任方頒發,將解密後的內容與JWT的頭部和有效載荷的base64編碼內容對比是否一致,如果一致,說明令牌未被篡改。也就解決了token信任的第二個問題。
演演算法實現:
下面是使用上述的兩種加密演演算法生成的jwt
HS256演演算法實現
1 public string GetToken(UserInfoDTO userInfo) 2 { 3 string secretKey = _configuration["SercetKey"]; 4 var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)), SecurityAlgorithms.HmacSha256); 5 6 var claims = new Claim[] { 7 new Claim("id",userInfo.Id.ToString()), 8 new Claim("age",userInfo.Age.ToString()), 9 new Claim("name",userInfo.Name), 10 new Claim("mobile",userInfo.Mobile), 11 new Claim("email",userInfo.EMail), 12 new Claim("role",userInfo.Role), 13 }; 14 15 var token = new JwtSecurityToken( 16 issuer: _configuration["Issuer"], //發行人 17 audience: _configuration["Audience"], //受眾人 18 claims: claims, 19 expires: DateTime.UtcNow.AddMinutes(2),//60分鐘有效期 20 notBefore: DateTime.UtcNow.AddMinutes(1),//1分鐘後有效 21 signingCredentials: signingCredentials); 22 string returnToken = new JwtSecurityTokenHandler().WriteToken(token); 23 return returnToken; 24 }
RSA256演演算法實現
1 public string GetToken(UserInfoDTO userInfo) 2 { 3 string filepath = Directory.GetCurrentDirectory(); 4 RSAParameters rSAParameter = default(RSAParameters); 5 //沒有生成過私鑰檔案,就建立,否則讀取私鑰 6 if (!File.Exists(Path.Combine(filepath, "key.private.json"))) 7 { 8 rSAParameter = GenerateAndSaveKey(filepath); 9 } 10 else 11 { 12 rSAParameter = JsonConvert.DeserializeObject<RSAParameters>(File.ReadAllText(Path.Combine(filepath, "key.private.json"))); 13 } 14 15 var signingCredentials = new SigningCredentials(new RsaSecurityKey(rSAParameter), SecurityAlgorithms.RsaSha256); 16 var claims = new Claim[] { 17 new Claim("id",userInfo.Id.ToString()), 18 new Claim("age",userInfo.Age.ToString()), 19 new Claim("name",userInfo.Name), 20 new Claim("mobile",userInfo.Mobile), 21 new Claim("email",userInfo.EMail), 22 new Claim("role",userInfo.Role), 23 }; 24 25 var token = new JwtSecurityToken( 26 issuer: _configuration["Issuer"], //發行人 27 audience: _configuration["Audience"], //受眾人 28 claims: claims, 29 expires: DateTime.UtcNow.AddMinutes(60),//60分鐘有效期 30 notBefore: DateTime.UtcNow.AddMinutes(1),//1分鐘後有效 31 signingCredentials: signingCredentials); 32 string returnToken = new JwtSecurityTokenHandler().WriteToken(token); 33 return returnToken; 34 }
在postman中請求:
將返回的jwt拿到jwt官網解析即可看到已經包含了我們需要傳遞的內容。因為jwt中傳遞的內容可以被看到,所以一定不要傳遞類似密碼這類敏感資訊
建立一個webapi專案,作為受保護的資源,開啟許可權認證,並使用jwt作為鑑權方式
1 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 2 .AddJwtBearer(configureOptions => 3 { 4 configureOptions.TokenValidationParameters = new TokenValidationParameters 5 { 6 ValidAudience = Configuration["Audience"], 7 ValidateAudience = true, 8 ValidIssuer = Configuration["Issuer"], 9 ValidateIssuer = true, 10 ValidateLifetime = false, 11 LifetimeValidator = (notBefore, expires, securityToken, validationParameters) => { 12 DateTime now = DateTime.UtcNow; 13 if (now.CompareTo(notBefore) < 0 || now.CompareTo(expires) > 0) 14 return false; 15 return true; 16 }, 17 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["SercetKey"])), //對稱加密方式,獲取金鑰 18 //IssuerSigningKey = new RsaSecurityKey(GetPulicKey()), //非對稱加密方式,獲取第三方提供的公鑰 19 ValidateIssuerSigningKey = true, 20 21 }; 22 });
不傳遞token請求受保護的api,則會返回401
加上獲取的token,則可以正常請求
專案原始碼連結: https://pan.baidu.com/s/1u9Lu7rLq7swSXOdV_sNh-g?pwd=mxfg
本文來自部落格園,作者:EdisonXie,轉載請註明原文連結:https://www.cnblogs.com/XFlyMan/p/16482674.html