通常狀況下,web項目都是經過session進行認證,每次請求數據時,都會把jsessionid放在cookie中,以便與服務端保持會話。java
先後端分離項目中,經過token進行認證(登陸時,生成惟一的token憑證),每次請求數據時,都會把token放在header中,服務端解析token,並肯定用戶身份及用戶權限,數據經過json交互。git
可是token通常都是UUID生成的一個隨機碼,做爲一個key使用,從緩存中獲取具體的用戶信息。因此通常須要一個存儲介質來保存token和用戶信息。在一些場景中,如單點登陸時候有點麻煩。web
有沒一種更方便的方式呢?答案是有的,就是咱們今天要講的jwt。jwt也算是一個特殊的token,不過jwt中自帶了用戶的相關信息,因此不須要存儲介質,只須要驗證簽名保證安全的前提下就能夠直接獲取到用戶的相關信息。redis
在講jwt以前,咱們先回顧一下session、token的相關內容。算法
咱們都知道http是無狀態的,因此須要某種機制來識別用戶和保存用戶的狀態。而這個機制就是session。session是保存在服務端的,服務器經過session辨別用戶,而後作權限認證等。spring
那如何才知道用戶的session是哪一個?這時候cookie就出場了,瀏覽器第一次與服務器創建鏈接的時候,服務器會生成一個sessionid返回瀏覽器,瀏覽器把這個sessionid存儲到cookie當中,之後每次發起請求都會在請求頭cookie中帶上這個sessionid信息,因此服務器就是根據這個sessionid因此key獲取到具體session。數據庫
google瀏覽器中查看cookie內容的方法有兩個:json
(一)F12,查看具體請求連接的請求頭信息後端
(二)點擊瀏覽器輸入框的認證小鎖,能夠查看這個域名的相關cookie信息。瀏覽器
涉及到集羣環境得話,session須要弄成分佈式session,從而保證多個應用的會話狀態一致性。spring項目可使用spring session+redis來解決session共享問題。shiro項目能夠重寫redis版SessionDAO,把會話信息存到redis中實現共享。
接下來咱們再來聊聊token。
Token
token,就是咱們常說的用戶身份令牌。只有涉及到受限資源的訪問時候才須要身份令牌,因此,在訪問開放資源時候http中是沒有token的信息的,也便是說這時候會話是徹底無狀態的。token的是在用戶登陸之後生成的。用戶登陸以後咱們會生成一個token做爲key保存用戶的信息並返回給客戶端。保存方式set(token,用戶信息)存儲到redis等介質。
以後客戶端發起的請求只要在請求頭中附帶token的信息就能夠完成身份認證。
開源項目renren-fast採用了先後分離的機制,使用token來完成身份認證,而且集成了shiro框架,因此想實戰的能夠去clone下來玩玩~
https://gitee.com/renrenio/renren-fast
(只能幫你到這了~)
好了,說了這麼拓展知識,接下來咱們進入咱們的正題!jwt。
jwt是什麼
Json web token (JWT), 是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519)。該token被設計爲緊湊且安全的,特別適用於分佈式站點的單點登陸(SSO)場景。
JWT的聲明通常被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也能夠增長一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。
jwt的特色
簡潔(Compact): 能夠經過URL,POST參數或者在HTTP header發送,由於數據量小,傳輸速度也很快
自包含(Self-contained):負載中包含了全部用戶所須要的信息,避免了屢次查詢數據庫或緩存。
說了這麼多~
(想知道jwt長啥樣子不?)
真面目:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
傻眼了吧?看不懂吧,哈哈哈哈哈~~~~~~~~~
那麼咱們一一把這串數據分析一下。
首先從jwt的消息結構開始分析:
jwt消息結構
jwt有3個組成部分,分別是
頭部(header)
載荷(payload)
簽證(signature)
先回頭看看jwt真面目那個例子。仔細點,認真點,有沒在茫茫字母和數字中發現兩個.(點號)。
(是的,你沒有猜錯!)
就是這兩個點號把jwt分紅了3部分,分別對應着上面說的頭部,載荷,簽證。
先來說講頭部:
Jwt的頭部承載兩部分信息:
聲明類型,這裏是jwt
聲明加密的算法,一般直接使用HMACSHA256,就是HS256了
{"alg":"HS256","typ":"JWT"}
而後將頭部進行base64編碼構成了第一部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
(base64懂吧?)
Base64是一種用64個字符來表示任意二進制數據的方法
Base64是一種任意二進制到文本字符串的編碼方法,經常使用於在URL、Cookie、網頁中傳輸少許二進制數據。
Java中可使用java.util.Base64進行編碼解碼。
(我沒騙你吧?頭部就是這樣來的)
而後咱們看第二部分:載荷。
(載荷載荷,啥意思?)
我依稀記得物理老師說過:直接施加在結構上的各類力,習慣上稱爲載荷(荷載)。
好了不吹牛了,這裏是承載的意思。也就是說這裏是承載消息具體內容的地方。
內容又能夠分爲3中標準
標準中註冊的聲明
公共的聲明
私有的聲明
payload-標準中註冊的聲明 (建議但不強制使用) :
iss: jwt簽發者
sub: jwt所面向的用戶
aud: 接收jwt的一方
exp: jwt的過時時間,這個過時時間必需要大於簽發時間
nbf: 定義在什麼時間以前,該jwt都是不可用的.
iat: jwt的簽發時間
jti: jwt的惟一身份標識,主要用來做爲一次性token,從而回避重放攻擊。
payload-公共的聲明 :
公共的聲明能夠添加任何的信息。通常這裏咱們會存放一下用戶的基本信息(非敏感信息)。
payload-私有的聲明 :
私有聲明是提供者和消費者所共同定義的聲明。
須要注意的是,不要存放敏感信息,不要存放敏感信息,不要存放敏感信息!!!
(我是好人,大家必定要信我!)
由於:這裏也是base64編碼,任何人獲取到jwt以後均可以解碼!!(產品應該就不懂)
好了,請允許我Base64解碼一下載荷部份內容究竟是啥。
{"sub":"1234567890","name":"John Doe","iat":1516239022}
sub和iat是標準聲明,分別表明所面向的用戶和jwt簽發時間。
從上面我知道了:
這個是發給一個帳號是1234567890的用戶(也許是ID)
名字叫John Doe
簽發時間是1516239022(2018/1/18 9:30:22)
(牛逼~)
只剩下最後一部分了,待我一個閃現,外加一個大招秒殺它!
(emmmmm~~~,請求集合!!求救~)
簽證部分貌似和Base64沒啥關係呀,那究竟是啥的~
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
別急,先來講說簽證用來幹啥的,其實就是一個簽名信息,使用了自定義的一個密鑰而後加密後的結果,目的就是爲了保證簽名的信息沒有被別人改過!(也就是保證jwt安全可用)
頭部那裏咱們不是定義了一個加密算法麼,就是它
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), (個人密鑰) )
也就是說,簽證部分的信息有3個組成部分:
頭部-header (base64後的)
載荷-payload (base64後的)
密鑰-secret
而後HMACSHA256只有兩個參數,
base64後的頭部 + "." + base64後的載荷
密鑰-secret
好了,這部分我就不作代碼演示了。由於有現成的工具類能夠直接用,哈哈
下面咱們就介紹一下經常使用的生成jwt的工具類:
https://jwt.io/
以上就是官網給咱們介紹的幾種java可用的jar包。其中,io.jsonwebtoken是最經常使用的工具包。
使用步驟以下:
第一步,導入jar包:
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
第二步:稍微封裝一下,方便集成到項目中:
/** * jwt工具類 * @author chenshun * @email sunlightcs@gmail.com * @date 2017/9/21 22:21 */ @ConfigurationProperties(prefix = "renren.jwt") @Component public class JwtUtils { private Logger logger = LoggerFactory.getLogger(getClass()); private String secret; private long expire; private String header; /** * 生成jwt token */ public String generateToken(long userId) { Date nowDate = new Date(); //過時時間 Date expireDate = new Date(nowDate.getTime() + expire * 1000); return Jwts.builder() .setHeaderParam("typ", "JWT") .setSubject(userId+"") .setIssuedAt(nowDate) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public Claims getClaimByToken(String token) { try { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); }catch (Exception e){ logger.debug("validate is token error ", e); return null; } } /** * token是否過時 * @return true:過時 */ public boolean isTokenExpired(Date expiration) { return expiration.before(new Date()); } //getter、setter }
生成jwt:
//生成token String token = jwtUtils.generateToken(userId);
獲取jwt的有效信息:
//獲取載荷信息 Claims claims = jwtUtils.getClaimByToken(token); if(claims == null || jwtUtils.isTokenExpired(claims.getExpiration())){ throw new RRException(jwtUtils.getHeader() + "失效,請從新登陸", HttpStatus.UNAUTHORIZED.value()); } long userId = Long.parseLong(claims.getSubject())
嘿嘿、以上例子來自以前說個的renren-fast項目。因此呀,你仍是去看看吧~
(又學了一個技能,好有成就感!)
使用場景
閒聊一下jwt的使用場景
詳細能夠看看這篇文章:
https://baijiahao.baidu.com/s?id=1598976581711450442&wfr=spider&for=pc
總結
最後的最後,再來個小總結:
一、在Web應用中,別再把JWT當作session使用,絕大多數狀況下,傳統的cookie-session機制工做得更好
二、JWT適合一次性的命令認證,頒發一個有效期極短的JWT,即便暴露了危險也很小,因爲每次操做都會生成新的JWT,所以也不必保存JWT,真正實現無狀態。
至此,我想說的都說完了,我是呂一明,歡迎關注我公衆號:java思惟導圖。
(完)
(java思惟導圖)
關注公衆號,天天java一下,成就架構師