深入總結SpringBoot整合JWT,這應該是全網講的最通俗易懂的了
JWT
JWT(JSON Web Token)是為了在網絡應用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開放標準。
舉例登錄過程


在這里個人整理了一些資料,有需要的朋友可以直接點擊領取。
Java基礎知識大全
22本Java架構師核心書籍
從0到1Java學習路線和資料
1000+道2021年最新面試題
組成
JWT具體長什么樣呢?JWT是由三段信息構成的,將這三段信息文本用.鏈接一起就構成了JWT字符串。就像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
復制代碼元素
header
JWT的頭部承載兩部分信息:
聲明類型,這里是JWT;
聲明加密的算法,通常直接使用 HMAC SHA256;
完整的頭部就像下面這樣的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
復制代碼使用base64加密,構成了第一部分。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
復制代碼playload(重點)
載荷就是存放有效信息的地方,這些有效信息包含三個部分:
標準中注冊的聲明;
公共的聲明;
私有的聲明;
其中,標準中注冊的聲明 (建議但不強制使用)包括如下幾個部分 :
iss: jwt簽發(fā)者;
sub: jwt所面向的用戶;
aud: 接收jwt的一方;
exp: jwt的過期時間,這個過期時間必須要大于簽發(fā)時間;
nbf: 定義在什么時間之前,該jwt都是不可用的;
iat: jwt的簽發(fā)時間;
jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊;
公共的聲明部分:公共的聲明可以添加任何的信息,一般添加用戶的相關信息或其他業(yè)務需要的必要信息,但不建議添加敏感信息,因為該部分在客戶端可解密。
私有的聲明部分:私有聲明是提供者和消費者所共同定義的聲明,一般不建議存放敏感信息,因為base64是對稱解密的,意味著該部分信息可以歸類為明文信息。
定義一個payload:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
復制代碼然后將其進行base64加密,得到Jwt的第二部分:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
復制代碼signature
jwt的第三部分是一個簽證信息,這個簽證信息由三部分組成:
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, '密鑰');
加密之后,得到signature簽名信息。
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
復制代碼jwt最終格式
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
復制代碼secret用來進行jwt的簽發(fā)和jwt的驗證,所以,在任何場景都不應該流露出去。
元素


