JWT認證登陸方式
在學習前后端分離的過程中往往遇到的第一個問題就是登陸驗證問題,以前的我們將Cookie、Session、Token,在我的學習過程中認識了JWT驗證方案,純屬自己理解,有錯還望大佬指出。
JWT 全稱是
JSON Web Token
,是目前非常流行的跨域認證解決方案.
前情回顧
早期的Cookie和Session的認證方式,之前沒有進行前后端分離的模式大部分使用的是Cookie-Session的認證方式,現在的未分離的項目基本也還是這種模式。
認證的大致流程就是:
- 用戶在前端輸入用戶名和密碼進行登陸系統。
- 服務端進行用戶名和密碼的驗證,創建一個Session對象,存入內存中,並將此Session的ID返回給前端
- 前端存儲返回的SessionID,每次請求都要帶着SessionID,服務端通過SessionID進行校驗。
JWT
JWT 就是一種Cookie-Session改造版的具體實現,讓你省去自己造輪子的時間,JWT 還有個好處,那就是你可以不用在服務端存儲認證信息(比如 token),完全由客戶端提供,服務端只要根據 JWT 自身提供的解密算法就可以驗證用戶合法性,而且這個過程是安全的。
如果你是剛接觸 JWT,最有疑問的一點可能就是: JWT 為什么可以完全依靠客戶端(比如瀏覽器端)就能實現認證功能,認證信息全都存在客戶端,怎么保證安全性?
JWT的構成
JWT 是由三段字符串和兩個.
組成,每個字符串和字符串之間沒有換行(類似於這樣:xxxxxx.yyyyyy.zzzzzz),每個字符串代表了不同的功能
- JWT頭部
- 有效載荷
- 哈希簽名
我們將這三個字符串的功能按順序列出來並講解:
1. JWT 頭
JWT 頭描述了 JWT 元數據,是一個 JSON 對象,它的格式如下:
{"alg":"HS256","typ":"JWT"}
這里的 alg 屬性表示簽名所使用的算法,JWT 簽名默認的算法為 HMAC SHA256 , alg 屬性值 HS256 就是 HMAC SHA256 算法。typ 屬性表示令牌類型,這里就是 JWT。
2. 有效載荷
有效載荷是 JWT 的主體,同樣也是個 JSON 對象。有效載荷包含三個部分:
-
標准注冊聲明標准注冊聲明不是強制使用是的,但是我建議使用。它一般包括以下內容:
-
- iss:jwt的簽發者/發行人;
-
- sub:主題;
-
- aud:接收方;
-
- exp:jwt過期時間;
-
- nbf:jwt生效時間;
-
- iat:簽發時間
-
- jti:jwt唯一身份標識,可以避免重放攻擊
-
公共聲明:可以在公共聲明添加任何信息,我們一般會在里面添加用戶信息和業務信息,但是不建議添加敏感信息,因為公共聲明部分可以在客戶端解密。
-
私有聲明:私有聲明是服務器和客戶端共同定義的聲明,同樣這里不建議添加敏感信息。
下面這個代碼段就是定義了一個有效載荷:
{"exp":"201909181230","role":"admin","isShow":false}
3. 哈希簽名
哈希簽名的算法主要是確保數據不會被篡改。它主要是對前面所講的兩個部分進行簽名,通過 JWT 頭定義的算法生成哈希。哈希簽名的過程如下:
-
指定密碼,密碼保存在服務器中,不能向客戶端公開;
-
使用 JWT 頭指定的算法進行簽名,進行簽名前需要對 JWT 頭和有效載荷進行 Base64URL 編碼,JWT 頭和有效載荷編碼后的結果之間需要用 . 來連接。
簡單示例如下:
HMACSHA256(base64UrlEncode(JWT 頭) + "." + base64UrlEncode(有效載荷),密碼)
最終結果如下:
base64UrlEncode(JWT 頭)+"."+base64UrlEncode(有效載荷)+"."+HMACSHA256(base64UrlEncode(JWT 頭) + "." + base64UrlEncode(有效載荷),密碼)
使用方式
- 用戶在前端輸入用戶名和密碼進行登陸系統。
- 服務器對賬號密碼進行驗證,計算出該用戶的JWT字符串,並且返回給客戶端
- 客戶端將后端返回的JWT字符串存儲到Cookie或者是LocalStorage
- 客戶端在進行請求的時候需要帶上JWT
- 服務端拿到這個JWT字符串后,使用 base64的頭部和 base64 的載荷部分,通過
HMACSHA256
算法計算簽名部分,比較計算結果和傳來的簽名部分是否一致,如果一致,說明此次請求沒有問題,如果不一致,說明請求過期或者是非法請求。
Java實現
-
依賴
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency>
-
實現
package com.example.jwtmdeo.utils; import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTCreator; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; /** * JWT處理類 */ public class JwtHelper { private static final String SECRET = "9a96349e2345385785e804e0f4254dee"; //加密字符串 隨便定義 private static String ISSUER = "sys_user"; //簽發人 /** * 生成JWT * @param claims 有效載荷 * @param expireDatePoint 過期時間點 * @return */ public static String createJWT(Map<String, String> claims, Date expireDatePoint){ try { //使用HMAC256進行加密 Algorithm algorithm = Algorithm.HMAC256(SECRET); //創建jwt JWTCreator.Builder builder = JWT.create(). withIssuer(ISSUER). //發行人 withExpiresAt(expireDatePoint); //過期時間點 //添加有效載荷 claims.forEach((key,value)-> { builder.withClaim(key, value); }); //簽名加密 return builder.sign(algorithm); } catch (IllegalArgumentException | UnsupportedEncodingException e) { throw new RuntimeException(e); } } /** * 解密jwt * @param token * @return * @throws RuntimeException */ public static Map<String,String> verifyToken(String token) throws RuntimeException{ Algorithm algorithm = null; try { //使用HMAC256進行加密 algorithm = Algorithm.HMAC256(SECRET); } catch (IllegalArgumentException | UnsupportedEncodingException e) { throw new RuntimeException(e); } //解密 JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build(); DecodedJWT jwt = verifier.verify(token); Map<String, Claim> map = jwt.getClaims(); Map<String, String> resultMap = new HashMap<>(); map.forEach((k,v) -> resultMap.put(k, v.asString())); return resultMap; } }
注意事項
在使用 JWT 時需要注意以下事項:
-
JWT 默認不加密,如果要寫入敏感信息必須加密,可以用生成的原始令牌再次對內容進行加密;
-
JWT 無法使服務器保存會話狀態,當令牌生成后在有效期內無法取消也不能更改;
-
JWT 包含認證信息,如果泄露了,任何人都可以獲得令牌所有的權限;因此 JWT 有效期不能太長,對於重要操作每次請求都必須進行身份驗證。
-
一旦頒發一個 JWT 令牌,服務端就沒辦法廢棄掉它,除非等到它自身過期。有很多應用默認只允許最新登錄的一個客戶端正常使用,不允許多端登錄,JWT 就沒辦法做到,因為頒發了新令牌,但是老的令牌在過期前仍然可用。這種情況下,就需要服務端增加相應的邏輯。