错误分析
从日志可以看出:
Securing OPTIONS /api/stores/address/provinces
Set SecurityContextHolder to anonymous SecurityContext
Pre-authenticated entry point called. Rejecting access
问题:Spring Security 将 OPTIONS 请求视为需要认证的请求,但预检请求是匿名的,所以被拒绝了。
解决方案
1. 在 Security 配置中允许 OPTIONS 请求
创建或修改 Spring Security 配置类:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authz -> authz
// 允许所有 OPTIONS 请求
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 允许其他公开端点
.requestMatchers("/api/auth/**").permitAll()
// 其他请求需要认证
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
2. 如果使用的是旧版本的 Spring Security
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.authorizeRequests()
// 允许所有 OPTIONS 请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
3. 确保 JWT Filter 跳过 OPTIONS 请求
在你的 JwtAuthenticationFilter
中:
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
// 跳过 OPTIONS 预检请求
if ("OPTIONS".equals(request.getMethod())) {
return true;
}
String requestPath = request.getServletPath();
boolean result = Arrays.stream(WhiteList.whiteList).anyMatch(path -> {
if (path.endsWith("/**")) {
String basePath = path.substring(0, path.length() - 3);
return requestPath.startsWith(basePath);
}
return path.equals(requestPath);
});
return result;
}
4. 确保 CORS 配置
@RestController
@CrossOrigin(
origins = "http://localhost:5173",
allowCredentials = "true",
allowedHeaders = {"Authorization", "Content-Type"},
methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE, RequestMethod.OPTIONS}
)
@RequestMapping("/api/stores")
public class StoreController {
// ... 控制器方法
}
或者 在WebMvcConfigurer 全局配置
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins(allowedOrigins.split(",")) // 使用配置文件中的值
.allowedMethods(allowedMethods.split(","))
.allowedHeaders(allowedHeaders)
.allowCredentials(allowCredentials)
.maxAge(maxAge);
}
关键配置要点
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
- 这是最重要的配置- CORS 配置必须在 Security 配置中启用 - 使用
.cors()
- JWT Filter 必须跳过 OPTIONS 请求
- 确保
allowedMethods
包含OPTIONS
,特别要注意这个坑爹的OPTIONS
方法
验证方法
配置完成后,重启应用,你应该看到:
- OPTIONS 请求返回 200 状态码
- 后续的实际请求能正常携带 Authorization 头
- 不再出现 403 Forbidden 错误
这样配置后,CORS 预检请求就能正常通过,实际的业务请求也能正常进行 JWT 认证。
评论