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

 更新时间:2024年09月12日 08:29:46   作者:一只爱撸猫的程序猿  
实现无感知的刷新 Token 是一种提升用户体验的常用技术,可以在用户使用应用时自动更新 Token,无需用户手动干预,这种技术在需要长时间保持用户登录状态的应用中非常有用,以下是使用Spring Boot实现无感知刷新Token的一个场景案例和相应的示例代码

引言

实现无感知的刷新 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. 配置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) 功能注入了 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;
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_tokenrefresh_token 并将它们放入HTTP响应头中。如果从Token中解析出的用户信息有效,过滤器将创建一个认证对象并将其设置到 SecurityContextHolder 中,这样,Spring Security就可以在后续处理中使用这个认证信息。

结论

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

以上就是使用SpringBoot简单实现无感知的刷新 Token功能的详细内容,更多关于SpringBoot无感知刷新Token的资料请关注脚本之家其它相关文章!

相关文章

  • SpringBoot Validation快速实现数据校验的示例代码

    SpringBoot Validation快速实现数据校验的示例代码

    在实际开发中,肯定会经常遇到对参数字段进行校验的场景,通常我们只能写大量的if else来完成校验工作,而如果使用SpringBoot Validation则可以轻松的通过注解来完成,接下来小编给大家介绍下利用SpringBoot Validation快速实现数据校验的示例代码,需要的朋友参考下吧
    2022-06-06
  • 使用spring boot开发时java对象和Json对象转换的问题

    使用spring boot开发时java对象和Json对象转换的问题

    这篇文章主要介绍了使用spring boot开发时java对象和Json对象转换的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • Java 数组分析及简单实例

    Java 数组分析及简单实例

    这篇文章主要介绍了Java 数组分析及简单实例的相关资料,在Java中它就是对象,一个比较特殊的对象,需要的朋友可以参考下
    2017-03-03
  • java yaml转properties工具类方式

    java yaml转properties工具类方式

    这篇文章主要介绍了java yaml转properties工具类方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • spring声明式事务@Transactional底层工作原理

    spring声明式事务@Transactional底层工作原理

    这篇文章主要为大家介绍分析spring声明式事务@Transactional底层工作原理,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-02-02
  • 实例讲解Java设计模式编程中如何运用代理模式

    实例讲解Java设计模式编程中如何运用代理模式

    这篇文章主要介绍了Java设计模式编程中如何运用代理模式,文中举了普通代理和强制代理的例子作为代理模式的扩展内容,需要的朋友可以参考下
    2016-02-02
  • 详解SpringBoot读取Yml配置文件的3种方法

    详解SpringBoot读取Yml配置文件的3种方法

    本文主要介绍了详解SpringBoot读取Yml配置文件的3种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • java读取zip/jar包中文件的几种方式

    java读取zip/jar包中文件的几种方式

    这篇文章主要给大家介绍了关于java读取zip/jar包中文件的几种方式,在我们日常使用中压缩文件是非常常用的,文中通过示例代码将java读取zip/jar包中文件的方法介绍的非常详细,需要的朋友可以参考下
    2023-07-07
  • idea热部署且开启自动编译的实现方法

    idea热部署且开启自动编译的实现方法

    这篇文章主要介绍了idea热部署且开启自动编译的实现方法,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • SpringBoot实现邮件任务的步骤详解

    SpringBoot实现邮件任务的步骤详解

    这篇文章主要介绍了SpringBoot实现邮件任务的步骤详解,使用Spring Boot实现QQ邮箱发送邮件具有快速集成、统一的开发体验、强大的维护和扩展能力、可靠的送达性和安全性等优势,可以帮助你快速构建稳定可靠的邮件发送功能,需要的朋友可以参考下
    2023-10-10

最新评论