You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在基于OAuth的Spring Boot应用中设置闲置会话超时

嘿,这个需求太常见了——固定的Token过期时间要么不够灵活(太短导致用户频繁登录,太长又有安全风险),闲置超时才是更贴合用户体验的方案。下面分两种最常用的OAuth2场景给你拆解实现方式:

1. 基于存储的令牌(Redis/JDBC 存储令牌)

如果你的OAuth2是把Access Token和Refresh Token存在后端存储(比如Redis)里,而不是用无状态的JWT,那实现闲置超时会更直接,因为我们可以跟踪每个令牌的最后活跃时间。

实现步骤:

  • 步骤1:记录令牌的活跃时间
    在令牌生成时,把令牌的最后活跃时间存入存储;同时在每次用户用令牌发起请求时,更新这个时间。我们可以用一个拦截器来做这件事:

    @Component
    public class TokenActivityInterceptor implements HandlerInterceptor {
        @Autowired
        private StringRedisTemplate redisTemplate;
        @Value("${security.oauth2.idle-timeout:1800}") // 30分钟,单位秒
        private long idleTimeoutSeconds;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String token = extractTokenFromHeader(request);
            if (token != null) {
                String tokenKey = "oauth:active:" + token;
                // 更新最后活跃时间
                redisTemplate.opsForHash().put(tokenKey, "lastActive", String.valueOf(System.currentTimeMillis()));
                // 重置令牌的闲置超时时间
                redisTemplate.expire(tokenKey, idleTimeoutSeconds, TimeUnit.SECONDS);
            }
            return true;
        }
    
        private String extractTokenFromHeader(HttpServletRequest request) {
            String authHeader = request.getHeader("Authorization");
            if (StringUtils.hasText(authHeader) && authHeader.startsWith("Bearer ")) {
                return authHeader.substring(7);
            }
            return null;
        }
    }
    

    别忘了把拦截器注册到Spring MVC里:

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
        @Autowired
        private TokenActivityInterceptor activityInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(activityInterceptor)
                    .addPathPatterns("/api/**"); // 只拦截你的业务接口
        }
    }
    
  • 步骤2:校验令牌时检查闲置时间
    自定义TokenServices,在读取令牌的时候,判断当前时间和最后活跃时间的间隔是否超过闲置阈值,如果超过就作废令牌:

    @Service
    public class CustomTokenServices extends DefaultTokenServices {
        @Autowired
        private StringRedisTemplate redisTemplate;
        @Value("${security.oauth2.idle-timeout:1800}")
        private long idleTimeoutSeconds;
    
        @Override
        public OAuth2AccessToken readAccessToken(String tokenValue) {
            OAuth2AccessToken token = super.readAccessToken(tokenValue);
            if (token != null && !token.isExpired()) {
                String tokenKey = "oauth:active:" + tokenValue;
                String lastActiveStr = (String) redisTemplate.opsForHash().get(tokenKey, "lastActive");
                if (lastActiveStr != null) {
                    long lastActive = Long.parseLong(lastActiveStr);
                    long idleDuration = (System.currentTimeMillis() - lastActive) / 1000;
                    if (idleDuration > idleTimeoutSeconds) {
                        // 闲置超时,作废令牌
                        revokeToken(tokenValue);
                        throw new InvalidTokenException("Token has been idle for too long");
                    }
                }
            }
            return token;
        }
    }
    

2. 无状态JWT令牌场景

JWT本身是无状态的,令牌里的过期时间是硬编码的,没法直接修改。这种情况下我们需要结合Refresh Token和后端存储来实现闲置超时:

核心思路:

  • 把Access Token的有效期设短一点(比如15分钟),Refresh Token设长一点,但把Refresh Token存在后端(Redis)并跟踪它的最后活跃时间。
  • 用户每次操作时,前端自动用Refresh Token刷新Access Token(可以在Access Token快过期时触发),后端刷新时更新Refresh Token的活跃时间。
  • 如果用户长时间不操作,Refresh Token的闲置时间超过阈值,后端就拒绝刷新请求,强制用户重新登录。

关键代码实现:

自定义TokenServices来处理Refresh Token的闲置校验:

@Service
public class CustomJwtTokenServices extends DefaultTokenServices {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Value("${security.oauth2.refresh-idle-timeout:3600}") // 1小时闲置超时
    private long refreshIdleTimeoutSeconds;

    @Override
    public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest) throws AuthenticationException {
        // 先检查Refresh Token的闲置状态
        String refreshKey = "oauth:refresh:active:" + refreshTokenValue;
        String lastActiveStr = (String) redisTemplate.opsForHash().get(refreshKey, "lastActive");
        
        if (lastActiveStr != null) {
            long lastActive = Long.parseLong(lastActiveStr);
            long idleDuration = (System.currentTimeMillis() - lastActive) / 1000;
            if (idleDuration > refreshIdleTimeoutSeconds) {
                revokeToken(refreshTokenValue);
                throw new InvalidTokenException("Refresh token has been idle for too long");
            }
        }

        // 更新Refresh Token的活跃时间并重置过期时间
        redisTemplate.opsForHash().put(refreshKey, "lastActive", String.valueOf(System.currentTimeMillis()));
        redisTemplate.expire(refreshKey, refreshIdleTimeoutSeconds + 3600, TimeUnit.SECONDS); // 总有效期比闲置超时久一点

        return super.refreshAccessToken(refreshTokenValue, tokenRequest);
    }
}

额外注意事项

  • 闲置超时和固定过期时间要配合:比如闲置超时设30分钟,固定过期时间设2小时,这样即使用户一直操作,2小时后还是需要重新登录,兼顾安全和体验。
  • 前端要做好异常处理:收到401响应时,自动跳转到登录页。
  • 分布式系统下,一定要用共享存储(比如Redis)来存令牌的活跃状态,不然不同节点的状态会不一致。

内容的提问来源于stack exchange,提问作者Ramesh Naidu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:50:41