最稳方案是自定义中间件并显式响应options预检请求;因浏览器在post或带authorization请求前必发options,若后端未返回204状态码及完整cors头,请求即被拦截且仅报“cors error”。

ThinkPHP 5 默认不处理跨域,必须手动配置中间件或控制器头,否则浏览器直接拦截;最稳的方案是自定义中间件并显式响应 OPTIONS 预检请求。
为什么加了 header() 还是被拦截?
常见错误是只设了 Access-Control-Allow-Origin,但没处理预检(OPTIONS)请求。浏览器在发 POST/带 Authorization 的请求前,会先发一个 OPTIONS 请求——如果后端没返回 204 状态码和完整 CORS 头,整个请求就卡死,控制台只显示“CORS error”,不报具体原因。
- TP5 中间件的
handle()方法里,$next($request)之后再设 header 是无效的:响应体已生成,header 被丢弃 - 用
header()函数设头时,必须在任何输出之前调用,否则触发 “headers already sent” 错误 - 若前端带
credentials: true(比如传 Cookie),Access-Control-Allow-Origin不能为*,必须动态读取$request->header('origin')并白名单校验
CorsMiddleware 怎么写才真正生效?
中间件必须在 handle() 开头判断是否为 OPTIONS 请求,并立即返回空响应;所有 CORS 头都要在返回前写入,不能依赖 $next() 后的 response 对象。
- 执行
php think make:middleware CorsMiddleware生成类 - 编辑
app/middleware/CorsMiddleware.php,核心逻辑如下:
public function handle($request, \Closure $next)
{
// 允许的源,生产环境务必替换为具体域名
$allowedOrigins = ['https://admin.example.com', 'http://localhost:3000'];
$origin = $request->header('origin');
if (in_array($origin, $allowedOrigins)) {
header('Access-Control-Allow-Origin: ' . $origin);
header('Access-Control-Allow-Credentials: true');
} else {
header('Access-Control-Allow-Origin: *');
}
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, X-CSRF-TOKEN');
if ($request->isOptions()) {
return response('', 204);
}
return $next($request);
}
- 注意:
header()必须在return response('', 204)前调用,否则 OPTIONS 响应不带 CORS 头 - 不要用
$response->header(),TP5 的 Response 对象在$next()后才构建,此时 header 已失效
中间件注册位置和顺序为什么关键?
TP5 全局中间件注册在 application/middleware.php(不是 app/middleware.php),且必须放在数组最前面,否则可能被后续中间件覆盖或跳过。
- 确认路径是
application/middleware.php,TP5 默认没有app/目录层级 - 注册写法必须是完整命名空间:
\app\middleware\CorsMiddleware::class,不能简写为'CorsMiddleware'或CorsMiddleware::class - 如果用了多模块(如 admin、api),检查是否改对了当前模块的
middleware.php - 中间件数组顺序即执行顺序,CORS 中间件必须早于 Auth、Log 等业务中间件
什么时候该放弃中间件,改用 Nginx/Apache?
当项目已上线、接口量大、或需要统一管控多个子应用时,直接在 Web 服务器层加 CORS 头更可靠,也避免 PHP 层重复判断 origin。
- Nginx 配置示例(加在 server 块内):
location / {
add_header 'Access-Control-Allow-Origin' 'https://admin.example.com';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With, X-CSRF-TOKEN';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
}
- Apache 需启用
mod_headers和mod_rewrite,并在.htaccess或虚拟主机配置中添加Header set指令 - 注意:Web 服务器配置不会执行 PHP 白名单逻辑,
Access-Control-Allow-Origin若需动态匹配 origin,仍得走 PHP 中间件
真正容易被忽略的是 OPTIONS 请求的响应时机——它必须在框架路由解析前就结束,不能等 Controller 初始化完再处理。很多“加了中间件还是不行”的问题,根源都在这里。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











