SpringSecurity Jwt Token 自動刷新的實現(xiàn)
功能需求
最近項目中有這么一個功能,用戶登錄系統(tǒng)后,需要給 用戶 頒發(fā)一個 token ,后續(xù)訪問系統(tǒng)的請求都需要帶上這個 token ,如果請求沒有帶上這個 token 或者 token 過期了,那么禁止訪問系統(tǒng)。如果用戶一直訪問系統(tǒng),那么還需要自動延長 token 的過期時間。
功能分析
1、token 的生成
使用現(xiàn)在比較流行的 jwt
來生成。
2、token 的自動延長
要實現(xiàn) token 的自動延長,系統(tǒng)給用戶 頒發(fā) 一個 token 無法實現(xiàn),那么通過變通一個,給用戶生成 2個 token ,一個用于 api 訪問的 token ,一個 用于在 token 過期的時候 用來 刷新 的 refreshToken。并且 refreshToken 的 生命周期要比 token 的生命周期長。
3、系統(tǒng)資源的保護(hù)
可以使用Spring Security
來保護(hù)系統(tǒng)的各種資源。
4、用戶如何傳遞 token
系統(tǒng)中 token
和 refreshToken
的傳遞一律放在請求頭。
實現(xiàn)思路
1、生成 token 和 refreshToken
用戶登錄系統(tǒng)的時候,后臺給用戶生成 token
和 refreshToken
并放在響應(yīng)頭中返回
2、系統(tǒng) 判斷 token 是否合法
token
未失效的時的處理token
失效 ,如何使用refreshToken
來生成新的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; } // 獲取到真實的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)證對象 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()); // 檢測 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)證對象 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)); } /** * 獲取到真實的 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)證對象 */ 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 自動刷新的實現(xiàn)的文章就介紹到這了,更多相關(guān)SpringSecurity 自動刷新內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java高效實現(xiàn)電商產(chǎn)品排序?qū)崙?zhàn)
這篇文章主要為大家介紹了Java高效實現(xiàn)電商產(chǎn)品排序?qū)崙?zhàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11MapStruct對象映射轉(zhuǎn)換解決Bean屬性拷貝性能問題
無意間看到項目中有小伙伴用到了 MapStruct 來做對象映射轉(zhuǎn)換當(dāng)時我就很好奇,這個是什么框架,能夠解決什么問題,帶著這兩個疑問就有了下面的文章2022-02-02SpringBoot整合XxlJob分布式任務(wù)調(diào)度平臺
xxl-job是一個開源的分布式定時任務(wù)框架,它可以與其他微服務(wù)組件一起構(gòu)成微服務(wù)集群。它的調(diào)度中心(xxl-job)和執(zhí)行器(自己的springboot項目中有@XxlJob("定時任務(wù)名稱")的方法)是相互分離,分開部署的,兩者通過HTTP協(xié)議進(jìn)行通信2023-02-02Spring條件注解@ConditionnalOnClass的原理分析
這篇文章主要介紹了Spring條件注解@ConditionnalOnClass的原理分析,所謂@ConditionalOnClass注解,翻譯過來就是基于class的條件,它為所標(biāo)注的類或方法添加限制條件,當(dāng)該條件的值為true時,其所標(biāo)注的類或方法才能生效,需要的朋友可以參考下2023-12-12Springboot使用redis進(jìn)行api防刷限流過程詳解
這篇文章主要介紹了Springboot使用redis進(jìn)行api防刷限流過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-12-12Java?Maven構(gòu)建工具中mvnd和Gradle誰更快
這篇文章主要介紹了Java?Maven構(gòu)建工具中mvnd和Gradle誰更快,mvnd?是?Maven?Daemon?的縮寫?,翻譯成中文就是?Maven?守護(hù)進(jìn)程,下文更多相關(guān)資料,需要的小伙伴可以參考一下2022-05-05