需先配置Web服务器放行OPTIONS请求,再在PHP入口文件开头拦截并返回204响应及完整CORS头,禁止走业务逻辑、避免Origin空判断、不可混用*与credentials,框架中须显式注册OPTIONS路由或中间件。

PHP怎么让OPTIONS请求不报405错误
Apache/Nginx默认不把OPTIONS转给PHP处理,直接由Web服务器拦截返回405 —— 所以你写的$_SERVER['REQUEST_METHOD'] === 'OPTIONS'根本不会执行。得先让服务器放行。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- Apache:在
.htaccess或虚拟主机配置里加RewriteCond %{REQUEST_METHOD} OPTIONS+RewriteRule ^(.*)$ index.php [QSA,L] - Nginx:在
location块里加if ($request_method = 'OPTIONS') { add_header Access-Control-Allow-Origin "*"; add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE"; add_header Access-Control-Allow-Headers "Content-Type, Authorization"; add_header Access-Control-Allow-Credentials "true"; add_header Content-Length 0; add_header Content-Type text/plain; return 204; }—— 注意:这里提前响应,不走PHP - 别依赖
Allow响应头让浏览器“猜”;必须显式返回204或200,且带Access-Control-Allow-*头
PHP里怎么正确响应预检请求(OPTIONS)
预检请求是浏览器发的试探性请求,不带业务逻辑,只验证CORS策略。PHP收到后不该查数据库、不走路由、不触发鉴权中间件 —— 否则性能白耗,还可能因未登录返回401,导致整个跨域失败。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 在入口文件(如
index.php)最开头判断:if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { header('Access-Control-Allow-Origin: https://your-frontend.com'); header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With'); header('Access-Control-Allow-Credentials: true'); http_response_code(204); exit; } -
Access-Control-Allow-Origin不能写*+Access-Control-Allow-Credentials: true,否则浏览器拒绝;二者互斥 - 如果前端带
Authorization头,Access-Control-Allow-Headers必须显式列出它,不能靠*兜底(浏览器不认)
为什么$_SERVER['HTTP_ORIGIN']有时为空
不是所有OPTIONS请求都带Origin头 —— 比如本地file://打开页面、curl手动发请求、或某些调试工具会省略。但真实浏览器跨域一定会带。所以别用$_SERVER['HTTP_ORIGIN']做空判断来跳过CORS头,而是按需固定返回(或白名单校验)。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 不要写
if (!empty($_SERVER['HTTP_ORIGIN'])) { ... }来决定是否输出CORS头 —— 预检请求必须始终返回对应头,否则浏览器直接报错 - 如果需要动态Origin(比如多前端域名),用白名单数组比对:
$allowed_origins = ['https://a.com', 'https://b.net']; $origin = $_SERVER['HTTP_ORIGIN'] ?? ''; if (in_array($origin, $allowed_origins)) { header("Access-Control-Allow-Origin: $origin"); } - 注意:
HTTP_ORIGIN是$_SERVER键名,不是$_SERVER['Origin'],大小写敏感
用框架时怎么避免重复处理OPTIONS
像Laravel、ThinkPHP这类框架默认不注册OPTIONS路由,或把OPTIONS当异常处理,结果走到全局异常处理器,返回HTML错误页 —— 浏览器收不到204,预检就挂。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- Laravel:在
routes/api.php里显式加Route::options('{any}', function () { return response()->noContent(); })->where('any', '.*');,并确保CorsServiceProvider已启用且allowed_methods包含OPTIONS - ThinkPHP:在
app/middleware/Cors.php中,handle()方法开头加if ($request->isOptions()) { $response = response('', 204)->header([...]); return $response; },别让它继续往下走 - 别依赖框架的“自动CORS中间件”处理预检 —— 很多中间件只处理
Access-Control-Allow-Origin,漏掉Methods和Headers,照样失败
真正的坑不在PHP代码里,而在Web服务器配置和浏览器对CORS规范的严格执行之间。少一个header、错一个状态码、多一次数据库查询,预检就静默失败 —— 而你看到的只是控制台里一行没头没尾的“CORS error”。



















