這篇博客主要是簡單介紹了一下什麼是JWT,以及如何在Spring Boot項目中使用JWT(JSON Web Token)。前端
老生常談的開頭,咱們要用這樣一種工具,首先得知道如下幾個問題。java
那什麼是JWT呢?如下是我對jwt官網上對JWT介紹的翻譯。git
JSON Web Token (JWT)是一種定義了一種緊湊而且獨立的,用於在各方之間使用JSON對象安全的傳輸信息的一個開放標準(RFC 7519)。github
如今咱們知道,JWT實際上是一種開放標準,用於在多點之間安全地傳輸用JSON表示的數據。在傳輸的過程當中,JWT以字符串的形式出如今咱們的視野中。該字符串中的信息能夠經過數字簽名進行驗證和信任。redis
JWT在實際的開發中,有哪些應用場景呢?算法
這應該算是JWT最多見的使用場景。在前端界面中,一旦用戶登陸成功,會接收到後端返回的JWT。後續的請求都會包含後端返回的JWT,做爲對後端路由、服務以及資源的訪問的憑證。spring
利用JWT在多方之間相互傳遞信息具備必定的安全性,例如JWT能夠用HMAC、RSA非對稱加密算法以及ECDSA數字簽名算法對JWT進行簽名,能夠確保消息的發送者是真的發送者,並且使用header和payload進行的簽名計算,咱們還能夠驗證發送的消息是否被篡改了。後端
通俗來說JWT由header.payload.signature
三部分組成的字符串,網上有太多帖子介紹這一塊了,因此在這裏就簡單介紹一下就行了。緩存
header
由使用的簽名算法和令牌的類型的組成,例如令牌的類型就是JWT這種開放標準,而使用的簽名算法就是HS256
,也就是HmacSHA256
算法。其餘的加密算法還有HmacSHA512
、SHA512withECDSA
等等。安全
而後將這個包含兩個屬性的JSON對象轉化爲字符串而後使用Base64編碼,最終造成了JWT的header。
payload
說直白一些就相似你的requestBody中的數據。只不過是分了三種類型,預先申明好的、自定義的以及私有的。像iss
發件人,exp
過時時間都是預先註冊好的申明。
預先申明在載荷中的數據不是強制性的使用,可是官方建議使用。而後這串相似於requestBody的JSON通過Base64編碼造成了JWT的第二部分。
若是要生成signature
,就須要使用jwt自定義配置項中的secret,也就是Hmac算法加密所須要的密鑰。將以前通過Base64編碼的header和payload用.
相連,再使用自定義的密鑰,對該消息進行簽名,最終生成了簽名。
生成的簽名用於驗證消息在傳輸的過程當中沒有被更改。在使用非對稱加密算法進行簽名的時候,還能夠用於驗證JWT的發件人是否與payload中申明的發件人是同一我的。
代碼以下。
public String createJwt(String userId, String projectId) throws IllegalArgumentException, UnsupportedEncodingException { Algorithm al = Algorithm.HMAC256(secret); Instant instant = LocalDateTime.now().plusHours(outHours).atZone(ZoneId.systemDefault()).toInstant(); Date expire = Date.from(instant); String token = JWT.create() .withIssuer(issuer) .withSubject("userInfo") .withClaim("user_id", userId) .withClaim("project_id", projectId) .withExpiresAt(expire) .sign(al); return token; }
傳入的兩個Claim是項目裏自定義的payload,al
是選擇的算法,而secret
就是對信息簽名的密鑰,subject
則是該token的主題,withExpiresAt
標識了該token的過時時間。
在用戶登陸系統成功以後,將token做爲返回參數,返回給前端。
在token返回給前端以後,後端要作的就是驗證這個token是不是合法的,是否能夠訪問服務器的資源。主要能夠經過如下幾種方式去驗證。
使用JWTVerifier
解析token,這是驗證token是否合法的第一步,例如前端傳過來的token是一串沒有任何意義的字符串,在這一步就能夠拋出錯誤。示例代碼以下。
try { Algorithm algorithm = Algorithm.HMAC256(secret); JWTVerifier verifier = JWT.require(algorithm).build(); DecodedJWT jwt = verifier.verify(token); } catch (JWTVerificationException e) { e.printStackTrace(); }
JWTVerifier可使用用制定secret簽名的算法,指定的claim來驗證token的合法性。
判斷了token是有效的以後,再對token的時效性進行驗證。
try { Algorithm algorithm = Algorithm.HMAC256(secret); JWTVerifier verifier = JWT.require(algorithm).build(); DecodedJWT jwt = verifier.verify(token); if (jwt.getExpiresAt().before(new Date())) { System.out.println("token已過時"); return null; } } catch (JWTVerificationException e) { e.printStackTrace(); return null; }
若是該token過時了,則不容許訪問服務器資源。具體的流程以下。
上面建立token的有效時間是能夠配置的,假設是2個小時,而且用戶登陸進來連續工做了1小時59分鐘,在進行一個很重要的操做的時候,點擊肯定,這個時候token過時了。若是程序沒有保護策略,那麼用戶接近兩個小時的工做就成爲了無用功。
遇到這樣的問題,咱們以前的流程設計必然面對一次重構。可能你們會有疑問,不就是在用戶登陸以後,每次操做對去刷新一次token的過時時間嗎?
那麼問題來了,咱們知道token是由header.payload.signature
三段內容組成的,而過時時間則是屬於payload,若是改變了過時的時間,那麼最終生成的payload的hash則勢必與上一次生成的不一樣。
換句話說,這是一個全新的token。前端要怎麼接收這個全新的token呢?可想到的解決方案無非就是每次請求,根據response header中的返回不斷的刷新的token。可是這樣的方式侵入了前端開發的業務層。使其每個接口都須要去刷新token。
你們可能會說,無非就是加一個攔截器嘛,對業務侵入不大啊。即便這部分邏輯是寫在攔截器裏的,可是前端由於token鑑權的邏輯而多出了這部分代碼。而這部分代碼從職能分工上來講,實際上是後端的邏輯。
說的直白一些,刷新token,對token的時效性進行管理,應該是由後端來作。前端不須要也不該該去關心這一部分的邏輯。
綜上所述,刷新token的過時時間勢必要放到後端,而且不能經過判斷JWT中payload中的expire來判斷token是否有效。
因此,在用戶登陸成功以後並將token返回給前端的同時,須要以某一個惟一表示爲key,當前的token爲value,寫入Redis緩存中。而且在每次用戶請求成功後,刷新token的過時時間,流程以下所示。
通過這樣的重構以後,流程就變成了這樣。
在流程中多了一個刷新token的流程。只要用戶登陸了系統,每一次的操做都會刷新token的過時時間,就不會出現以前說的在進行某個操做時忽然失效所形成數據丟失的狀況。
在用戶登陸以後的兩個小時內,若是用戶沒有進行任何操做,那麼2小時後再次請求接口就會直接被服務器拒絕訪問。
總的來講,JWT中是不建議放特別敏感信息的。若是沒有用非對稱加密算法的話,把token複製以後直接能夠去jwt官網在線解析。若是請求被攔截到了,裏面的全部信息等因而透明的。
可是JWT能夠用來看成一段時間內運行訪問服務器資源的憑證。例如JWT的payload中帶有userId這個字段,那麼就能夠對該token標識的用戶的合法性進行驗證。例如,該用戶當前狀態是否被鎖定?該userId所標識的用戶是否存在於咱們的系統?等等。
而且經過實現token的公用,能夠實現用戶的多端同時登陸。像以前的登陸以後建立token,就限定了用戶只能同時在一臺設備上登陸。
歡迎你們瀏覽以前的文章:我的博客,若是有說的不對的地方,還請不吝賜教。