SpringBoot整合JWT【正片】
引入依賴
com.auth0
java-jwt
復制代碼創(chuàng)建JWT工具類
注意靜態(tài)屬性的配置文件注入方式:
package com.neuq.common.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.neuq.common.exception.ApiException;
import com.neuq.common.response.ResultInfo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @Author: xiang
* @Date: 2021/5/11 21:11
*
* JwtToken生成的工具類
* JWT token的格式:header.payload.signature
* header的格式(算法、token的類型),默認:{"alg": "HS512","typ": "JWT"}
* payload的格式 設置:(用戶信息、創(chuàng)建時間、生成時間)
* signature的生成算法:
* HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
*/
@Component
@ConfigurationProperties(prefix = "jwt")
public class JWTUtils {
//定義token返回頭部
public static String header;
//token前綴
public static String tokenPrefix;
//簽名密鑰
public static String secret;
//有效期
public static long expireTime;
//存進客戶端的token的key名
public static final String USER_LOGIN_TOKEN = "USER_LOGIN_TOKEN";
public void setHeader(String header) {
JWTUtils.header = header;
}
public void setTokenPrefix(String tokenPrefix) {
JWTUtils.tokenPrefix = tokenPrefix;
}
public void setSecret(String secret) {
JWTUtils.secret = secret;
}
public void setExpireTime(int expireTimeInt) {
JWTUtils.expireTime = expireTimeInt*1000L*60;
}
/**
* 創(chuàng)建TOKEN
* @param sub
* @return
*/
public static String createToken(String sub){
return tokenPrefix + JWT.create()
.withSubject(sub)
.withExpiresAt(new Date(System.currentTimeMillis() + expireTime))
.sign(Algorithm.HMAC512(secret));
}
/**
* 驗證token
* @param token
*/
public static String validateToken(String token){
try {
return JWT.require(Algorithm.HMAC512(secret))
.build()
.verify(token.replace(tokenPrefix, ""))
.getSubject();
} catch (TokenExpiredException e){
throw new ApiException(ResultInfo.unauthorized("token已經過期"));
} catch (Exception e){
throw new ApiException(ResultInfo.unauthorized("token驗證失敗"));
}
}
/**
* 檢查token是否需要更新
* @param token
* @return
*/
public static boolean isNeedUpdate(String token){
//獲取token過期時間
Date expiresAt = null;
try {
expiresAt = JWT.require(Algorithm.HMAC512(secret))
.build()
.verify(token.replace(tokenPrefix, ""))
.getExpiresAt();
} catch (TokenExpiredException e){
return true;
} catch (Exception e){
throw new ApiException(ResultInfo.unauthorized("token驗證失敗"));
}
//如果剩余過期時間少于過期時常的一般時 需要更新
return (expiresAt.getTime()-System.currentTimeMillis()) < (expireTime>>1);
}
}
復制代碼
yaml屬性配置
jwt:
header: "Authorization" #token返回頭部
tokenPrefix: "Bearer " #token前綴
secret: "qwertyuiop7418520" #密鑰
expireTime: 1 #token有效時間 (分鐘) 建議一小時以上
復制代碼登錄方法將用戶信息存入token中返回
@Override
public Map login(User user) {
//phone是除id外的唯一標志 需要進行檢查
if (user.getPhone() == null || user.getPhone().equals(""))
throw new ApiException("手機號不合法");
User selectUser = userDao.selectUserByPhone(user.getPhone());
if (selectUser == null) {
//注冊用戶
int count = userDao.insertUser(user);
if (count < 1) throw new ApiException(ResultInfo.serviceUnavailable("注冊異常"));
}
//將userId存入token中
String token = JWTUtils.createToken(selectUser.getUserId().toString());
Map map = new HashMap<>();
map.put("user",selectUser);
map.put("token",token);
return map;
}
復制代碼 注意將token保存到Http 的 header
@GetMapping("/login")
public ResultInfo login(User user, HttpServletResponse response) {
Map map = userService.login(user);
//將token存入Http的header中
response.setHeader(JWTUtils.USER_LOGIN_TOKEN, (String) map.get("token"));
return ResultInfo.success((User)map.get("user"));
}
復制代碼 攔截器驗證每次請求的token
/**
* @Author: xiang
* @Date: 2021/5/7 20:56
*
* 攔截器:驗證用戶是否登錄
*/
public class UserLoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//http的header中獲得token
String token = request.getHeader(JWTUtils.USER_LOGIN_TOKEN);
//token不存在
if (token == null || token.equals("")) throw new ApiException("請先登錄");
//驗證token
String sub = JWTUtils.validateToken(token);
if (sub == null || sub.equals(""))
throw new ApiException(ResultInfo.unauthorized("token驗證失敗"));
//更新token有效時間 (如果需要更新其實就是產生一個新的token)
if (JWTUtils.isNeedUpdate(token)){
String newToken = JWTUtils.createToken(sub);
response.setHeader(JWTUtils.USER_LOGIN_TOKEN,newToken);
}
return true;
}
}
復制代碼
@Configuration
@ComponentScan(basePackages = "com.neuq.common") //全局異常處理類需要被掃描才能
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 注冊自定義攔截器
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserLoginInterceptor())
.addPathPatterns("/user/**")
.addPathPatterns("/userInfo/**")
.excludePathPatterns("/user/login");//開放登錄路徑
}
}
復制代碼單點登錄
將token或者一個唯一標識UUID=UUID.randomUUID().toString()存進Cookie中(別存在Http的header中了),設置路徑為整個項目根路徑/*;往往以這個唯一標識為key,用戶信息為value緩存在服務器中?。?!
最后
感謝大佬能看到最后,覺得文章對你有幫助記得點個贊!
作者:程序員偉杰
鏈接:https://juejin.cn/post/6962142423879270437
來源:稀土掘金
著作權歸作者所有。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處。
