SpringSecurity Jwt Token 自動(dòng)刷新的實(shí)現(xiàn)
功能需求
最近項(xiàng)目中有這么一個(gè)功能,用戶登錄系統(tǒng)后,需要給 用戶 頒發(fā)一個(gè) token ,后續(xù)訪問系統(tǒng)的請(qǐng)求都需要帶上這個(gè) token ,如果請(qǐng)求沒有帶上這個(gè) token 或者 token 過期了,那么禁止訪問系統(tǒng)。如果用戶一直訪問系統(tǒng),那么還需要自動(dòng)延長(zhǎng) token 的過期時(shí)間。
功能分析
1、token 的生成
使用現(xiàn)在比較流行的 jwt 來(lái)生成。
2、token 的自動(dòng)延長(zhǎng)
要實(shí)現(xiàn) token 的自動(dòng)延長(zhǎng),系統(tǒng)給用戶 頒發(fā) 一個(gè) token 無(wú)法實(shí)現(xiàn),那么通過變通一個(gè),給用戶生成 2個(gè) token ,一個(gè)用于 api 訪問的 token ,一個(gè) 用于在 token 過期的時(shí)候 用來(lái) 刷新 的 refreshToken。并且 refreshToken 的 生命周期要比 token 的生命周期長(zhǎng)。
3、系統(tǒng)資源的保護(hù)
可以使用Spring Security 來(lái)保護(hù)系統(tǒng)的各種資源。
4、用戶如何傳遞 token
系統(tǒng)中 token 和 refreshToken 的傳遞一律放在請(qǐng)求頭。
實(shí)現(xiàn)思路
1、生成 token 和 refreshToken
用戶登錄系統(tǒng)的時(shí)候,后臺(tái)給用戶生成 token 和 refreshToken 并放在響應(yīng)頭中返回
2、系統(tǒng) 判斷 token 是否合法
token未失效的時(shí)的處理token失效 ,如何使用refreshToken來(lái)生成新的token

核心代碼如下
1、過濾器代碼,token判斷和再次生成
package com.huan.study.security.token;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.huan.study.security.configuration.TokenProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* @author huan 2020-06-07 - 14:34
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class TokenAuthenticateFilter extends OncePerRequestFilter {
private final TokenProperties tokenProperties;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 獲取 認(rèn)證頭
String authorizationHeader = request.getHeader(tokenProperties.getAuthorizationHeaderName());
if (!checkIsTokenAuthorizationHeader(authorizationHeader)) {
log.debug("獲取到認(rèn)證頭Authorization的值:[{}]但不是我們系統(tǒng)中登錄后簽發(fā)的。", authorizationHeader);
filterChain.doFilter(request, response);
return;
}
// 獲取到真實(shí)的token
String realToken = getRealAuthorizationToken(authorizationHeader);
// 解析 jwt token
Jws<Claims> jws = JwtUtils.parserAuthenticateToken(realToken, tokenProperties.getSecretKey());
// token 不合法
if (null == jws) {
writeJson(response, "認(rèn)證token不合法");
return;
}
// token 是否過期
if (JwtUtils.isJwtExpired(jws)) {
// 處理過期
handleTokenExpired(response, request, filterChain);
return;
}
// 構(gòu)建認(rèn)證對(duì)象
JwtUtils.buildAuthentication(jws, tokenProperties.getUserId());
filterChain.doFilter(request, response);
}
/**
* 處理token過期情況
*
* @param response
* @param request
* @param filterChain
* @return
* @throws IOException
*/
private void handleTokenExpired(HttpServletResponse response, HttpServletRequest request, FilterChain filterChain) throws IOException, ServletException {
// 獲取刷新 token
String refreshTokenHeader = request.getHeader(tokenProperties.getRefreshHeaderName());
// 檢測(cè) refresh-token 是否是我們系統(tǒng)中簽發(fā)的
if (!checkIsTokenAuthorizationHeader(refreshTokenHeader)) {
log.debug("獲取到刷新認(rèn)證頭:[{}]的值:[{}]但不是我們系統(tǒng)中登錄后簽發(fā)的。", tokenProperties.getRefreshHeaderName(), refreshTokenHeader);
writeJson(response, "token過期了,refresh token 不是我們系統(tǒng)簽發(fā)的");
return;
}
// 解析 refresh-token
Jws<Claims> refreshToken = JwtUtils.parserAuthenticateToken(getRealAuthorizationToken(refreshTokenHeader),
tokenProperties.getSecretKey());
// 判斷 refresh-token 是否不合法
if (null == refreshToken) {
writeJson(response, "refresh token不合法");
return;
}
// 判斷 refresh-token 是否過期
if (JwtUtils.isJwtExpired(refreshToken)) {
writeJson(response, "refresh token 過期了");
return;
}
// 重新簽發(fā) token
String newToken = JwtUtils.generatorJwtToken(
refreshToken.getBody().get(tokenProperties.getUserId()),
tokenProperties.getUserId(),
tokenProperties.getTokenExpireSecond(),
tokenProperties.getSecretKey()
);
response.addHeader(tokenProperties.getAuthorizationHeaderName(), newToken);
// 構(gòu)建認(rèn)證對(duì)象
JwtUtils.buildAuthentication(JwtUtils.parserAuthenticateToken(newToken, tokenProperties.getSecretKey()), tokenProperties.getUserId());
filterChain.doFilter(request, response);
}
/**
* 寫 json 數(shù)據(jù)給前端
*
* @param response
* @throws IOException
*/
private void writeJson(HttpServletResponse response, String msg) throws IOException {
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
Map<String, String> params = new HashMap<>(4);
params.put("msg", msg);
response.getWriter().print(OBJECT_MAPPER.writeValueAsString(params));
}
/**
* 獲取到真實(shí)的 token 串
*
* @param authorizationToken
* @return
*/
private String getRealAuthorizationToken(String authorizationToken) {
return StringUtils.substring(authorizationToken, tokenProperties.getTokenHeaderPrefix().length()).trim();
}
/**
* 判斷是否是系統(tǒng)中登錄后簽發(fā)的token
*
* @param authorizationHeader
* @return
*/
private boolean checkIsTokenAuthorizationHeader(String authorizationHeader) {
if (StringUtils.isBlank(authorizationHeader)) {
return false;
}
if (!StringUtils.startsWith(authorizationHeader, tokenProperties.getTokenHeaderPrefix())) {
return false;
}
return true;
}
}
2、jwt 工具類代碼
package com.huan.study.security.token;
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.DefaultJws;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
/**
* jwt 工具類
*
* @author huan
* @date 2020-05-20 - 17:09
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JwtUtils {
/**
* 解析 jwt token
*
* @param token 需要解析的json
* @param secretKey 密鑰
* @return
*/
public static Jws<Claims> parserAuthenticateToken(String token, String secretKey) {
try {
final Jws<Claims> claimsJws = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token);
return claimsJws;
} catch (ExpiredJwtException e) {
return new DefaultJws<>(null, e.getClaims(), "");
} catch (UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException | IncorrectClaimException e) {
log.error(e.getMessage(), e);
return null;
}
}
/**
* 判斷 jwt 是否過期
*
* @param jws
* @return true:過期 false:沒過期
*/
public static boolean isJwtExpired(Jws<Claims> jws) {
return jws.getBody().getExpiration().before(new Date());
}
/**
* 構(gòu)建認(rèn)證過的認(rèn)證對(duì)象
*/
public static Authentication buildAuthentication(Jws<Claims> jws, String userIdFieldName) {
Object userId = jws.getBody().get(userIdFieldName);
TestingAuthenticationToken testingAuthenticationToken = new TestingAuthenticationToken(userId, null, new ArrayList<>(0));
SecurityContextHolder.getContext().setAuthentication(testingAuthenticationToken);
return SecurityContextHolder.getContext().getAuthentication();
}
/**
* 生成 jwt token
*/
public static String generatorJwtToken(Object loginUserId, String userIdFieldName, Long expireSecond, String secretKey) {
Date expireTime = Date.from(LocalDateTime.now().plusSeconds(expireSecond).atZone(ZoneId.systemDefault()).toInstant());
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setIssuedAt(new Date())
.setExpiration(expireTime)
.claim(userIdFieldName, loginUserId)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
}
完整代碼
代碼 https://gitee.com/huan1993/Spring-Security/tree/master/spring-security-jwt
到此這篇關(guān)于SpringSecurity Jwt Token 自動(dòng)刷新的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringSecurity 自動(dòng)刷新內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java捕獲AOP級(jí)別的異常并將其傳遞到Controller層
如何在一個(gè)現(xiàn)代的Java應(yīng)用中,捕獲AOP(面向切面編程)級(jí)別的異常,并將這些異常傳遞到Controller層進(jìn)行合適的處理,異常處理在構(gòu)建可靠的應(yīng)用程序中起著關(guān)鍵作用,而AOP則可以幫助我們更好地管理和組織代碼,我們將深入研究如何結(jié)合AOP和異常處理來(lái)構(gòu)建健壯的應(yīng)用2023-09-09
@Scheduled注解不能同時(shí)執(zhí)行多個(gè)定時(shí)任務(wù)的解決方案
這篇文章主要介紹了@Scheduled注解不能同時(shí)執(zhí)行多個(gè)定時(shí)任務(wù)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
java啟動(dòng)jar包設(shè)置啟動(dòng)參數(shù)的實(shí)現(xiàn)
本文主要介紹了java啟動(dòng)jar包設(shè)置啟動(dòng)參數(shù)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
Java描述數(shù)據(jù)結(jié)構(gòu)學(xué)習(xí)之鏈表的增刪改查詳解
這篇文章主要給大家介紹了關(guān)于Java描述數(shù)據(jù)結(jié)構(gòu)學(xué)習(xí)之鏈表的增刪改查的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05
JAVA泛型的繼承和實(shí)現(xiàn)、擦除原理解析
這篇文章主要介紹了JAVA泛型的繼承和實(shí)現(xiàn)、擦除原理解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
Spring Boot + Mybatis多數(shù)據(jù)源和動(dòng)態(tài)數(shù)據(jù)源配置方法
最近做項(xiàng)目遇到這樣的應(yīng)用場(chǎng)景,項(xiàng)目需要同時(shí)連接兩個(gè)不同的數(shù)據(jù)庫(kù)A, B,并且它們都為主從架構(gòu),一臺(tái)寫庫(kù),多臺(tái)讀庫(kù)。下面小編給大家?guī)?lái)了Spring Boot + Mybatis多數(shù)據(jù)源和動(dòng)態(tài)數(shù)據(jù)源配置方法,需要的朋友參考下吧2018-01-01

