ThinkPHP跨域失败主因是响应头被框架重置、OPTIONS预检未处理或credentials与*冲突;手动header()无效因框架响应生命周期覆盖,须用$response->header()或中间件统一拦截OPTIONS并动态设置白名单Origin。

ThinkPHP 跨域失败,八成不是没设头,而是头被框架重置、预检请求没响应、或 credentials 和 * 冲突了。
为什么 header('Access-Control-Allow-Origin: *') 在控制器里写了也没用
ThinkPHP 的响应对象(response() 或 JSON 返回)会在最终输出前重置所有手动写的 header()。哪怕你写在控制器第一行,只要后续调用了 return json(...) 或 return view(...),那些头大概率就丢了。
- 绝对不要在控制器方法里用原生
header()函数 —— 它和 ThinkPHP 响应生命周期不兼容 - 如果硬要用,必须确保:没有输出、没开启 session、没调用任何框架响应构造函数,且在
exit前完成 - 更稳妥的做法是改用
$response->header(),它作用于框架的响应实例,不会被覆盖
OPTIONS 预检请求返回 405 或静默失败怎么办
浏览器对带 Content-Type: application/json、Authorization 或非 GET/POST 方法的请求,会先发一个 OPTIONS 请求。ThinkPHP 默认不处理它,直接进路由匹配 —— 若没定义 Route::options(),就 404;若匹配到不支持 OPTIONS 的控制器方法,就 405。
- 必须在中间件中拦截:
if ($request->isOptions()) { return response('', 204); } -
204比200更合适:无响应体,避免 Content-Length 冲突 - 这个判断一定要放在中间件
handle()最开头,否则可能被后续逻辑跳过 - 别依赖路由级
Route::options()—— 它只管显式声明的路径,漏掉动态路由或 RESTful 资源时照样崩
带 Cookie 的跨域请求始终被拒绝
前端设了 credentials: 'include',后端却还用 Access-Control-Allow-Origin: *,浏览器会直接拦掉,连请求都不发。
立即学习“PHP免费学习笔记(深入)”;
-
Access-Control-Allow-Credentials: true和Access-Control-Allow-Origin: *是互斥的,不能共存 - 必须动态读取
$request->header('origin'),白名单校验后再写具体域名,例如:'https://admin.example.com' - 白名单不能用逗号分隔字符串,得用数组
in_array($origin, $whitelist)判断 - 记得同时设置
Access-Control-Allow-Credentials: true,且只在匹配成功时设
用 think-cors 扩展时常见配置陷阱
装了 topthink/think-cors 却还是跨不过去,问题往往出在配置没生效或中间件注册顺序不对。
- 配置文件
config/cors.php中的allow_origin设为['*']时,allow_credentials必须为false,否则扩展内部会跳过写头 - 中间件注册必须加到
app/middleware.php的全局中间件数组**首位**,否则可能被 JWT、日志等中间件干扰 - 扩展默认不处理 OPTIONS,得手动在中间件里补上
if ($request->isOptions()) return response('', 204); - 若用的是 ThinkPHP 8,确认扩展版本兼容 —— 有些老版
think-cors不适配 TP8 的中间件签名
真正卡住人的从来不是“怎么加头”,而是头加在哪、什么时候加、加给谁看 —— 尤其是 OPTIONS 那一瞬,错一步,整个请求链就断了。



















