JWT 實現自動刷新登陸token


JWT  的全稱為Json Web Token,簡而言之json類型的web服務身份認證令牌(個人理解喲,勿噴)。

  適合做前后端分離身份認證,集群服務身份認證,系統群單點登錄等。

1、學習這個技術之前先來了解一下這個技術有哪些優點吧。(參考:https://blog.csdn.net/qq_34037264/article/details/108273333)

 1 1、支持跨域訪問: Cookie是不允許垮域訪問的,這一點對Token機制是不存在的,前提是傳輸的用戶認證信息通過HTTP頭傳輸.
 2 
 3 2、無狀態(也稱:服務端可擴展行):Token機制在服務端不需要存儲session信息,因為Token 自身包含了所有登錄用戶的信息,只需要在客戶端的cookie或本地介質存儲狀態信息.
 4 
 5 4、更適用CDN: 可以通過內容分發網絡請求你服務端的所有資料(如:javascript,HTML,圖片等),而你的服務端只要提供API即可.(居於前面兩點得出這個更適用於CDN內容分發網絡)
 6 
 7 5、去耦: 不需要綁定到一個特定的身份驗證方案。Token可以在任何地方生成,只要在你的API被調用的時候,你可以進行Token生成調用即可.(這個似乎也在繼續說前面第一點和第二點的好處。。。)
 8 
 9 6、更適用於移動應用: 當你的客戶端是一個原生平台(iOS, Android,Windows 8等)時,Cookie是不被支持的(你需要通過Cookie容器進行處理),這時采用Token認證機制就會簡單得多。
10 
11 7、CSRF:因為不再依賴於Cookie,所以你就不需要考慮對CSRF(跨站請求偽造)的防范。(如果token是用cookie保存,CSRF還是需要考慮,一般建議使用1、在HTTP請求中以參數的形式加入一個服務器端產生的token。或者2.放入http請求頭中也就是一次性給所有該類請求加上csrftoken這個HTTP頭屬性,並把token值放入其中)
12 ps:后面會推出一些常見的網絡安全的處理
13 
14 8、性能: 一次網絡往返時間(通過數據庫查詢session信息)總比做一次HMACSHA256計算 的Token驗證和解析要費時得多.
15 
16 9、不需要為登錄頁面做特殊處理: 如果你使用Protractor 做功能測試的時候,不再需要為登錄頁面做特殊處理.
17 
18 10、基於標准化:你的API可以采用標准化的 JSON Web Token (JWT). 這個標准已經存在多個后端庫(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).

 

2、基礎性知識和demo搭建參考問題1所提的博客就可以了。話不多說進入主題,學習如下內容需要對JWT有簡單的了解。

3、在使用JWT的時候,你會發現token的生命周期不能夠進行續命。此處有不少小伙伴可能不理解什么意思。

我舉個栗子,用戶A進入一個系統,獲取到一個token,這個token默認有效期是30分鍾,但是由於是業務系統,幾乎工作時間都在使用這個系統。那么等到30分鍾token到期的時候,用戶還是需要重新登錄。

期望:用戶不斷操作給token續命,如果token還未失效,只要用戶處於活躍狀態就一直給token順延到期時間,即當前時間向后推遲30分鍾,如果token已經失效,則重新登錄。

現狀:JWT由於無法更新失效時間(主要原因token的產生和失效時間有關系),當用戶登錄系統沒到30分鍾就需要重新登錄一次,無論用戶是否活躍。

4、針對這個問題,網友也各抒己見。

  1) 用redis存儲jwt(這個可以實現,但是有違jwt的初衷)

       2) 利用refreshToken來刷新JWT (這個方案個人覺得意義不大)

5、下面我們直接開始擼代碼。

1、添加JWT依賴。

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.3</version>
        </dependency>

2、創建JWT測試代碼類。

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import net.sf.json.JSONObject;
import org.apache.commons.codec.binary.Base64;

import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


/**
 * @author LH
 * @version V1.0
 * @Package com.purang.test
 * @date 2021/3/25 16:59
 * @description: 修改描述
 */
