使用SpringBoot简单实现无感知的刷新 Token功能
作者:一只爱撸猫的程序猿
引言
实现无感知的刷新 Token 是一种提升用户体验的常用技术,可以在用户使用应用时自动更新 Token,无需用户手动干预。这种技术在需要长时间保持用户登录状态的应用中非常有用,比如在一些需要频繁访问服务器资源的WEB和移动应用。以下是使用Spring Boot实现无感知刷新Token的一个场景案例和相应的示例代码。
场景案例
假设我们有一个电子商务平台,用户登录后可以浏览商品、加入购物车、提交订单等。为了保持用户会话的安全,我们使用JWT(JSON Web Tokens)技术。用户的登录会话由两部分组成:access_token
和 refresh_token
。access_token
有较短的有效期,例如15分钟,而 refresh_token
有较长的有效期,例如7天。
用户每次发起请求时,系统都会检查 access_token
的有效性。如果 access_token
过期但 refresh_token
仍然有效,系统会自动发起一个刷新令牌的过程,为用户颁发新的 access_token
和 refresh_token
,从而实现无感知刷新。
技术实现
我们将使用Spring Boot框架实现这一功能,具体技术栈包括:
- 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工具类
创建一个工具类用于生成和解析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验证过滤器
创建一个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执行标准身份验证之前处理JWT令牌的提取和验证。我们通过Spring的自动装配 (@Autowired
) 功能注入了 JwtTokenUtil
和 UserDetailsService
。
4. JwtTokenFilter 实现
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中解析出的用户信息有效,过滤器将创建一个认证对象并将其设置到 SecurityContextHolder
中,这样,Spring Security就可以在后续处理中使用这个认证信息。
结论
通过上述代码,你可以在Spring Boot应用中实现一个基本的无感知Token刷新机制。这只是一个基础示例,实际应用中你可能需要添加更多的错误处理、日志记录以及安全措施。此外,处理和存储 refresh_token
需要特别小心,因为它具有较长的有效期并能用于获取新的 access_token
。
以上就是使用SpringBoot简单实现无感知的刷新 Token功能的详细内容,更多关于SpringBoot无感知刷新Token的资料请关注脚本之家其它相关文章!