一、需求
-
SpringBoot 集成 JWT(token),
-
攔截器自動驗證驗證 token 是否過期
-
token 自動重繪(單個 token 重繪機制,保證活躍用戶不會掉線)
-
標準統一的 RESTFul 回傳體資料格式
-
例外統一攔截處理
單個 token 重繪機制(介紹):
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6ksAAtY2-1613013321715)(13.SpringBoot%20%E9%9B%86%E6%88%90token%E5%AE%9E%E8%B7%B5%E8%AF%A6%E8%A7%A3.assets/1611831269211-d13250d1-227d-48f3-bdb4-40d8bd643652.png)]
token 距離發布token 2 個小時內的token為新生token,2-3 個小時的token為老年token
每次請求,前端帶上 token,
(1)如果 token 為新 token ,服務器回傳原來的 token
(2)如果 token 為老年 token,服務器回傳 重繪后的新生token ,
(3)如果 token 為過期 token,服務器回傳token過期 狀態碼 401,,請求失敗, 前端重新登錄
二、代碼
1. 匯入依賴
jwt 依賴
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
整個 SpringBoot 依賴
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<!-- jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2. 組態檔
server:
port: 8081
spring:
application:
name: tokendemo
# token
token:
privateKey: 'fdasfgdsagaxgsregdfdjyghjfhebfdgwe45ygrfbsdfshfdsag'
yangToken: 1000000
oldToken: 3000000000
3. 代碼
代碼結構如下

AuthWebMvcConfigurer
@Configuration
public class AuthWebMvcConfigurer implements WebMvcConfigurer {
@Autowired
AuthHandlerInterceptor authHandlerInterceptor;
/**
* 給除了 /login 的介面都配置攔截器,攔截轉向到 authHandlerInterceptor
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authHandlerInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login");
}
}
TokenTestController
@RestController
public class TokenTestController {
@Autowired
TokenUtil tokenUtil;
/**
* 使用 /login 請求獲得 token, /login 不經過攔截器
*/
@RequestMapping("/login")
public String login(){
return tokenUtil.getToken("靚仔","admin");
}
/**
* 使用 /test-token 測驗 token,進過攔截器
*/
@RequestMapping("/test-token")
public Map testToken(HttpServletRequest request){
String token = request.getHeader("token");
return tokenUtil.parseToken(token);
}
}
TokenAuthExpiredException
public class TokenAuthExpiredException extends RuntimeException{
}
AuthHandlerInterceptor
@Slf4j
@Component
public class AuthHandlerInterceptor implements HandlerInterceptor {
@Autowired
TokenUtil tokenUtil;
@Value("${token.privateKey}")
private String privateKey;
@Value("${token.yangToken}")
private Long yangToken;
@Value("${token.oldToken}")
private Long oldToken;
/**
* 權限認證的攔截操作.
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
log.info("=======進入攔截器========");
// 如果不是映射到方法直接通過,可以訪問資源.
if (!(object instanceof HandlerMethod)) {
return true;
}
//為空就回傳錯誤
String token = httpServletRequest.getHeader("token");
if (null == token || "".equals(token.trim())) {
return false;
}
log.info("==============token:" + token);
Map<String, String> map = tokenUtil.parseToken(token);
String userId = map.get("userId");
String userRole = map.get("userRole");
long timeOfUse = System.currentTimeMillis() - Long.parseLong(map.get("timeStamp"));
//1.判斷 token 是否過期
//年輕 token
if (timeOfUse < yangToken) {
log.info("年輕 token");
}
//老年 token 就重繪 token
else if (timeOfUse >= yangToken && timeOfUse < oldToken) {
httpServletResponse.setHeader("token",tokenUtil.getToken(userId,userRole));
}
//過期 token 就回傳 token 無效.
else {
throw new TokenAuthExpiredException();
}
//2.角色匹配.
if ("user".equals(userRole)) {
log.info("========user賬戶============");
return true;
}
if ("admin".equals(userRole)) {
log.info("========admin賬戶============");
return true;
}
return false;
}
}
GlobalExceptionHandler
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 用戶 token 過期
* @return
*/
@ExceptionHandler(value = TokenAuthExpiredException.class)
@ResponseBody
public String tokenExpiredExceptionHandler(){
log.warn("用戶 token 過期");
return "用戶 token 過期";
}
}
TokenUtil
@Component
public class TokenUtil {
@Value("${token.privateKey}")
private String privateKey;
/**
* 加密token.
*/
public String getToken(String userId, String userRole) {
//這個是放到負載payLoad 里面,魔法值可以使用常量類進行封裝.
String token = JWT
.create()
.withClaim("userId" ,userId)
.withClaim("userRole", userRole)
.withClaim("timeStamp", System.currentTimeMillis())
.sign(Algorithm.HMAC256(privateKey));
return token;
}
/**
* 決議token.
* (優化可以用常量固定魔法值+使用DTO在 mvc 之前傳輸資料,而不是 map,這里因為篇幅原因就不做了)
* {
* "userId": "3412435312",
* "userRole": "ROLE_USER",
* "timeStamp": "134143214"
* }
*/
public Map<String, String> parseToken(String token) {
HashMap<String, String> map = new HashMap<>();
DecodedJWT decodedjwt = JWT.require(Algorithm.HMAC256(privateKey))
.build().verify(token);
Claim userId = decodedjwt.getClaim("userId");
Claim userRole = decodedjwt.getClaim("userRole");
Claim timeStamp = decodedjwt.getClaim("timeStamp");
map.put("userId", userId.asString());
map.put("userRole", userRole.asString());
map.put("timeStamp", timeStamp.asLong().toString());
return map;
}
}
完整專案代碼的地址
三、測驗
1. 獲得 token
訪問
localhost:8081/login
效果:

2. 測驗 token 是否可用
將 1 測驗得到的 token 放到 header 里面測驗 token是否可用
訪問
localhost:8081/test-token

3. 測驗 token 過期
測驗全域例外攔截類攔截到 TokenAuthExpiredException 例外,然后回傳提示,
將過期時間調小,修改 application.yaml 檔案,3 秒鐘就過期
server:
port: 8081
spring:
application:
name: tokendemo
# token
token:
privateKey: 'fdasfgdsagaxgsregdfdjyghjfhebfdgwe45ygrfbsdfshfdsag'
yangToken: 1000
oldToken: 3000
重啟應用測驗:

完整專案代碼的地址
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/259053.html
標籤:其他
上一篇:漫談BBR演算法的收斂點和公平性
