前言
SpringBoot項目集成"JWT",首先了解一下JWT,本文項目屬於前後端分離項目,後端要做攔截處理以及安全驗證,採用“JWT”生成token信息,前端登錄時將後端生成的token信息跟隨登錄回執信息傳到前端,之後前端每次請求均要攜帶token信息,以便後端攔截驗證。
JWT maven依賴處理
本文項目爲maven項目,需要引入maven依賴
<!--引入JWT依賴,由於是基於Java,所以需要的是java-jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
JWT工具類
JwtUtil工具類,特別說明的是跟TUser相關的信息,均可根據實際情況自定義
/**
* @program: share
* @description: 工具類
* @author: Mr.Jkx
* @create: 2020-03-11 19:20
*/
public class JwtUtil {
/**
* JWT_WEB_TTL:WEBAPP應用中token的有效時間,默認30分鐘
*/
public static final long JWT_WEB_TTL = 30 * 60 * 1000;
/**
* 用戶登錄成功後生成Jwt
* 使用Hs256算法 私匙使用用戶密碼
*
* @param user 登錄成功的user對象
* @return
*/
public static String createJWT(TUser user) {
//指定簽名的時候使用的簽名算法,也就是header那部分,jjwt已經將這部分內容封裝好了。
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//生成JWT的時間
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//創建payload的私有聲明(根據特定的業務需要添加,如果要拿這個做驗證,一般是需要和jwt的接收方提前溝通好驗證方式的)
Map<String, Object> claims = new HashMap<>();
// id和name爲自定義信息,只要用於後端攔截器攔截請求之後,解析JWT數據做安全驗證使用
claims.put("id", user.getUserId());
claims.put("username", user.getUserName());
//生成簽名的時候使用的祕鑰secret,這個方法本地封裝了的,一般可以從本地配置文件中讀取,切記這個祕鑰不能外露哦。它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味着客戶端是可以自我簽發jwt了。
String key = user.getPassword();
//生成簽發人
String subject = user.getUserName();
//下面就是在爲payload添加各種標準聲明和私有聲明瞭
//這裏其實就是new一個JwtBuilder,設置jwt的body
JwtBuilder builder = Jwts.builder()
//如果有私有聲明,一定要先設置這個自己創建的私有的聲明,這個是給builder的claim賦值,一旦寫在標準的聲明賦值之後,就是覆蓋了那些標準的聲明的
.setClaims(claims)
//設置jti(JWT ID):是JWT的唯一標識,根據業務需要,這個可以設置爲一個不重複的值,主要用來作爲一次性token,從而回避重放攻擊。
.setId(UUIDUtil.getUUID())
//iat: jwt的簽發時間
.setIssuedAt(now)
//代表這個JWT的主體,即它的所有人,這個是一個json格式的字符串,可以存放什麼userid,roldid之類的,作爲什麼用戶的唯一標誌。
.setSubject(subject)
//設置簽名使用的簽名算法和簽名使用的祕鑰
.signWith(signatureAlgorithm, key);
long expMillis = nowMillis + JWT_WEB_TTL;
Date exp = new Date(expMillis);
//設置過期時間
builder.setExpiration(exp);
return builder.compact();
}
/**
* Token的解密
*
* @param token 加密後的token
* @param user 用戶的對象
* @return
*/
public static Claims parseJWT(String token, TUser user) {
//簽名祕鑰,和生成的簽名的祕鑰一模一樣
String key = user.getPassword();
//得到DefaultJwtParser
Claims claims = Jwts.parser()
//設置簽名的祕鑰
.setSigningKey(key)
//設置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
/**
* 校驗token
* 在這裏可以使用官方的校驗,我這裏校驗的是token中攜帶的用戶名於數據庫一致的話就校驗通過
*
* @param token
* @param user
* @return
*/
public static Boolean isVerify(String token, TUser user) {
//簽名祕鑰,和生成的簽名的祕鑰一模一樣
String key = user.getPassword();
//得到DefaultJwtParser
Claims claims = Jwts.parser()
//設置簽名的祕鑰
.setSigningKey(key)
//設置需要解析的jwt
.parseClaimsJws(token).getBody();
if (claims.get("username").equals(user.getUserName())) {
return true;
}
return false;
}
}
後臺根據JWT生成token
後端生成token信息很簡單,直接調用JWT的工具類方法即可
public Map<String, Object> selUser(TUser user) {
Map<String, Object> loginMap = new HashMap<>();
TUser tUser = tUserMapper.selUser(user);
if (tUser != null) {
// 登錄成功添加token緩存信息
loginMap.put("statusCode", "200");
// 調用token信息生成token信息
loginMap.put("token", JwtUtil.createJWT(tUser));
} else {
loginMap.put("statusCode", "300");
}
return loginMap;
}
登錄回執數據(測試數據)
{
"statusCode": "200",
"token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlkIjoiNDNkMjE0MjliNWQ0NDU4MTllMDkzN2ViMTc1NDYzMjgiLCJleHAiOjE1ODUyMDQ0NDgsImlhdCI6MTU4NTIwNDM4OCwianRpIjoiYmE1ZTdkOGI5NGQ3NDkzMmI2NGYxYmU4N2YzNzYxMzYiLCJ1c2VybmFtZSI6ImFkbWluIn0.emrxqF9S2Yh7rn2yWdMJ56aMLNMS-VpgAs65VM2ErhA"
}
請求攔截,token信息驗證
本文驗證方式爲,獲取前端請求攜帶的token信息,解析token信息,然後請求數據庫,驗證此數據是否存在,然後驗證數據的真實性,攔截器中的回執數據可自定義,此處不過多描述;可參閱作者自己整理的攔截器
/**
* @program: share
* @description: 登錄攔截器
* @author: Mr.Jkx
* @create: 2020-03-11 17:22
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(LoginInterceptor.class);
@Resource
private UserMapper UserMapper;
/**
* 預處理回調方法,實現處理器的預處理
* 返回值:true表示繼續流程;false表示流程中斷,不會繼續調用其他的攔截器或處理器
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 處理OPTIONS請求
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "86400");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Content-Type", "text/html;charset=utf-8");
if (request.getMethod().equals("OPTIONS")) {
response.setStatus(HttpServletResponse.SC_OK);
return false;
}
String requestURI = request.getRequestURI();
LOGGER.info("-------攔截請求鏈接:{},時間:{}-------", requestURI, DateUtil.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
// 獲取 token 信息
String token = request.getHeader("token");
if (StringUtils.isNotBlank(token)) {
DecodedJWT decode = JWT.decode(token);
// token 有效期
Date expiresAt = decode.getExpiresAt();
// 判斷此token信息是否在有效期內(時間設置參考“JwtUtil”)
if (expiresAt.getTime() > new Date().getTime()) {
// 操作用戶信息,此處“id”爲工具類自定義信息
String userId = decode.getClaim("id").asString();
if (StringUtils.isNotBlank(userId)) {
TUser tUser = UserMapper.selectByPrimaryKey(userId);
if (null != tUser) {
// 將得到的數據信息驗證真僞
if (JwtUtil.isVerify(token, tUser)) {
return true;
} else {
returnJson(response, "用戶數據錯誤");
}
} else {
returnJson(response, "用戶不存在");
}
}
} else {
returnJson(response, "token失效");
}
} else {
returnJson(response, "沒有token");
returnJson(response, "requestURI");
}
return false;
}
/**
* 後處理回調方法,實現處理器(controller)的後處理,但在渲染視圖之前
* 此時我們可以通過modelAndView對模型數據進行處理或對視圖進行處理
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 整個請求處理完畢回調方法,即在視圖渲染完畢時回調,
* 如性能監控中我們可以在此記錄結束時間並輸出消耗時間,
* 還可以進行一些資源清理,類似於try-catch-finally中的finally,
* 但僅調用處理器執行鏈中
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
/**
* @Description: 攔截回執數據
* @Author: Mr.Jkx
* @date: 2020/3/12 13:46
*/
private void returnJson(HttpServletResponse response, String msgData) {
PrintWriter writer = null;
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
try {
writer = response.getWriter();
Msg msg = Msg.fail().add("info", msgData);
String res = JsonUtil.toJsonString(msg);
writer.print(res);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
writer.close();
}
}
}
}
本文爲作者爲解決問題查詢整理的一個小結,可能存在不足或者安全隱患,非常期待大家的評論留言,一方面檢驗自己的一個學習整理成果,另一方面還是想跟大家學習,歡迎大家批評指正,謝謝!!!