因爲多終端的出現,不少的站點經過 web api restful
的形式對外提供服務,採用了先後端分離模式進行開發,於是在身份驗證的方式上可能與傳統的基於 cookie
的 Session Id
的作法有所不一樣,除了面臨跨域提交 cookie
的問題外,更重要的是,有些終端可能根本不支持 cookie
。html
JWT(JSON Web Token)
是一種身份驗證及受權方案,簡單的說就是調用端調用 api
時,附帶上一個由 api
端頒發的 token
,以此來驗證調用者的受權信息。web
通常流程是下面這樣:算法
- 用戶向服務器發送用戶名和密碼。
- 服務器驗證經過後,在當前對話(session)裏面保存相關數據,好比用戶角色、登陸時間等等。
- 服務器向用戶返回一個 session_id,寫入用戶的 Cookie。
- 用戶隨後的每一次請求,都會經過 Cookie,將 session_id 傳回服務器。
- 服務器收到 session_id,找到前期保存的數據,由此得知用戶的身份。
這種模式的問題在於擴展性很差。單機沒有問題,若是是服務器集羣、跨域的服務導向架構或者用戶禁用了 cookie
,就不行了。json
2、解決方案後端
1. 單機和分佈式應用下登陸校驗,session 共享api
單機和多節點 tomcat
應用登陸檢驗跨域
①、單機tomcat
應用登陸,sesssion
保存在瀏覽器和應用服務器會話之間,用戶登陸成功後,服務端會保證一個session
,也會給客戶端一個sessionId
,客戶端會把sessionId
保存在cookie
中,用戶每次請求都會攜帶這個sessionId
。②、多節點
tomcat
應用登陸,開啓session
數據共享後,每臺服務器都可以讀取session
。缺點是每一個session
都是佔用內存和資源的,每一個服務器節點都須要同步用戶的數據,即一個數據須要存儲多份到每一個服務器,當用戶量到達百萬、千萬級別的時,佔用資源就嚴重,用戶體驗特別很差!!瀏覽器
分佈式應用中 session
共享tomcat
①、真實的應用不可能單節點部署,因此就有個多節點登陸session
共享的問題須要解決。tomcat
支持session
共享,可是有廣播風暴;用戶量大的時候,佔用資源就嚴重,不推薦②、
Reids
集羣,存儲登錄的token
,向外提供服務接口,Redis
可設置過時時間(服務端使用UUID
生成隨機64
位或者128
位token
,放入Redis
中,而後返回給客戶端並存儲)。服務器③、用戶第一次登陸成功時,須要先自行生成
token
,而後將token
返回到瀏覽器並存儲在cookie
中, 並在Redis
服務器上以token
爲key
,用戶信息做爲value
保存。後續用戶再操做,能夠經過HttpServletRequest
對象直接讀取cookie
中的token
,並在Redis
中取得相對應的用戶數據進行比較(用戶每次訪問都攜帶此token
,服務端去Redis
中校驗是否有此用戶便可)。④、 缺點:必須部署
Redis
,每次必須訪問Redis
,IO
開銷特別大。
2. 最終解決方案:使用 JWT 實現 Token 認證
JWT 的原理
服務器認證之後,生成一個 JSON 對象發回給用戶,之後用戶與服務端通訊的時候,都要發回這個 JSON 對象。服務器徹底只靠這個對象認定用戶身份。爲了防止用戶篡改數據,服務器在生成這個對象的時候,會加上簽名。也就是說服務器就不保存任何 session 數據了,即服務器變成無狀態了,從而比較容易實現擴展。簡單來講,就是經過必定規範來生成 token,而後能夠經過解密算法逆向解密 token,這樣就能夠獲取用戶信息</pre>
優勢和缺點
優勢:生產的 token 能夠包含基本信息,好比 id、用戶暱稱、頭像等信息,避免再次查庫;存儲在客戶端,不佔用服務端的內存資源缺點:token 是通過 base64 編碼,因此能夠解碼,所以 token 加密前的對象不該該包含敏感信息(如用戶權限,密碼等)
JWT 格式組成:頭部+負載+簽名 ( header + payload + signature )
頭部:主要是描述簽名算法。負載:主要描述是加密對象的信息,如用戶的 id 等,也能夠加些規範裏面的東西,如 iss 簽發者,exp 過時時間,sub 面向的用戶。
簽名:主要是把前面兩部分進行加密,防止別人拿到 token 進行base 解密後篡改 token。
3. 案例圖設計
3、代碼演示案例
<!-- 依賴能夠減小實體類 getter/setter等方法書寫 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- JWT相關 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency> ========================================================= @Getter @Setter @ToString @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private Integer id; private String openid; private String name; private String headImg; private String phone; private String sign; private Integer sex; private String city; private Date createTime; }
public class JwtUtil { // 主題 public static final String SUBJECT = "RookieLi"; // 祕鑰 public static final String SECRETKEY = "Rookie666"; // 過時時間 public static final long EXPIRE = 1000 * 60 * 60 * 24 * 7; //過時時間,毫秒,一週 // 生成 JWT public static String geneJsonWebToken(User user) { if (user == null || user.getId() == null || user.getName() == null || user.getHeadImg() == null) { return null; } String token = Jwts.builder() .setSubject(SUBJECT) .claim("id", user.getId()) .claim("name", user.getName()) .claim("img", user.getHeadImg()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRETKEY).compact(); return token; } // 校驗 JWT public static Claims checkJWT(String token) { try { final Claims claims = Jwts.parser().setSigningKey(SECRETKEY). parseClaimsJws(token).getBody(); return claims; } catch (Exception e) { e.printStackTrace(); } return null; } }
public class JwtUtilTest { @Test public void testGeneJwt(){ User user = new User(); user.setId(999); user.setHeadImg("I'm busy"); user.setName("Rookie"); String token = JwtUtil.geneJsonWebToken(user); System.out.println(token); } @Test public void testCheck(){ // 下面此 token 字符串是上面的結果生成的,每次不同,不是寫死的 String token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJSb29raWVMaSIsImlkIjo5OTksIm5hbWUiOiJSb29raWUiLCJpbWciOiJJJ20gYnVzeSIsImlhdCI6MTU2NzMxNjk4NywiZXhwIjoxNTY3OTIxNzg3fQ.FJh41VwVh2gh5-_cOG0SOgoO3dR_ZcK9VWNNskWqKl0"; Claims claims = JwtUtil.checkJWT(token); if(claims != null){ String name = (String)claims.get("name"); String img = (String)claims.get("img"); int id =(Integer) claims.get("id"); System.out.println(name); System.out.println(img); System.out.println(id); }else{ System.out.println("非法token"); } } }
參考博客:
http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
https://www.cnblogs.com/jpfss/p/10929458.html