1、引入jwt的jar包
<!--JWT(Json Web Token)登錄支持-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
2、jwt工具類
package com.linhaijing.javaxxbjcommon.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* add by linhaijing 2020年7月3日21:14:24
* JwtToken生成的工具類
* JWT token的格式:header.payload.signature
* header的格式(算法、token的類型):
* {"alg": "HS512","typ": "JWT"}
* payload的格式(用戶名、創建時間、生成時間):
* {"sub":"wang","created":1489079981393,"exp":1489684781}
* signature的生成算法:
* HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
* https://github.com/shenzhuan/mallplus on 2018/4/26.
*/
@Component
public class JwtTokenUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
private static final String CLAIM_KEY_USERNAME = "sub";
private static final String CLAIM_KEY_CREATED = "created";
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
/**
* 根據負責生成JWT的token
*/
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 從token中獲取JWT中的負載
*/
private Claims getClaimsFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.error("JWT格式驗證失敗:{}", token);
}
return claims;
}
/**
* 生成token的過期時間
*/
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
/**
* 從token中獲取登錄用戶名
*/
public String getUserNameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 驗證token是否還有效
*
* @param token 客戶端傳入的token
* @param loginName 從數據庫中查詢出來的用戶登錄名
*/
public boolean validateToken(String token, String loginName) {
if (StringUtils.isNotBlank(loginName)) {
String username = getUserNameFromToken(token);
return username.equals(loginName) && !isTokenExpired(token);
}
return false;
}
/**
* 判斷token是否已經失效
*/
public boolean isTokenExpired(String token) {
Date expiredDate = getExpiredDateFromToken(token);
return expiredDate.before(new Date());
}
/**
* 從token中獲取過期時間
*/
private Date getExpiredDateFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
/**
* 根據用戶信息生成token
*/
// public String generateToken(String loginName) {
// Map<String, Object> claims = new HashMap<>();
// claims.put(CLAIM_KEY_USERNAME, loginName);
// claims.put(CLAIM_KEY_CREATED, new Date());
// return generateToken(claims);
// }
public String generateToken(String loginName) {
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, loginName);
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/**
* 判斷token是否可以被刷新
*/
public boolean canRefresh(String token) {
return !isTokenExpired(token);
}
/**
* 刷新token
*/
public String refreshToken(String token) {
Claims claims = getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
}
3、登錄時返回jwt的token
String token = jwtTokenUtil.generateToken("token中攜帶的用戶信息,比如:id,username");
resultMap.put("token", tokenHead + token);
4、前端保存token
c.request(c.config.loginSubmit, "post", JSON.stringify(userPO), "application/json", false, function (result) {
if(result.code == 200){
var data = result.data;
showMsg("登錄成功");
//保存token
window.localStorage.setItem('token', data.token);
_this.login = false;
var url = window.location.href.split("targetPage=");
if(url.length == 2){
window.location.href = url[1];
}else{
window.location.href = "/index.html";
}
}else{
showMsg(result.msg);
_this.refreshCaptcha();
}
})
5、前端請求接口時每個方法的header中攜帶token
c.request = function(url, get, data, contentType, async, callback) {
$.ajax({
url: url,
type: get ? get : post,
data: data,
async: async,
dataType: 'json',
contentType: contentType,
timeout: 100000,
cache:false,
processData: false,
headers: {
Authorization: window.localStorage.getItem("token")
},
success: function(data) {
if (typeof callback == "function") {
(function() {
layer.close(index);
callback(data);
})();
}
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
if(XMLHttpRequest.responseText == 403){
showMsg("登錄已過期,請重新登錄");
// setTimeout(function () {
window.location.href = "/login.html";
// },1500);
}
layer.close(index);
},
//請求之前
beforeSend: function() {
// index = layer.load(2, {time: 10*1000});
index =layer.load();
},
complete: function(XMLHttpRequest, status) {
if (status == 'timeout') {
layer.close(index);
} else if (status == 'error') {
layer.close(index);
}
}
});
};
6、後端對需要登錄的接口進行攔截,並校驗token是否過期,以及校驗後端對應的用戶信息緩存中是否存在,即判斷登錄是否過期
import com.linhaijing.javaxxbjcommon.utils.JwtTokenUtil;
import com.linhaijing.javaxxbjpcweb.util.GetGlobalUserBeanUtil;
import com.linhaijing.javaxxbjssoapi.po.UserPO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 功能描述:登錄攔截器
*
* @Author : 林海靜
* @CreateTime: 2019/12/30 18:05
*/
@Slf4j
public class LoginInterceptor extends HandlerInterceptorAdapter {
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Autowired
private JwtTokenUtil jwtTokenUtil;
public LoginInterceptor(){}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判斷jwt token
UserPO userPO = GetGlobalUserBeanUtil.getGlobalUserBean();
if(userPO == null){
log.info("LoginInterceptor->jwt爲空");
if (this.ajaxHandleFail(request, response)) {
return false;
}
response.sendRedirect("/index.html");
return false;
}
return true;
}
/**
* 功能描述:判斷是否是ajax請求
* @Author: 林海靜
* @CreateTime: 2019/12/30 18:23
* <>
* @params:
* @return:
* @Exception:
*/
private boolean ajaxHandleFail(HttpServletRequest request, HttpServletResponse response) throws Exception {
if ("XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"))) {
response.setStatus(403);
response.getWriter().write("403");
log.info("ajax請求session超時了");
response.getWriter().close();
return true;
} else {
return false;
}
}
}
@Autowired
public static UserPO getGlobalUserBean() {
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
String tokenPre = tokenHeader;
String authHeader = request.getHeader(tokenPre);
log.info("getGlobalUserBean()---->authHeader=" + authHeader);
if (authHeader != null && authHeader.startsWith("Bearer")) {
String authToken = authHeader.substring("Bearer".length());
//校驗token時間是否過期
if (StringUtils.isNotBlank(authToken) && !jwtTokenUtil.isTokenExpired(authToken)) {
String loginName = jwtTokenUtil.getUserNameFromToken(authToken);
log.info("獲取 "+loginName+" 的信息");
String key = GlobalConstants.JAVAXXBJ_USER_SESSION + ":" + loginName;
return userServiceFeign.getBySessionKey(key);
}
log.info("getGlobalUserBean()---->jwt token已過期");
return null;
}
log.info("getGlobalUserBean()---->用戶未登錄,獲取當前用戶信息:null");
return null;
}
7、如果過期,則返回403,前端跳轉到登錄界面
c.request = function(url, get, data, contentType, async, callback) {
$.ajax({
url: url,
type: get ? get : post,
data: data,
async: async,
dataType: 'json',
contentType: contentType,
timeout: 100000,
cache:false,
processData: false,
headers: {
Authorization: window.localStorage.getItem("token")
},
success: function(data) {
if (typeof callback == "function") {
(function() {
layer.close(index);
callback(data);
})();
}
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
if(XMLHttpRequest.responseText == 403){
showMsg("登錄已過期,請重新登錄");
window.location.href = "/login.html";
}
layer.close(index);
},
//請求之前
beforeSend: function() {
// index = layer.load(2, {time: 10*1000});
index =layer.load();
},
complete: function(XMLHttpRequest, status) {
if (status == 'timeout') {
layer.close(index);
} else if (status == 'error') {
layer.close(index);
}
}
});
};
8、系統退出時,刪除redis中的用戶信息,則表示服務端用戶信息已經過期,即使前端token還存在,但也不能進行登錄
@Override
@Transactional
public ResultObject logout(UserPO userPO, String session) {
String key = GlobalConstants.JAVAXXBJ_USER_SESSION + ":" + userPO.getLoginName();
Boolean delReturn = jedisClient.del(key);
if(!delReturn) throw new SysException("退出失敗,請重試");
return ResultObject.ok();
}