Spring Security拦截请求致403:实现登出功能后异常
问题诊断与解决方案
核心问题分析
你遇到的403问题大概率和过滤器配置错误以及JWT验证逻辑缺失有关:
JWTTokenGeneratorFilter的命名和配置位置存疑:如果这个过滤器负责生成JWT令牌,不应该被放在UsernamePasswordAuthenticationFilter之前,这会导致请求还未完成认证就执行生成token的逻辑,无法正确设置SecurityContext,进而所有非白名单请求返回403。- 缺少JWT认证过滤器:无状态架构下,需要一个过滤器负责解析请求头中的JWT,验证其有效性(包括数据库中的revoked/expired状态),并将认证信息注入SecurityContext,否则Spring Security无法识别已登录用户。
- 登出后的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
相关产品推荐
相关产品推荐

