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

Spring Security拦截请求致403:实现登出功能后异常

问题诊断与解决方案

核心问题分析

你遇到的403问题大概率和过滤器配置错误以及JWT验证逻辑缺失有关:

  1. JWTTokenGeneratorFilter的命名和配置位置存疑:如果这个过滤器负责生成JWT令牌,不应该被放在UsernamePasswordAuthenticationFilter之前,这会导致请求还未完成认证就执行生成token的逻辑,无法正确设置SecurityContext,进而所有非白名单请求返回403。
  2. 缺少JWT认证过滤器:无状态架构下,需要一个过滤器负责解析请求头中的JWT,验证其有效性(包括数据库中的revoked/expired状态),并将认证信息注入SecurityContext,否则Spring Security无法识别已登录用户。
  3. 登出后的SecurityContext清理逻辑冗余:因为是STATELESS会话模式,SecurityContextHolder.clearContext()不会对后续请求产生影响,反而可能干扰正常流程。

分步修复方案

1. 修正安全配置中的过滤器逻辑

移除错误的JWTTokenGeneratorFilter配置,替换为JWT认证过滤器:

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class BookStoreSecurityConfig {
    private final ProjectAuthenticationProvider authenticationProvider;
    // 替换为JWT认证过滤器
    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    private final LogoutHandler logoutHandler;

    @Bean
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception{
        http.csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(requests -> requests
                        .requestMatchers("/api/v1/auth/**").permitAll()
                        .anyRequest().authenticated()
                )
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                .authenticationProvider(authenticationProvider)
                // 添加JWT认证过滤器,放在UsernamePasswordAuthenticationFilter之前
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .logout(l -> l
                        .logoutUrl("/api/v1/auth/logout")
                        .addLogoutHandler(logoutHandler)
                        // 移除冗余的SecurityContext清理,STATELESS模式下无需手动处理
                        .logoutSuccessHandler((request, response, authentication) -> 
                                response.setStatus(HttpServletResponse.SC_OK)
                        )
                );
        return http.build();
    }

    @Bean
    public AuthenticationManager authManager(HttpSecurity http) throws Exception{
        AuthenticationManagerBuilder authenticationManagerBuilder
                = http.getSharedObject(AuthenticationManagerBuilder.class);
        authenticationManagerBuilder.authenticationProvider(authenticationProvider);
        return authenticationManagerBuilder.build();
    }
}

2. 实现JWT认证过滤器

创建JwtAuthenticationFilter,负责解析并验证JWT,同时检查数据库中的token状态:

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JWTService jwtService;
    private final UserService userService;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
        final String authHeader = request.getHeader("Authorization");
        final String jwt;
        final String userEmail;

        // 跳过登录/登出等白名单接口
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        jwt = authHeader.substring(7);
        // 从JWT中解析用户邮箱
        userEmail = jwtService.extractUsername(jwt);

        // 如果用户未被认证,执行验证逻辑
        if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            Users user = userService.getByUserEmail(userEmail).orElseThrow();
            
            // 检查JWT是否有效,同时检查数据库中的token状态(未撤销、未过期)
            if (jwtService.isTokenValid(jwt, user) && jwtService.isTokenNotRevoked(jwt)) {
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                        user,
                        null,
                        // 如果有角色权限,这里传入用户的权限集合
                        Collections.emptyList()
                );
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }
        filterChain.doFilter(request, response);
    }
}

3. 完善JWTService中的验证方法

在JWTService中添加检查token是否被撤销的方法:

@Service
@RequiredArgsConstructor
public class JWTService {
    // 其他已有方法...

    // 检查token是否未被撤销且未过期
    public boolean isTokenNotRevoked(String jwt) {
        return getByToken(jwt)
                .map(token -> !token.isExpired() && !token.isRevoked())
                .orElse(false);
    }

    // 确保isTokenValid方法验证JWT签名和过期时间
    public boolean isTokenValid(String jwt, Users user) {
        final String username = extractUsername(jwt);
        return (username.equals(user.getEmail())) && !isTokenExpired(jwt);
    }
}

4. 优化登出服务逻辑

保留核心逻辑的同时,添加明确的状态码返回:

@Service
@RequiredArgsConstructor
public class LogoutService implements LogoutHandler {
    private final JWTService jwtService;

    @Override
    public void logout(
            HttpServletRequest request,
            HttpServletResponse response,
            Authentication authentication) {
        final String authHeader = request.getHeader("Authorization");
        final String jwt;

        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        jwt = authHeader.substring(7);
        var storedToken = jwtService.getByToken(jwt).orElse(null);

        if (storedToken != null) {
            storedToken.setExpired(true);
            storedToken.setRevoked(true);
            jwtService.save(storedToken);
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        }
    }
}

额外检查点

  • 确认ProjectAuthenticationProvider的authenticate方法逻辑正确,能够正确验证用户凭证。
  • 登录时的jwtService.revokeAllUserTokens(user)方法确保用户旧令牌全部失效,避免多端登录冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 15:12:10