Json web token (JWT), 是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519).該token被設計爲緊湊且安全的,特別適用於分佈式站點的單點登陸(SSO)場景。JWT的聲明通常被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也能夠增長一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。html
簡單來講就是 JWT(Json Web Token)是實現token技術的一種解決方案java
token驗證和session認證的區別git
傳統的session認證github
http協議自己是一種無狀態的協議,而這就意味着若是用戶向咱們的應用提供了用戶名和密碼來進行用戶認證,那麼下一次請求時,用戶還要再一次進行用戶認證才行,由於根據http協議,咱們並不能知道是哪一個用戶發出的請求,因此爲了讓咱們的應用能識別是哪一個用戶發出的請求,咱們只能在服務器存儲一份用戶登陸的信息,這份登陸信息會在響應時傳遞給瀏覽器,告訴其保存爲cookie,以便下次請求時發送給咱們的應用,這樣咱們的應用就能識別請求來自哪一個用戶了,這就是傳統的基於session認證。web
session缺點算法
基於session的認證使應用自己很可貴到擴展,隨着不一樣客戶端用戶的增長,獨立的服務器已沒法承載更多的用戶apache
Session方式存儲用戶id的最大弊病在於要佔用大量服務器內存,對於較大型應用而言可能還要保存許多的狀態。json
基於session認證暴露的問題api
基於token的鑑權機制相似於http協議也是無狀態的,它不須要在服務端去保留用戶的認證信息或者會話信息。這就意味着基於token認證機制的應用不須要去考慮用戶在哪一臺服務器登陸了,這就爲應用的擴展提供了便利。數組
JWT方式將用戶狀態分散到了客戶端中,能夠明顯減輕服務端的內存壓力。除了用戶id以外,還能夠存儲其餘的和用戶相關的信息,例如用戶角色,用戶性別等。
請求流程
這個token必需要在每次請求時傳遞給服務端,它應該保存在請求頭裏, 另外,服務端要支持
CORS(跨來源資源共享)
策略,通常咱們在服務端這麼作就能夠了Access-Control-Allow-Origin: *
。
一個JWT是下面的結構
加密後jwt信息以下所示,是由.分割的三部分組成,分別爲Header、Payload、Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9. TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT 的組成
Head -主要包含兩個部分,alg指加密類型,可選值爲HS256
、RSA
等等,typ=JWT
爲固定值,表示token的類型
Header: { "alg": "HS256", "typ": "JWT" }
Payload - Payload又被稱爲Claims包含您想要簽署的任何信息
Claims: { "sub": "1234567890", "name": "John Doe", "admin": true }JWT Payload的組成
Payload一般由三個部分組成,分別是 Registered Claims ; Public Claims ; Private Claims ;每一個聲明,都有各自的字段。
Registered Claims
iss 【issuer】發佈者的url地址
sub 【subject】該JWT所面向的用戶,用於處理特定應用,不是經常使用的字段
aud 【audience】接受者的url地址
exp 【expiration】 該jwt銷燬的時間;unix時間戳
nbf 【not before】 該jwt的使用時間不能早於該時間;unix時間戳
iat 【issued at】 該jwt的發佈時間;unix 時間戳
jti 【JWT ID】 該jwt的惟一ID編號
Signature 對 則爲對Header、Payload的簽名
Signature: base64UrlEncode(Header) + "." + base64UrlEncode(Claims)
頭部、聲明、簽名用 . 號連在一塊兒就獲得了咱們要的JWT 也就是夏明這種類型的字符串
eyJhbGciOiJIUzI1NiJ9.
eyJleHAiOjE1MTUyOTgxNDEsImtleSI6InZhdWxlIn0.
orewTmil7YmIXKILHwFnw3Bq1Ox4maXEzp0NC5LRaFQ
其實這些事一行的,我只是讓看的更直白點將其割開了。
JAVA中使用JWT
使用Maven引入和Gradle引入
Maven
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
Gradle
dependencies { compile 'io.jsonwebtoken:jjwt:0.9.0' }
JWT依賴於Jackson,須要在程序中加入Jackson的jar包且版本大於2.x
簽發JWT
public static String createJWT() { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; SecretKey secretKey = generalKey(); JwtBuilder builder = Jwts.builder() .setId(id) // JWT_ID .setAudience("") // 接受者 .setClaims(null) // 自定義屬性 .setSubject("") // 主題 .setIssuer("") // 簽發者 .setIssuedAt(new Date()) // 簽發時間 .setNotBefore(new Date()) // 失效時間 .setExpiration(long) // 過時時間 .signWith(signatureAlgorithm, secretKey); // 簽名算法以及密匙 return builder.compact(); }
驗證JWT
public static Claims parseJWT(String jwt) throws Exception { SecretKey secretKey = generalKey(); return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(jwt) .getBody(); }
完整示例
package com.tingfeng.demo; import com.google.gson.Gson; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.apache.tomcat.util.codec.binary.Base64; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Date; import java.util.HashMap; import java.util.Map; public class JwtUtil { /** * 由字符串生成加密key * * @return */ public SecretKey generalKey() { String stringKey = Constant.JWT_SECRET; // 本地的密碼解碼 byte[] encodedKey = Base64.decodeBase64(stringKey); // 根據給定的字節數組使用AES加密算法構造一個密鑰 SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } /** * 建立jwt * @param id * @param issuer * @param subject * @param ttlMillis * @return * @throws Exception */ public String createJWT(String id, String issuer, String subject, long ttlMillis) throws Exception { // 指定簽名的時候使用的簽名算法,也就是header那部分,jjwt已經將這部份內容封裝好了。 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; // 生成JWT的時間 long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); // 建立payload的私有聲明(根據特定的業務須要添加,若是要拿這個作驗證,通常是須要和jwt的接收方提早溝通好驗證方式的) Map<String, Object> claims = new HashMap<>(); claims.put("uid", "123456"); claims.put("user_name", "admin"); claims.put("nick_name", "X-rapido"); // 生成簽名的時候使用的祕鑰secret,切記這個祕鑰不能外露哦。它就是你服務端的私鑰,在任何場景都不該該流露出去。 // 一旦客戶端得知這個secret, 那就意味着客戶端是能夠自我簽發jwt了。 SecretKey key = generalKey(); // 下面就是在爲payload添加各類標準聲明和私有聲明瞭 JwtBuilder builder = Jwts.builder() // 這裏其實就是new一個JwtBuilder,設置jwt的body .setClaims(claims) // 若是有私有聲明,必定要先設置這個本身建立的私有的聲明,這個是給builder的claim賦值,一旦寫在標準的聲明賦值以後,就是覆蓋了那些標準的聲明的 .setId(id) // 設置jti(JWT ID):是JWT的惟一標識,根據業務須要,這個能夠設置爲一個不重複的值,主要用來做爲一次性token,從而回避重放攻擊。 .setIssuedAt(now) // iat: jwt的簽發時間 .setIssuer(issuer) // issuer:jwt簽發人 .setSubject(subject) // sub(Subject):表明這個JWT的主體,即它的全部人,這個是一個json格式的字符串,能夠存放什麼userid,roldid之類的,做爲何用戶的惟一標誌。 .signWith(signatureAlgorithm, key); // 設置簽名使用的簽名算法和簽名使用的祕鑰 // 設置過時時間 if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); builder.setExpiration(exp); } return builder.compact(); } /** * 解密jwt * * @param jwt * @return * @throws Exception */ public Claims parseJWT(String jwt) throws Exception { SecretKey key = generalKey(); //簽名祕鑰,和生成的簽名的祕鑰如出一轍 Claims claims = Jwts.parser() //獲得DefaultJwtParser .setSigningKey(key) //設置簽名的祕鑰 .parseClaimsJws(jwt).getBody(); //設置須要解析的jwt return claims; } public static void main(String[] args) { User user = new User("tingfeng", "bulingbuling", "1056856191"); String subject = new Gson().toJson(user); try { JwtUtil util = new JwtUtil(); String jwt = util.createJWT(Constant.JWT_ID, "Anson", subject, Constant.JWT_TTL); System.out.println("JWT:" + jwt); System.out.println("\n解密\n"); Claims c = util.parseJWT(jwt); System.out.println(c.getId()); System.out.println(c.getIssuedAt()); System.out.println(c.getSubject()); System.out.println(c.getIssuer()); System.out.println(c.get("uid", String.class)); } catch (Exception e) { e.printStackTrace(); } } }
Constant.java
package com.tingfeng.demo; import java.util.UUID; public class Constant { public static final String JWT_ID = UUID.randomUUID().toString(); /** * 加密密文 */ public static final String JWT_SECRET = "woyebuzhidaoxiediansha"; public static final int JWT_TTL = 60*60*1000; //millisecond }
輸出示例
JWT:eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiIxMjM0NTYiLCJzdWIiOiJ7XCJuaWNrbmFtZVwiOlwidGluZ2ZlbmdcIixcIndlY2hhdFwiOlwiYnVsaW5nYnVsaW5nXCIsXCJxcVwiOlwiMTA1Njg1NjE5MVwifSIsInVzZXJfbmFtZSI6ImFkbWluIiwibmlja19uYW1lIjoiWC1yYXBpZG8iLCJpc3MiOiJBbnNvbiIsImV4cCI6MTUyMjMxNDEyNCwiaWF0IjoxNTIyMzEwNTI0LCJqdGkiOiJhNGQ5MjA0Zi1kYjM3LTRhZGYtODE0NS1iZGNmMDAzMzFmZjYifQ.B5wdY3_W4MZLj9uBHSYalG6vmYwdpdTXg0otdwTmU4U 解密 a4d9204f-db37-4adf-8145-bdcf00331ff6 Thu Mar 29 16:02:04 CST 2018 {"nickname":"tingfeng","wechat":"bulingbuling","qq":"1056856191"} Anson 123456
優勢
安全相關
http://www.ibloger.net/article/3075.html
jjwt-gitHub:https://github.com/jwtk/jjwt
https://blog.csdn.net/u012240455/article/details/79019825
https://blog.csdn.net/u012017645/article/details/53585872
http://itindex.net/detail/58305-jwt-json-web
http://itindex.net/detail/58305-jwt-json-web
歡迎訪問個人github github博客地址