public class JwtUtils {
    //SING簽名的設置,不能對外暴露(可以提取到配置文件)
    public static final String SING = "token!J1JK3JH^&g%f*f@f*(f!)fs*#s*$H3J4DK43";
    //產生token的有效時長,單位分鍾(可以提取到配置文件)
    public static final int TOKEN_EFFECTIVE_TIME_MINUTE = 1;
    //回話連接的有效時長,單位分鍾(可以提取到配置文件)
    public static final int SCOKET_EFFECTIVE_TIME_MINUTE = 140;

    public static void main(String[] args) throws UnsupportedEncodingException {
            /*
            //1、模擬第一次登錄獲取token
            Map<String,String> tokenMap = new HashMap<>();
tokenMap.put("userName","張三zhangsan1");
tokenMap.put("passWord","張三zhangsan1pass");
String token = getToken(tokenMap);*/
//================================================ //2、模擬用戶操作系統校驗token,有效則直接獲取所需信息,直接訪問,其他異常做相應處理 String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzV29yZCI6IuW8oOS4iXpoYW5nc2FuMXBhc3MiLCJ1c2VyTmFtZSI6IuW8oOS4iXpoYW5nc2FuMSIsImV4cCI6MTYxNjcyMjI0MH0.T6lm9kQjzUpJlio-MT2arAhAciFMzpdHyVQtpdkFjjA"; JSONObject tokenPayloadJson = null; try { //根據前台請求頭中獲取token的Base64編碼值 String[] tokenSplit = token.split("\\."); //解析載荷信息,用於用戶信息的續命 tokenPayloadJson = decodeBase64(tokenSplit[1]); System.out.println("舊token=="+token); System.out.println("舊tokenStr=="+tokenPayloadJson.toString()); //校驗token合法性 verify(token); System.out.println("有效簽名,驗證通過"); } catch (SignatureVerificationException e) { e.printStackTrace(); System.out.println("無效簽名"); } catch (TokenExpiredException e) { e.printStackTrace(); //只有在token過期的時候才可以進行token重生策略的調用 System.out.println("token已過期!"); //因JWT只精確到秒,所以此處獲取的時間戳你會發現少了后面三位(也就是毫秒) Long token_time = tokenPayloadJson.getLong("exp"); Date date = new Date(); //token已經超期多少時間 System.out.println("token已經超期的時間="+(date.getTime()/1000 - token_time)/60+"分鍾"); //token創建時間點到現在的時間 int time = (int) ((date.getTime()/1000 - token_time)/60 - TOKEN_EFFECTIVE_TIME_MINUTE); System.out.println("token創建時間點到現在的時間="+time+"分鍾"); if(time < SCOKET_EFFECTIVE_TIME_MINUTE){ System.out.println(">>>>>>>>>>>>>>>>>>token可以重新產生"); Map<String,String> map = new HashMap<>(); tokenPayloadJson.forEach((k, v) -> { if(!"exp".equals((String)k)) map.put((String)k, (String)v); }); map.put("newToken","new"+Math.random()); String newToken = getToken(map); System.out.println("新token=="+newToken); System.out.println("新tokenStr=="+decodeBase64(getTokenInfo(newToken).getPayload())); } } catch (AlgorithmMismatchException e) { e.printStackTrace(); System.out.println("token算法不一致!"); } catch (Exception e) { e.printStackTrace(); System.out.println("token無效!!!"); } } /** * @Description 生成token header.payload.sing * @Author LH * @Date 2021-03-26 10:48:52 * @Param Map 集合存入payload信息 * @Return token * @Exception */ public static String getToken(Map<String, String> map) { //設置令牌的過期時間 Calendar instance = Calendar.getInstance(); //設置失效時間 instance.add(Calendar.MINUTE, TOKEN_EFFECTIVE_TIME_MINUTE); //創建JWT builder JWTCreator.Builder builder = JWT.create(); //payload map.forEach((k, v) -> { builder.withClaim(k, v); }); System.out.println("時間的times"+instance.getTime().getTime()); String token = builder.withExpiresAt(instance.getTime()) //指定令牌過期時間 .sign(Algorithm.HMAC256(SING)); return token; } /** * @Description 驗證令牌是否合法 * @Author LH * @Date 2021-03-26 10:57:04 */ public static void verify(String token) { JWT.require(Algorithm.HMAC256(SING)).build().verify(token); } /** * @Author LH * @Date 2021-03-26 11:13:52 * @Return DecodedJWT JWT信息獲取 * @Description 獲取JWT的信息 */ public static DecodedJWT getTokenInfo(String token) { DecodedJWT verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(token); return verify; } /** * @Author LH * @Date 2021-03-26 11:13:52 * @Return 解析TOKEN后的JSONObject內容 * @Description 根據Base64獲取解析內容 */ public static JSONObject decodeBase64(String tokenPayload) throws UnsupportedEncodingException { byte[] bytes = Base64.decodeBase64(tokenPayload); String decode = new String(bytes, "UTF-8"); return JSONObject.fromObject(decode); } }

3、新建token,運行結果如下

 

打開如下代碼注釋:
            //1、模擬第一次登錄獲取token
            Map<String,String> tokenMap = new HashMap<>();
tokenMap.put("userName","張三zhangsan1");
tokenMap.put("passWord","張三zhangsan1pass");
String token = getToken(tokenMap);
========================================================================

時間的times1616731158511 舊token
==eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzV29yZCI6IuW8oOS4iXpoYW5nc2FuMXBhc3MiLCJ1c2VyTmFtZSI6IuW8oOS4iXpoYW5nc2FuMSIsImV4cCI6MTYxNjczMTE1OH0.tf7dT_kHFAto-AJ8BzCYI8YY8pmmgY8javepTNwGZHY 舊tokenStr=={"passWord":"張三zhangsan1pass","userName":"張三zhangsan1","exp":1616731158} 有效簽名,驗證通過

4、校驗token有效期,進行動態token續命。

舊token==eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzV29yZCI6IuW8oOS4iXpoYW5nc2FuMXBhc3MiLCJ1c2VyTmFtZSI6IuW8oOS4iXpoYW5nc2FuMSIsImV4cCI6MTYxNjczMTE1OH0.tf7dT_kHFAto-AJ8BzCYI8YY8pmmgY8javepTNwGZHY
舊tokenStr=={"passWord":"張三zhangsan1pass","userName":"張三zhangsan1","exp":1616731158}
com.auth0.jwt.exceptions.TokenExpiredException: The Token has expired on Fri Mar 26 11:59:18 CST 2021.
    at com.auth0.jwt.JWTVerifier.assertDateIsFuture(JWTVerifier.java:379)
    at com.auth0.jwt.JWTVerifier.assertValidDateClaim(JWTVerifier.java:370)
    at com.auth0.jwt.JWTVerifier.verifyClaims(JWTVerifier.java:295)
    at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:278)
    at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:261)
    at com.purang.track.testCase.JwtUtils.verify(JwtUtils.java:131)
    at com.purang.track.testCase.JwtUtils.main(JwtUtils.java:55)
token已過期!
token已經超期的時間=88分鍾
token創建時間點到現在的時間=87分鍾
>>>>>>>>>>>>>>>>>>token可以重新產生
時間的times1616736521241
新token==eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzV29yZCI6IuW8oOS4iXpoYW5nc2FuMXBhc3MiLCJuZXdUb2tlbiI6Im5ldzAuMjEzNTkzMjYwNTMxNDY3IiwidXNlck5hbWUiOiLlvKDkuIl6aGFuZ3NhbjEiLCJleHAiOjE2MTY3MzY1MjF9.r3HaEzSRwgY4UlBFNtVX39vUAC0_drIZhWL4j4tZOVc
新tokenStr=={"passWord":"張三zhangsan1pass","newToken":"new0.213593260531467","userName":"張三zhangsan1","exp":1616736521}

5、有了新的token就可以進行前台返回了,后面請求就會拿新token進行驗證了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM