SpringSecurity Jwt Token 自動(dòng)刷新的實(shí)現(xiàn)
功能需求
最近項(xiàng)目中有這么一個(gè)功能,用戶登錄系統(tǒng)后,需要給 用戶 頒發(fā)一個(gè) token ,后續(xù)訪問(wèn)系統(tǒng)的請(qǐng)求都需要帶上這個(gè) token ,如果請(qǐng)求沒(méi)有帶上這個(gè) token 或者 token 過(guò)期了,那么禁止訪問(wèn)系統(tǒng)。如果用戶一直訪問(wèn)系統(tǒng),那么還需要自動(dòng)延長(zhǎng) token 的過(guò)期時(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),那么通過(guò)變通一個(gè),給用戶生成 2個(gè) token ,一個(gè)用于 api 訪問(wèn)的 token ,一個(gè) 用于在 token 過(guò)期的時(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、過(guò)濾器代碼,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 是否過(guò)期
if (JwtUtils.isJwtExpired(jws)) {
// 處理過(guò)期
handleTokenExpired(response, request, filterChain);
return;
}
// 構(gòu)建認(rèn)證對(duì)象
JwtUtils.buildAuthentication(jws, tokenProperties.getUserId());
filterChain.doFilter(request, response);
}
/**
* 處理token過(guò)期情況
*
* @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過(guò)期了,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 是否過(guò)期
if (JwtUtils.isJwtExpired(refreshToken)) {
writeJson(response, "refresh token 過(guò)期了");
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 是否過(guò)期
*
* @param jws
* @return true:過(guò)期 false:沒(méi)過(guò)期
*/
public static boolean isJwtExpired(Jws<Claims> jws) {
return jws.getBody().getExpiration().before(new Date());
}
/**
* 構(gòu)建認(rèn)證過(guò)的認(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高效實(shí)現(xiàn)電商產(chǎn)品排序?qū)崙?zhàn)
這篇文章主要為大家介紹了Java高效實(shí)現(xiàn)電商產(chǎn)品排序?qū)崙?zhàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
Maven項(xiàng)目繼承實(shí)現(xiàn)過(guò)程圖解
這篇文章主要介紹了Maven項(xiàng)目繼承實(shí)現(xiàn)過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
MapStruct對(duì)象映射轉(zhuǎn)換解決Bean屬性拷貝性能問(wèn)題
無(wú)意間看到項(xiàng)目中有小伙伴用到了 MapStruct 來(lái)做對(duì)象映射轉(zhuǎn)換當(dāng)時(shí)我就很好奇,這個(gè)是什么框架,能夠解決什么問(wèn)題,帶著這兩個(gè)疑問(wèn)就有了下面的文章2022-02-02
SpringBoot整合XxlJob分布式任務(wù)調(diào)度平臺(tái)
xxl-job是一個(gè)開(kāi)源的分布式定時(shí)任務(wù)框架,它可以與其他微服務(wù)組件一起構(gòu)成微服務(wù)集群。它的調(diào)度中心(xxl-job)和執(zhí)行器(自己的springboot項(xiàng)目中有@XxlJob("定時(shí)任務(wù)名稱")的方法)是相互分離,分開(kāi)部署的,兩者通過(guò)HTTP協(xié)議進(jìn)行通信2023-02-02
Spring?Boot與Redis的緩存一致性問(wèn)題解決
在使用緩存時(shí),緩存一致性問(wèn)題是一個(gè)常見(jiàn)的挑戰(zhàn),本文主要介紹了Spring?Boot與Redis的緩存一致性問(wèn)題,具有一定的參考價(jià)值,感興趣的可以了解一下2024-07-07
Spring條件注解@ConditionnalOnClass的原理分析
這篇文章主要介紹了Spring條件注解@ConditionnalOnClass的原理分析,所謂@ConditionalOnClass注解,翻譯過(guò)來(lái)就是基于class的條件,它為所標(biāo)注的類或方法添加限制條件,當(dāng)該條件的值為true時(shí),其所標(biāo)注的類或方法才能生效,需要的朋友可以參考下2023-12-12
Springboot使用redis進(jìn)行api防刷限流過(guò)程詳解
這篇文章主要介紹了Springboot使用redis進(jìn)行api防刷限流過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
Java?Maven構(gòu)建工具中mvnd和Gradle誰(shuí)更快
這篇文章主要介紹了Java?Maven構(gòu)建工具中mvnd和Gradle誰(shuí)更快,mvnd?是?Maven?Daemon?的縮寫?,翻譯成中文就是?Maven?守護(hù)進(jìn)程,下文更多相關(guān)資料,需要的小伙伴可以參考一下2022-05-05

