开发者

使用SpringBoot简单实现无感知的刷新 Token功能

目录
  • 引言
  • 场景案例
  • 技术实现
    • 示例代码
    • 1. 引入依赖
    • 2. 配置JWT工具类
    • 3. 配置Spring Security和Token验证过滤器
    • 4. JwtTokenFilter 实现
  • 结论

    引言

    实现无感知的刷新 Token 是一种提升用户体验的常用技术,可以在用户使用应用时自动更新 Token,无需用户手动干预。这种技术在需要长时间保持用户登录状态的应用中非常有用,比如在一些需要频繁访问服务器资源的WEB和移动应用。以下是使用Spring Boot实现无感知刷新Token的一个场景案例和相应的示例代码。

    场景案例

    假设我们有一个电子商务平台,用户登录后可以浏览商品、加入购物车、提交订单等。为了保持用户会话的安全,我们使用JWT(jsON Web Tokens)技术。用户的登录会话由两部分组成:Access_tokenrefresh_tokenaccess_token 有较短的有效期,例如15分钟,而 refresh_token 有较长的有效期,例如7天。

    用户每次发起请求时,系统都会检查 access_token 的有效性。如果 access_token 过期但 refresh_token 仍然有效,系统会自动发起一个刷新令牌的过程,为用户颁发新的 access_tokenrefresh_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. 配置JWTdygRkScBF工具类

    创建一个工具类用于生成和解析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) 功能注入了 JwtTokenUtilUserDetailsService

    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;http://www.devze.com
    import java.io.IOException;
    
    public class JwtTokenFilter extends OncePerRequestFilter {
        private JwtTokenUtil jwtTokenUtil;
        private UserDetailsService userDetailsService;
    
        public JwtTokenFilter(JwtTokenUtil jwtTokenUtil, UserDetailsService userDetailsSerphpvice) {
            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 = accphpessToken.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_tokenrefresh_token 并将它们放入HTTP响应头中。如果从Token中解析出的用户信息有效,过滤器将创建一个认证对象并将其设置到 SecurityContextHolder 中,这样,Spring Security就可以在后续处理中使用这个认证信息。

    结论

    通过上述代码,你可以在Spring Boot应用中实现一个基本的无感知Token刷新机制。这只是一个基础示例,实际应用中你可能需要添加更多的错误处理、日志记录以及安全措施。此外,处理和存储 refresh_token 需要特别小心,因为它具有较长的有效期并能用于获取新的 access_token

    以上就是使用SpringBoot简单实现无感知的刷新 Token功能的详细内容,更多关于SpringBoot无感知刷新Token的资料请关注编程客栈(www.devze.com)其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