直接用topthink/think-cors插件90%场景可行,但遇cookie、自定义header或localhost开发时易失效;根本原因在于默认路由匹配限制、响应头被重置、*与credentials冲突及浏览器预检/凭证策略不匹配。

直接用 topthink/think-cors 插件配置,90% 的场景能跑通;但如果你用了 Cookie、带自定义 Header(比如 Authorization 或 X-Token),或者前端是 localhost:3000 这类开发地址,它大概率会失效——不是插件有问题,而是默认配置和浏览器策略不匹配。
为什么 config/cors.php 配了还是跨域失败
常见错误现象:控制台报错 Access to XMLHttpRequest at ... has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present,但你明明改了 config/cors.php 里的 allowed_origins。
-
topthink/think-cors默认只对paths配置里匹配的路由生效,初始值是['api/*'],如果你接口路径是/v1/user或根路径/login,它根本不会触发中间件 - 哪怕路径匹配,如果控制器里调用了
response()->json()或return json(...),ThinkPHP 8.0+ 的响应对象会重置 header,导致你配的 CORS 头被丢弃 -
allowed_origins设为['*']时,supports_credentials必须为false,否则浏览器直接拒绝——但你又需要 Cookie,这就矛盾了
手动写中间件比插件更可控
推荐在 app/middleware/Cors.php 中手写逻辑,关键点是“预检拦截 + 响应头注入 + 不干扰业务体”:
- 对
$request->isOptions()请求,立刻返回response('', 204),不走后续逻辑,避免框架覆盖 header - 对普通请求,在
$next($request)之后再调用$response->header(),确保 header 写在最终响应上 - 如果前端带 Cookie,
Access-Control-Allow-Origin必须动态读取$request->header('origin'),且不能是*;同时必须加Access-Control-Allow-Credentials: true - 允许的 Header 列表要包含前端实际发的字段,比如 axios 默认带
Authorization,但插件默认没开,就会卡在预检
示例关键行:
public function handle(Request $request, Closure $next)
{
if ($request->isOptions()) {
return response('', 204)
->header('Access-Control-Allow-Origin', $request->header('origin') ?: '*')
->header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS')
->header('Access-Control-Allow-Headers', 'Authorization,Content-Type,X-Requested-With,X-Token')
->header('Access-Control-Allow-Credentials', 'true');
}
$response = $next($request);
return $response->header([
'Access-Control-Allow-Origin' => $request->header('origin') ?: '*',
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Allow-Methods' => 'GET,POST,PUT,DELETE,OPTIONS',
'Access-Control-Allow-Headers' => 'Authorization,Content-Type,X-Requested-With,X-Token',
]);
}
开发环境用 localhost 时 Cookie 总是不发送
这不是 ThinkPHP 的问题,是浏览器限制:domain 设为 .example.com 时,localhost 不被识别为子域,Cookie 根本不会存也不会发。
- 别在
config/cookie.php里硬写'domain' => '.example.com',开发时改成''(空字符串)或注释掉 - 前端发请求必须显式声明凭证,fetch 要加
credentials: 'include',axios 要设withCredentials: true - 后端
Access-Control-Allow-Origin不能是*,得和前端 origin 完全一致,比如http://localhost:3000,否则浏览器禁止附带 Cookie - HTTPS 环境下,
secure必须为true,但本地开发 HTTP 时得关掉,否则 Cookie 不写入
用内置 AllowCrossDomain 类要注意什么
ThinkPHP 8.0 自带 \think\middleware\AllowCrossDomain::class,但它默认不支持 Authorization 和 X-Token 等常用 Header,源码里 Access-Control-Allow-Headers 固定写死为 X-Requested-With, Content-Type。
- 如果直接注册这个类,前端带 token 就会报错:
Request header field token is not allowed by Access-Control-Allow-Headers in preflight response - 可以继承它并重写
handle()方法,或干脆不用,自己写一个更轻量的中间件 - 它的
supports_credentials默认是false,要支持 Cookie 得手动传参或改配置,但不如手写清晰
真正难的不是加几行 header,而是理解浏览器什么时候发 OPTIONS、什么时候拒绝凭证、什么时候忽略你写的 header——这些细节一旦漏掉一个,整个跨域链就断了。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











