如何基于Redis实现Spring Boot Security JWT的登录与登出功能
基于Redis实现Spring Boot Security JWT登出(Token黑名单)方案
1. 实现Redis黑名单操作服务
封装Redis操作逻辑,用于管理黑名单Token,自动同步Token的过期时间,避免手动清理无效数据:
@Service public class TokenBlacklistService { private final RedisTemplate<String, String> redisTemplate; // 构造注入RedisTemplate(确保你的Redis配置已正确初始化该Bean) public TokenBlacklistService(RedisTemplate<String, String> redisTemplate) { this.redisTemplate = redisTemplate; } // 将Token加入黑名单,设置与Token剩余有效期一致的过期时间 public void addToBlacklist(String token, long remainingExpireSeconds) { redisTemplate.opsForValue() .set(token, "blacklisted", remainingExpireSeconds, TimeUnit.SECONDS); } // 校验Token是否在黑名单中 public boolean isTokenBlacklisted(String token) { return Boolean.TRUE.equals(redisTemplate.hasKey(token)); } }
2. 编写登出接口
通过REST接口接收前端传递的Token,解析其剩余有效期后加入黑名单:
@RestController @RequestMapping("/auth") public class AuthController { private final TokenBlacklistService blacklistService; private final JwtTokenUtil jwtTokenUtil; // 你已实现的JWT工具类 public AuthController(TokenBlacklistService blacklistService, JwtTokenUtil jwtTokenUtil) { this.blacklistService = blacklistService; this.jwtTokenUtil = jwtTokenUtil; } @PostMapping("/logout") public ResponseEntity<Void> logout(@RequestHeader("Authorization") String authHeader) { // 提取Bearer Token(可补充格式错误的异常处理) String token = authHeader.replaceFirst("Bearer ", ""); // 获取Token剩余有效秒数 long remainingExpire = jwtTokenUtil.getRemainingExpirationSeconds(token); // 加入黑名单 blacklistService.addToBlacklist(token, remainingExpire); return ResponseEntity.ok().build(); } }
在你的JwtTokenUtil中补充获取剩余有效期的方法:
public long getRemainingExpirationSeconds(String token) { Claims claims = Jwts.parserBuilder() .setSigningKey(yourSecretKey) // 替换为你的JWT签名密钥 .build() .parseClaimsJws(token) .getBody(); long expireTimestamp = claims.getExpiration().getTime(); return (expireTimestamp - System.currentTimeMillis()) / 1000; }
3. 扩展JWT过滤器,加入黑名单校验
修改你已有的JWT认证过滤器,在验证Token有效性之前,先检查是否在黑名单中:
@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenUtil jwtTokenUtil; private final TokenBlacklistService blacklistService; private final UserDetailsService userDetailsService; public JwtAuthenticationFilter(JwtTokenUtil jwtTokenUtil, TokenBlacklistService blacklistService, UserDetailsService userDetailsService) { this.jwtTokenUtil = jwtTokenUtil; this.blacklistService = blacklistService; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.replaceFirst("Bearer ", ""); // 第一步:检查Token是否在黑名单中 if (blacklistService.isTokenBlacklisted(token)) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token已被注销"); return; } // 原有JWT认证逻辑(保持你之前的实现) String username = jwtTokenUtil.extractUsername(token); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(token, userDetails)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } } filterChain.doFilter(request, response); } }
4. 更新Spring Security配置
确保自定义的JWT过滤器被正确加入Security链:
@Configuration @EnableWebSecurity public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthFilter; private final AuthenticationEntryPoint unauthorizedEntryPoint; // 你已实现的未授权处理器 public SecurityConfig(JwtAuthenticationFilter jwtAuthFilter, AuthenticationEntryPoint unauthorizedEntryPoint) { this.jwtAuthFilter = jwtAuthFilter; this.unauthorizedEntryPoint = unauthorizedEntryPoint; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf().disable() .exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint) .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeHttpRequests(auth -> auth .requestMatchers("/auth/login", "/auth/logout").permitAll() .anyRequest().authenticated() ); // 将JWT过滤器添加到UsernamePasswordAuthenticationFilter之前 http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }
关键注意事项
- Redis过期时间同步:必须保证黑名单Token的Redis过期时间与JWT本身的剩余有效期一致,避免Redis存储无效数据。
- 前端配合:登出成功后,前端需立即清除本地存储的Token,避免重复发送已失效的Token请求。
- Refresh Token处理:如果你的系统使用Refresh Token,登出时需同时将Refresh Token加入黑名单,逻辑与Access Token一致。
内容的提问来源于stack exchange,提问作者Obert
相关产品推荐
相关产品推荐

