這篇文章帶給大家的內容是關於如何在SpringBoot中整合JWT鑑權(附程式碼),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。
1、關於JWT
1.1 什麼是JWT
老生常談的開頭,我們要用這樣一個工具,首先得知道以下幾個問題。
那什麼是JWT呢?以下是我對jwt官網上對JWT介紹的翻譯。
JSON Web Token (JWT)是一種定義了一種緊湊且獨立的,用於在各方之間使用JSON物件安全的傳輸資訊的一個開放標準(RFC 7519)。
現在我們知道,JWT其實是一種開放標準,用於在多點之間安全地傳輸以JSON表示的資料。在傳輸的過程中,JWT以字串的形式出現在我們的視野中。該字串中的資訊可以透過數位簽章進行驗證和信任。 (推薦:Java教學)
JWT在實際的開發中,有哪些應用場景呢?
這應該算是JWT最常見的使用場景。在前端介面中,一旦使用者登入成功,會接收到後端回傳的JWT。後續的請求都會包含後端傳回的JWT,作為對後端路由、服務以及資源的存取的憑證。
利用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小時後再次請求介面就會直接被伺服器拒絕存取。
4.總結
總的來說,JWT中是不建議放特別敏感資訊的。如果沒有用非對稱加密演算法的話,把token複製之後直接可以去jwt官網線上解析。如果請求被攔截到了,裡面的所有資訊等於是透明的。
但是JWT可以用來當作一段時間運作存取伺服器資源的憑證。例如JWT的payload中帶有userId這個字段,那麼就可以對該token標識的用戶的合法性進行驗證。例如,該使用者目前狀態是否被鎖定?該userId所識別的使用者是否存在於我們的系統?等等。
並且透過實作token的公用,可以實現使用者的多端同時登入。像之前的登入之後才創建token,就限定了使用者只能同時在一台裝置上登入。
以上是如何在SpringBoot中整合JWT鑑權(附程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!