Spring Security登出端点无法从前端访问:跨域问题排查
问题
使用Java 17、Spring Boot 3.0.5和Angular 15开发项目,已配置CORS与安全过滤器,但前端(localhost:4200)调用/auth/api/logout登出接口时出现跨域错误,其他接口均可正常访问。相关配置如下:
CORS配置类
@Configuration @EnableWebMvc @RequiredArgsConstructor public class CorsConfig implements WebMvcConfigurer { private final WebConfigPropertiesProvider propertiesProvider; @Override public void addCorsMappings(@Nonnull CorsRegistry registry) { registry.addMapping(propertiesProvider.getConfig().mapping()) .allowedOriginPatterns(propertiesProvider.getConfig().allowedCorsOrigins()) .allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS") .allowCredentials(true); } }
配置属性(已正确加载)
spring.security.web.config.allowed-cors-origins=http://localhost* spring.security.web.config.mapping=/**
安全过滤器链配置
@Configuration @EnableWebSecurity @RequiredArgsConstructor class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final AuthenticationProvider authenticationProvider; private final LogoutService logoutService; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.cors().and().csrf().disable().authorizeHttpRequests() .requestMatchers("/auth/api/**").permitAll() .anyRequest().authenticated(). and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authenticationProvider(authenticationProvider) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)).and() .logout().logoutUrl("/auth/api/logout").addLogoutHandler(logoutService) .logoutSuccessHandler((request, response, authentication) -> SecurityContextHolder.clearContext()); return http.build(); } }
登出服务实现
@Service @RequiredArgsConstructor public class LogoutService implements LogoutHandler { private final TokenRepository tokenRepository; @Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { final var authHeader = request.getHeader("Authorization"); final String jwt; if (authHeader == null || !authHeader.startsWith("Bearer ")) { return; } jwt = authHeader.substring(7); tokenRepository.findByToken(jwt). ifPresent(storedToken -> tokenRepository.deleteTokenByToken(storedToken.getToken())); } }
可能原因分析
Spring Security的CORS配置优先级冲突
Spring Security的http.cors()默认优先使用自身的CORS配置逻辑,而非WebMvcConfigurer中定义的规则。你当前通过WebMvcConfigurer实现的CORS配置,可能没有被Spring Security的过滤器链正确应用到/auth/api/logout接口,导致该接口的跨域请求被拦截。Logout接口的请求方法不匹配
Spring Security的logout()默认仅支持GET请求,但前端调用登出接口时可能使用了POST方法。即使CORS配置允许POST方法,若Spring Security的logout端点未配置接受POST请求,会导致实际请求或预检请求被拦截,触发跨域错误。预检OPTIONS请求被过滤器拦截
虽然CORS配置允许OPTIONS方法,但JwtAuthenticationFilter可能拦截了OPTIONS请求。OPTIONS预检请求不需要携带Authorization Token,过滤器会因Token缺失返回未授权,浏览器会将这类错误判定为跨域问题。AllowedOriginPatterns匹配不精确
配置中的http://localhost*模式可能无法正确匹配http://localhost:4200。Spring Boot 3对allowedOriginPatterns的匹配规则有严格要求,模糊的localhost*可能无法覆盖带端口的localhost地址,导致跨域校验失败。
内容的提问来源于stack exchange,提问作者Binary

