使用SpringBoot簡單實現(xiàn)無感知的刷新 Token功能
引言
實現(xiàn)無感知的刷新 Token 是一種提升用戶體驗的常用技術,可以在用戶使用應用時自動更新 Token,無需用戶手動干預。這種技術在需要長時間保持用戶登錄狀態(tài)的應用中非常有用,比如在一些需要頻繁訪問服務器資源的WEB和移動應用。以下是使用Spring Boot實現(xiàn)無感知刷新Token的一個場景案例和相應的示例代碼。
場景案例
假設我們有一個電子商務平臺,用戶登錄后可以瀏覽商品、加入購物車、提交訂單等。為了保持用戶會話的安全,我們使用JWT(JSON Web Tokens)技術。用戶的登錄會話由兩部分組成:access_token
和 refresh_token
。access_token
有較短的有效期,例如15分鐘,而 refresh_token
有較長的有效期,例如7天。
用戶每次發(fā)起請求時,系統(tǒng)都會檢查 access_token
的有效性。如果 access_token
過期但 refresh_token
仍然有效,系統(tǒng)會自動發(fā)起一個刷新令牌的過程,為用戶頒發(fā)新的 access_token
和 refresh_token
,從而實現(xiàn)無感知刷新。
技術實現(xiàn)
我們將使用Spring Boot框架實現(xiàn)這一功能,具體技術棧包括:
- Spring Boot 2.x
- Spring Security for Authentication
- JWT for token generation and validation
- Maven for dependency management
示例代碼
1. 引入依賴
在 pom.xml
中添加以下依賴:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> </dependencies>
2. 配置JWT工具類
創(chuàng)建一個工具類用于生成和解析JWT Token。
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.stereotype.Component; import java.util.Date; @Component public class JwtTokenUtil { private String secretKey = "secret"; // 密鑰,實際應用中應保密 public String generateAccessToken(String username) { return Jwts.builder() .setSubject(username) .setIssuer("YourApp") .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 15 * 60 * 1000)) // 15分鐘后過期 .signWith(SignatureAlgorithm.HS512, secretKey) .compact(); } public String generateRefreshToken(String username) { return Jwts.builder() .setSubject(username) .setIssuer("YourApp") .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000)) // 7天后過期 .signWith(SignatureAlgorithm.HS512, secretKey) .compact(); } public Claims getClaimsFromToken(String token) { return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token) .getBody(); } }
3. 配置Spring Security和Token驗證過濾器
創(chuàng)建一個Security配置類和一個JWT驗證過濾器,用于檢查和刷新Tokens。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { JwtTokenFilter jwtTokenFilter = new JwtTokenFilter(jwtTokenUtil, userDetailsService); http.csrf().disable() .authorizeRequests() .anyRequest().authenticated() .and() .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); } }
在這里,JwtTokenFilter
是一個自定義的過濾器,它負責每次HTTP請求時檢查和刷新 access_token
。這里我們使用 addFilterBefore
方法將 JwtTokenFilter
添加到 UsernamePasswordAuthenticationFilter
之前。這是因為我們希望在Spring Security執(zhí)行標準身份驗證之前處理JWT令牌的提取和驗證。我們通過Spring的自動裝配 (@Autowired
) 功能注入了 JwtTokenUtil
和 UserDetailsService
。
4. JwtTokenFilter 實現(xiàn)
import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 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; public class JwtTokenFilter extends OncePerRequestFilter { private JwtTokenUtil jwtTokenUtil; private UserDetailsService userDetailsService; public JwtTokenFilter(JwtTokenUtil jwtTokenUtil, UserDetailsService userDetailsService) { this.jwtTokenUtil = jwtTokenUtil; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String accessToken = request.getHeader("Authorization"); String username = null; Claims claims = null; if (accessToken != null && accessToken.startsWith("Bearer ")) { accessToken = accessToken.substring(7); try { claims = jwtTokenUtil.getClaimsFromToken(accessToken); username = claims.getSubject(); } catch (ExpiredJwtException e) { // 在這里處理 access_token 過期的情況 String refreshToken = request.getHeader("Refresh-Token"); if (refreshToken != null && jwtTokenUtil.validateToken(refreshToken)) { // 驗證 refresh_token,如果有效則重新生成 tokens username = jwtTokenUtil.getClaimsFromToken(refreshToken).getSubject(); String newAccessToken = jwtTokenUtil.generateAccessToken(username); String newRefreshToken = jwtTokenUtil.generateRefreshToken(username); // 將新的 tokens 放入響應頭 response.setHeader("Access-Token", newAccessToken); response.setHeader("Refresh-Token", newRefreshToken); } } } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(accessToken, userDetails)) { Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } chain.doFilter(request, response); } }
過濾器首先從HTTP請求的 Authorization
頭中提取 access_token
。如果令牌已過期,它將嘗試從 Refresh-Token
頭獲取 refresh_token
。如果 refresh_token
有效,過濾器將生成新的 access_token
和 refresh_token
并將它們放入HTTP響應頭中。如果從Token中解析出的用戶信息有效,過濾器將創(chuàng)建一個認證對象并將其設置到 SecurityContextHolder
中,這樣,Spring Security就可以在后續(xù)處理中使用這個認證信息。
結(jié)論
通過上述代碼,你可以在Spring Boot應用中實現(xiàn)一個基本的無感知Token刷新機制。這只是一個基礎示例,實際應用中你可能需要添加更多的錯誤處理、日志記錄以及安全措施。此外,處理和存儲 refresh_token
需要特別小心,因為它具有較長的有效期并能用于獲取新的 access_token
。
以上就是使用SpringBoot簡單實現(xiàn)無感知的刷新 Token功能的詳細內(nèi)容,更多關于SpringBoot無感知刷新Token的資料請關注腳本之家其它相關文章!
相關文章
SpringBoot上傳文件到本服務器 目錄與jar包同級問題
這篇文章主要介紹了SpringBoot上傳文件到本服務器 目錄與jar包同級問題,需要的朋友可以參考下2018-11-11如何使用Jenkins編譯并打包SpringCloud微服務目錄
這篇文章主要介紹了如何使用Jenkins編譯并打包SpringCloud微服務目錄,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-11-11Spring中@ConfigurationProperties的用法解析
這篇文章主要介紹了Spring中@ConfigurationProperties的用法解析,傳統(tǒng)的Spring一般都是基本xml配置的,后來spring3.0新增了許多java config的注解,特別是spring boot,基本都是清一色的java config,需要的朋友可以參考下2023-11-11