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

如何基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 03:12:34