使用 JWT 生成 token
JWT 簡介
-
JWT:Json Web Token
-
官網://jwt.io
-
優點:可生成安全性較高的 token 且可以完成時效性的檢驗(登陸過期檢查)
-
JWT 結構:(由官網獲取)
JWT 生成 token
添加依賴:
<!-- java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.2</version>
</dependency>
<!-- jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
生成 token 示例程式碼:
//使用jwt生成token進行加密
JwtBuilder builder = Jwts.builder();
//此map可以存儲用戶角色許可權資訊
Map<String, Object> map = new HashMap<>();
map.put("k1", "v1");
//鏈式調用,設置相關加密資訊
String token = builder.setSubject(name) //設置主題,也就是設置token中攜帶的數據
.setIssuedAt(new Date()) //設置token生成時間
.setId(users.get(0).getUserId() + "") //設置token的id(此處用的用戶id)
.setClaims(map) //map中可存放用戶角色許可權資訊
.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)) //設置token過期時間為1天後
.signWith(SignatureAlgorithm.HS256, "luis333") //設置加密方式和加密密碼
.compact();
JWT 解析 token
//獲取token並校驗
if (token == null) {
return new ResultVo(ResStatus.NO, "請先登陸!", null);
} else {
//獲取jwt解析器
JwtParser parser = Jwts.parser();
parser.setSigningKey("luis333"); //之前加密時的加密密碼,一致才能解析成功
try {
//如果token正確(密碼正確,且在有效期內)則正常執行,否則拋出異常
Jws<Claims> claimsJws = parser.parseClaimsJws(token); //解析token
//獲取解析的token中相關數據
Claims body = claimsJws.getBody(); //獲取token中用戶數據
String subject = body.getSubject(); //獲取subject中數據
String v1 = body.get("k1", String.class); //獲取claims中存儲的map中數據
return new ResultVo(ResStatus.OK, "success", null);
} catch (Exception e) {
return new ResultVo(ResStatus.NO, "登陸過期,請重新登陸!", null);
}
}
攔截器校驗 token
請求非常多,一個一個地進行校驗不現實,所以需要統一校驗 token,此時可以使用攔截器。
在攔截器的預處理方法中進行 token 統一校驗,校驗通過則放行請求,不通過則做出相關響應提示。
在攔截器統一校驗 token 後,在控制器中做過校驗的請求就不需要再次進行校驗了,直接寫業務邏輯即可。
攔截器類:
/**
* 攔截器
*/
@Component
public class CheckTokenInterceptor implements HandlerInterceptor {
/**
* 預處理方法,攔截請求,決定是否放行。
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//獲取token並校驗
String token = request.getParameter("token");
System.out.println("preHandle ========> token = " + token);
if (token == null) {
//沒有token
ResultVo resultVo = new ResultVo(ResStatus.NO, "請先登錄!", null);
doResponse(response, resultVo);
} else {
//獲取jwt解析器
JwtParser parser = Jwts.parser();
parser.setSigningKey("luis333"); //之前加密時的加密密碼,一致才能解析成功
try {
//如果token正確(密碼正確,且在有效期內)則正常執行,否則拋出異常
Jws<Claims> claimsJws = parser.parseClaimsJws(token); //解析token
return true;
} catch (ExpiredJwtException e) {
ResultVo resultVo = new ResultVo(ResStatus.NO, "token已過期!", null);
doResponse(response, resultVo);
} catch (UnsupportedJwtException e) {
ResultVo resultVo = new ResultVo(ResStatus.NO, "異常token!", null);
doResponse(response, resultVo);
} catch (Exception e) {
ResultVo resultVo = new ResultVo(ResStatus.NO, "請先登錄!", null);
doResponse(response, resultVo);
}
}
return false;
}
/**
* 響應流程封裝
*/
private void doResponse(HttpServletResponse response, ResultVo resultVo) throws IOException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
String s = new ObjectMapper().writeValueAsString(resultVo);
out.print(s);
out.flush();
out.close();
}
}
攔截器配置類:
/**
* 攔截器配置類(SpringBoot中配置攔截器)
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer { //注意此處需要實現WebMvcConfigurer介面
@Autowired
private CheckTokenInterceptor checkTokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(checkTokenInterceptor) //添加攔截器
.addPathPatterns("/shopcart/**") //添加要攔截的請求
.addPathPatterns("/orders/**")
.excludePathPatterns("/user/**"); //放行的請求
}
}
常用請求頭傳遞 token
前端但凡訪問受限資源,都必須攜帶 token 發起請求;token 可以通過請求行(params)、請求頭(headers)以及請求體(data)傳遞,但是習慣性使用 headers 傳遞!
在實際開發中,發送請求時,我們一般都是在請求頭中來傳遞 token,通過自定義請求頭數據,傳遞 token 到後端。
前端使用自定義請求頭數據傳遞 token 示例:(注意請求頭傳遞數據關鍵字 headers,帶 s)
<script type="text/javascript">
var baseUrl = "//localhost:8080";
var vm = new Vue({
el: "#container",
data: {
token: ""
},
//鉤子函數,生命周期函數,直接定義!
created: function() {
//鉤子函數,data數據初始化之後自動調用
console.log("========> created method execute !")
var token = getCookieValue("token");
this.token = token;
console.log("token = " + token);
axios({
url: baseUrl + "/shopcart/list",
method: "get",
headers: {token: this.token} //自定義請求頭數據傳遞token
}).then(function(res) {
console.log(res.data);
});
}
});
</script>
注意:使用自定義請求頭傳遞數據時,瀏覽器會自動發起一次預檢請求(method="OPTIONS"
),來監測環境是否暢通,如果伺服器正常響應,則後續請求才可正常發起。
所以,當使用攔截器統一校驗 token ,並且使用自定義請求頭傳遞 token 時,需要在攔截器中放行瀏覽器自動發起的預檢請求,即放行 OPTIONS
請求方式的預檢請求。
主要注意點:
攔截器程式碼示例:
/**
* 攔截器
*/
@Component
public class CheckTokenInterceptor implements HandlerInterceptor {
/**
* 預處理方法,攔截請求,決定是否放行。
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//放行瀏覽器自動發起的預檢請求(請求方式為OPTIONS)
String method = request.getMethod();
System.out.println("method ========> " + method);
if ("OPTIONS".equalsIgnoreCase(method)) {
return true;
}
//從請求頭中獲取token並校驗
String token = request.getHeader("token");
System.out.println("preHandle ========> token = " + token);
if (token == null) {
//沒有token
ResultVo resultVo = new ResultVo(ResStatus.NO, "請先登錄!", null);
doResponse(response, resultVo);
} else {
//獲取jwt解析器
JwtParser parser = Jwts.parser();
parser.setSigningKey("luis333"); //之前加密時的加密密碼,一致才能解析成功
try {
//如果token正確(密碼正確,且在有效期內)則正常執行,否則拋出異常
Jws<Claims> claimsJws = parser.parseClaimsJws(token); //解析token
return true;
} catch (ExpiredJwtException e) {
ResultVo resultVo = new ResultVo(ResStatus.NO, "token已過期!", null);
doResponse(response, resultVo);
} catch (UnsupportedJwtException e) {
ResultVo resultVo = new ResultVo(ResStatus.NO, "異常token!", null);
doResponse(response, resultVo);
} catch (Exception e) {
ResultVo resultVo = new ResultVo(ResStatus.NO, "請先登錄!", null);
doResponse(response, resultVo);
}
}
return false;
}
/**
* 響應流程封裝
*/
private void doResponse(HttpServletResponse response, ResultVo resultVo) throws IOException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
String s = new ObjectMapper().writeValueAsString(resultVo);
out.print(s);
out.flush();
out.close();
}
}