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-11Maven項(xiàng)目繼承實(shí)現(xiàn)過(guò)程圖解
這篇文章主要介紹了Maven項(xiàng)目繼承實(shí)現(xiàn)過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08MapStruct對(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-02SpringBoot整合XxlJob分布式任務(wù)調(diào)度平臺(tái)
xxl-job是一個(gè)開源的分布式定時(shí)任務(wù)框架,它可以與其他微服務(wù)組件一起構(gòu)成微服務(wù)集群。它的調(diào)度中心(xxl-job)和執(zhí)行器(自己的springboot項(xiàng)目中有@XxlJob("定時(shí)任務(wù)名稱")的方法)是相互分離,分開部署的,兩者通過(guò)HTTP協(xié)議進(jìn)行通信2023-02-02Spring?Boot與Redis的緩存一致性問(wèn)題解決
在使用緩存時(shí),緩存一致性問(wèn)題是一個(gè)常見的挑戰(zhàn),本文主要介紹了Spring?Boot與Redis的緩存一致性問(wèn)題,具有一定的參考價(jià)值,感興趣的可以了解一下2024-07-07Spring條件注解@ConditionnalOnClass的原理分析
這篇文章主要介紹了Spring條件注解@ConditionnalOnClass的原理分析,所謂@ConditionalOnClass注解,翻譯過(guò)來(lái)就是基于class的條件,它為所標(biāo)注的類或方法添加限制條件,當(dāng)該條件的值為true時(shí),其所標(biāo)注的類或方法才能生效,需要的朋友可以參考下2023-12-12Springboot使用redis進(jìn)行api防刷限流過(guò)程詳解
這篇文章主要介紹了Springboot使用redis進(jìn)行api防刷限流過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12Java?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