使用SpringBoot簡單實(shí)現(xiàn)無感知的刷新 Token功能
引言
實(shí)現(xiàn)無感知的刷新 Token 是一種提升用戶體驗(yàn)的常用技術(shù),可以在用戶使用應(yīng)用時(shí)自動更新 Token,無需用戶手動干預(yù)。這種技術(shù)在需要長時(shí)間保持用戶登錄狀態(tài)的應(yīng)用中非常有用,比如在一些需要頻繁訪問服務(wù)器資源的WEB和移動應(yīng)用。以下是使用Spring Boot實(shí)現(xiàn)無感知刷新Token的一個場景案例和相應(yīng)的示例代碼。
場景案例
假設(shè)我們有一個電子商務(wù)平臺,用戶登錄后可以瀏覽商品、加入購物車、提交訂單等。為了保持用戶會話的安全,我們使用JWT(JSON Web Tokens)技術(shù)。用戶的登錄會話由兩部分組成:access_token 和 refresh_token。access_token 有較短的有效期,例如15分鐘,而 refresh_token 有較長的有效期,例如7天。
用戶每次發(fā)起請求時(shí),系統(tǒng)都會檢查 access_token 的有效性。如果 access_token 過期但 refresh_token 仍然有效,系統(tǒng)會自動發(fā)起一個刷新令牌的過程,為用戶頒發(fā)新的 access_token 和 refresh_token,從而實(shí)現(xiàn)無感知刷新。
技術(shù)實(shí)現(xiàn)
我們將使用Spring Boot框架實(shí)現(xiàn)這一功能,具體技術(shù)棧包括:
- 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"; // 密鑰,實(shí)際應(yīng)用中應(yīng)保密
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驗(yàn)證過濾器
創(chuàng)建一個Security配置類和一個JWT驗(yàn)證過濾器,用于檢查和刷新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 是一個自定義的過濾器,它負(fù)責(zé)每次HTTP請求時(shí)檢查和刷新 access_token。這里我們使用 addFilterBefore 方法將 JwtTokenFilter 添加到 UsernamePasswordAuthenticationFilter 之前。這是因?yàn)槲覀兿M赟pring Security執(zhí)行標(biāo)準(zhǔn)身份驗(yàn)證之前處理JWT令牌的提取和驗(yàn)證。我們通過Spring的自動裝配 (@Autowired) 功能注入了 JwtTokenUtil 和 UserDetailsService。
4. JwtTokenFilter 實(shí)現(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)) {
// 驗(yàn)證 refresh_token,如果有效則重新生成 tokens
username = jwtTokenUtil.getClaimsFromToken(refreshToken).getSubject();
String newAccessToken = jwtTokenUtil.generateAccessToken(username);
String newRefreshToken = jwtTokenUtil.generateRefreshToken(username);
// 將新的 tokens 放入響應(yīng)頭
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響應(yīng)頭中。如果從Token中解析出的用戶信息有效,過濾器將創(chuàng)建一個認(rèn)證對象并將其設(shè)置到 SecurityContextHolder 中,這樣,Spring Security就可以在后續(xù)處理中使用這個認(rèn)證信息。
結(jié)論
通過上述代碼,你可以在Spring Boot應(yīng)用中實(shí)現(xiàn)一個基本的無感知Token刷新機(jī)制。這只是一個基礎(chǔ)示例,實(shí)際應(yīng)用中你可能需要添加更多的錯誤處理、日志記錄以及安全措施。此外,處理和存儲 refresh_token 需要特別小心,因?yàn)樗哂休^長的有效期并能用于獲取新的 access_token。
以上就是使用SpringBoot簡單實(shí)現(xiàn)無感知的刷新 Token功能的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot無感知刷新Token的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot上傳文件到本服務(wù)器 目錄與jar包同級問題
這篇文章主要介紹了SpringBoot上傳文件到本服務(wù)器 目錄與jar包同級問題,需要的朋友可以參考下2018-11-11
Java實(shí)現(xiàn)常見排序算法的優(yōu)化
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識,文章圍繞著Java實(shí)現(xiàn)常見排序算法的優(yōu)化展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-01-01
MyBatis?在使用上的注意事項(xiàng)及其辨析(最新最全整理)
這篇文章主要介紹了MyBatis的在使用上的注意事項(xiàng)及其辨析,本文內(nèi)容比較長,是小編用心給大家整理的,圖文實(shí)例代碼相結(jié)合給大家講解的非常詳細(xì),需要的朋友參考下吧2024-06-06
如何使用Jenkins編譯并打包SpringCloud微服務(wù)目錄
這篇文章主要介紹了如何使用Jenkins編譯并打包SpringCloud微服務(wù)目錄,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
Spring中@ConfigurationProperties的用法解析
這篇文章主要介紹了Spring中@ConfigurationProperties的用法解析,傳統(tǒng)的Spring一般都是基本xml配置的,后來spring3.0新增了許多java config的注解,特別是spring boot,基本都是清一色的java config,需要的朋友可以參考下2023-11-11

