跨域且满足任一非简单条件时浏览器必发options:content-type为application/json、含authorization等自定义头、使用put/delete方法、post配非简单content-type、credentials: 'include'。

哪些请求会触发浏览器自动发 OPTIONS
只要跨域,且满足任一非简单条件,浏览器就一定会先发 OPTIONS。这不是 bug,是强制安全检查。
常见触发点包括:
-
fetch或axios发送Content-Type: application/json(哪怕只是{}) - 手动加了自定义 header,比如
Authorization、X-Request-ID - 用了
PUT、DELETE、PATCH方法 - 哪怕用
POST,但Content-Type是application/xml或text/csv - 设置了
credentials: 'include'(带 cookie)
注意:GET 和 POST 表单提交(enctype="application/x-www-form-urlencoded")通常不会触发——前提是没加任何自定义头、没改 Content-Type、没带凭证。
Spring Boot 2.4+ 怎么正确响应 OPTIONS
新版默认不处理 OPTIONS 路径,@CrossOrigin 注解只作用于实际接口路径(如 /api/user),但预检请求打的是同路径的 OPTIONS /api/user,容易 405。
推荐做法是显式注册 CorsConfigurationSource:
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Arrays.asList("http://localhost:3000", "https://myapp.com"));
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "X-Request-ID"));
config.setAllowCredentials(true); // 如果前端带 credentials
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
关键点:
- 必须显式列出
OPTIONS在setAllowedMethods中 -
setAllowedHeaders要包含前端实际用到的头,比如Authorization和Content-Type - 如果用了
Spring Security,还得在配置里加http.cors(),否则安全链会拦截OPTIONS
Nginx 层怎么透传或响应 OPTIONS
如果你用 Nginx 做反向代理,又不想改后端代码,可以在 Nginx 配置里直接拦截并返回 204。
典型写法:
location /api/ {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "$http_origin" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Request-ID" always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Max-Age "3600" always;
add_header Access-Control-Expose-Headers "X-Total-Count" always;
return 204;
}
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
注意:
- 必须用
if ($request_method = 'OPTIONS')判断,不能只靠add_header——因为OPTIONS请求可能不带Origin头,Nginx 不会自动继承 -
always参数很重要,否则某些状态码下 header 不生效 - 返回
204是最佳实践,避免 body 冗余;不要返回200+ 空 body
为什么 OPTIONS 成功了但主请求还是被拦
最常被忽略的是:服务端对 OPTIONS 返回了正确头,但真实请求的响应里缺了 Access-Control-Allow-Origin 或其他必要头。
比如你用 Nginx 拦截了 OPTIONS 并返回了所有 CORS 头,但后端接口本身没加 Access-Control-Allow-Origin,那主请求响应仍会被浏览器拒绝。
还有两个硬伤:
- 前端带
credentials: 'include',但服务端用了通配符*——必须写具体域名 - 前端读取了自定义响应头(如
X-Total-Count),但服务端没设Access-Control-Expose-Headers
查问题时,别只盯着 Network 面板里的 OPTIONS 成功与否,一定要点开主请求的 Response Headers,确认每个 CORS 相关头都存在且值合法。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











