閱讀本文大概須要 4.2 分鐘。程序員
做者:王廷駿原文:https://juejin.im/post/5ce272c1e51d45109b01b0f8複製代碼
JSON Web Token(JWT)是一個很是輕巧的規範。web
這個規範容許咱們使用JWT在用戶和服務器之間傳遞安全可靠的信息。 簡稱JWT,在HTTP通訊過程當中,進行身份認證。 算法
咱們知道HTTP通訊是無狀態的,所以客戶端的請求到了服務端處理完以後是沒法返回給原來的客戶端。數據庫
所以須要對訪問的客戶端進行識別,經常使用的作法是經過session機制:apache
客戶端在服務端登錄成功以後,服務端會生成一個sessionID,返回給客戶端,客戶端將sessionID保存到cookie中,再次發起請求的時候,攜帶cookie中的sessionID到服務端,服務端會緩存該session(會話),當客戶端請求到來的時候,服務端就知道是哪一個用戶的請求,並將處理的結果返回給客戶端,完成通訊。json
經過上面的分析,能夠知道session存在如下問題: api
一、session保存在服務端,當客戶訪問量增長時,服務端就須要存儲大量的session會話,對服務器有很大的考驗; 跨域
二、當服務端爲集羣時,用戶登錄其中一臺服務器,會將session保存到該服務器的內存中,可是當用戶的訪問到其餘服務器時,會沒法訪問,一般採用緩存一致性技術來保證能夠共享,或者採用第三方緩存來保存session,不方便。瀏覽器
客戶端經過用戶名和密碼登陸服務器;緩存
服務端對客戶端身份進行驗證;
服務端對該用戶生成Token,返回給客戶端;
客戶端發起請求,須要攜帶該Token;
服務端收到請求後,首先驗證Token,以後返回數據。
客戶端將Token保存到本地瀏覽器,通常保存到cookie中。
服務端不須要保存Token,只須要對Token中攜帶的信息進行驗證便可;
不管客戶端訪問後臺的那臺服務器,只要能夠經過用戶信息的驗證便可。
JWT 的原理是,服務器認證之後,生成一個 JSON 對象,發回給用戶,就像下面這樣。
{ "姓名": "張三", "角色": "管理員", "到期時間": "2018年10月31日0點0分"}複製代碼
之後,用戶與服務端通訊的時候,都要發回這個 JSON 對象。服務器徹底只靠這個對象認定用戶身份。爲了防止用戶篡改數據,服務器在生成這個對象的時候,會加上簽名(詳見後文)。
服務器就不保存任何 session 數據了,也就是說,服務器變成無狀態了,從而比較容易實現擴展。
實際的 JWT 大概就像下面這樣。
它是一個很長的字符串,中間用點(.
)分隔成三個部分。注意,JWT 內部是沒有換行的,這裏只是爲了便於展現,將它寫成了幾行。
JWT 的三個部分依次以下。
Header(頭部)
Payload(負載)
Signature(簽名)
寫成一行,就是下面的樣子。
Header 部分是一個 JSON 對象,描述 JWT 的元數據,一般是下面的樣子。
{ "alg": "HS256", "typ": "JWT"}複製代碼
上面代碼中,alg屬性表示簽名的算法(algorithm),默認是 HMAC SHA256(寫成 HS256);typ屬性表示這個令牌(token)的類型(type),JWT 令牌固定寫爲JWT。
最後,將上面的 JSON 對象使用 Base64URL 算法(詳見後文)轉成字符串。
Payload 部分也是一個 JSON 對象,用來存放實際須要傳遞的數據。JWT 規定了7個官方字段,供選用。
iss (issuer):簽發人
exp (expiration time):過時時間
sub (subject):主題
aud (audience):受衆
nbf (Not Before):生效時間
iat (Issued At):簽發時間
jti (JWT ID):編號
除了官方字段,你還能夠在這個部分定義私有字段,下面就是一個例子。
{ "sub": "1234567890", "name": "John Doe", "admin": true}複製代碼
注意,JWT 默認是不加密的,任何人均可以讀到,因此不要把祕密信息放在這個部分。
這個 JSON 對象也要使用 Base64URL 算法轉成字符串。
Signature 部分是對前兩部分的簽名,防止數據篡改。
首先,須要指定一個密鑰(secret)。這個密鑰只有服務器才知道,不能泄露給用戶。而後,使用 Header 裏面指定的簽名算法(默認是 HMAC SHA256),按照下面的公式產生簽名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)複製代碼
算出簽名之後,把 Header、Payload、Signature 三個部分拼成一個字符串,每一個部分之間用"點"(.
)分隔,就能夠返回給用戶。
前面提到,Header 和 Payload 串型化的算法是 Base64URL。這個算法跟 Base64 算法基本相似,但有一些小的不一樣。
JWT 做爲一個令牌(token),有些場合可能會放到 URL(好比 api.example.com/?token=xxx)。
Base64 有三個字符+、/
和=
,在 URL 裏面有特殊含義,因此要被替換掉:=
被省略、+
替換成-
,/
替換成_
。這就是 Base64URL 算法。
客戶端收到服務器返回的 JWT,能夠儲存在 Cookie 裏面,也能夠儲存在 localStorage。
此後,客戶端每次與服務器通訊,都要帶上這個 JWT。你能夠把它放在 Cookie 裏面自動發送,可是這樣不能跨域,因此更好的作法是放在 HTTP 請求的頭信息Authorization
字段裏面。
Authorization: Bearer <token>複製代碼
另外一種作法是,跨域的時候,JWT 就放在 POST 請求的數據體裏面。
JWT 默認是不加密,但也是能夠加密的。生成原始 Token 之後,能夠用密鑰再加密一次。
JWT 不加密的狀況下,不能將祕密數據寫入 JWT。
JWT 不只能夠用於認證,也能夠用於交換信息。有效使用 JWT,能夠下降服務器查詢數據庫的次數。
JWT 的最大缺點是,因爲服務器不保存 session 狀態,所以沒法在使用過程當中廢止某個 token,或者更改 token 的權限。也就是說,一旦 JWT 簽發了,在到期以前就會始終有效,除非服務器部署額外的邏輯。
JWT 自己包含了認證信息,一旦泄露,任何人均可以得到該令牌的全部權限。爲了減小盜用,JWT 的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用戶進行認證。
爲了減小盜用,JWT 不該該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸。
建立一個maven項目,加入pom依賴:
<!--JWT--><dependencies><!--JWT--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>RELEASE</version> <scope>compile</scope> </dependency></dependencies><build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins></build>複製代碼
建立類JWTDemo:
public class JWTDemo { //加密的 private static final String SECRET_KEY = "123456789"; @Test public void jwtTest() throws InterruptedException { // 設置3秒後過時 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); long time = System.currentTimeMillis() + 30*60*1000; String jwt = this.buildJwt(new Date(time)); System.out.println("jwt = " + jwt); // 驗證token是否可用 boolean isOk = this.isJwtValid(jwt); System.out.println(isOk); } public String buildJwt(Date exp) { String jwt = Jwts.builder() .signWith(SignatureAlgorithm.HS256, SECRET_KEY) //SECRET_KEY是加密算法對應的密鑰,這裏使用額是HS256加密算法 .setExpiration(exp) //expTime是過時時間 .claim("name","wangtingjun") .claim("age","18") .claim("key", "vaule") //該方法是在JWT中加入值爲vaule的key字段 .compact(); return jwt; } public boolean isJwtValid(String jwt) { try { //解析JWT字符串中的數據,並進行最基礎的驗證 Claims claims = Jwts.parser() .setSigningKey(SECRET_KEY) //SECRET_KEY是加密算法對應的密鑰,jjwt能夠自動判斷機密算法 .parseClaimsJws(jwt) //jwt是JWT字符串 .getBody(); System.out.println(claims); String vaule = claims.get("key", String.class); //獲取自定義字段key //判斷自定義字段是否正確 if ("vaule".equals(vaule)) { return true; } else { return false; } } //在解析JWT字符串時,若是密鑰不正確,將會解析失敗,拋出SignatureException異常,說明該JWT字符串是僞造的 //在解析JWT字符串時,若是‘過時時間字段’已經早於當前時間,將會拋出ExpiredJwtException異常,說明本次請求已經失效 catch (SignatureException | ExpiredJwtException e) { return false; } }}複製代碼
最後能夠在控制檯看到打印出來的信息
jwt = eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDA5NzgxMzAsIm5hbWUiOiJ3YW5ndGluZ2p1biIsImFnZSI6IjE4Iiwia2V5IjoidmF1bGUifQ.XEDlK0UNTV3aKANQe9QCE2Y7JiP7D7ebrDVOs2JxRCQ{exp=1540978130, name=wangtingjun, age=18, key=vaule}true複製代碼
咱們還可使用jwt的解析工具進行解析看一下,jwt解析(https://jwt.io/)
·END·
程序員的成長之路
路雖遠,行則必至
本文原發於 同名微信公衆號「程序員的成長之路」,回覆「1024」你懂得,給個讚唄。
回覆 [ 520 ] 領取程序員最佳學習方式
回覆 [ 256 ] 查看 Java 程序員成長規劃
往期精彩回顧