必须用 $response->header() 而非 header(),且需在 $next($request) 后调用;options 请求须首行拦截并返回空 204 响应;带 credentials 时 origin 必须白名单动态设置,禁用 *。

中间件里用 $response->header(),别用 header()
ThinkPHP 6/8 的响应对象是 PSR-7 兼容的,header() 函数在中间件中基本无效——它可能被后续流程覆盖,或根本没写入最终响应。必须操作 $response 实例本身。
常见错误:在 $next($request) 前调用 $response->header(),但此时 $response 还没生成;或者直接写 header('Access-Control-Allow-Origin: *'),结果上线后跨域头消失。
- 正确做法:先执行
$response = $next($request),再对返回的$response调用header()方法 - 必须确保中间件注册在
app/middleware.php数组**首位**,否则 Session、JWT 或日志中间件可能提前输出内容,触发 “headers already sent” - 若项目用了
think-swoole,header()函数完全不可用,只认$response->header()
handle() 开头必须拦截 OPTIONS 请求
浏览器对带 Authorization、Content-Type: application/json 或自定义头(如 X-Api-Key)的请求,一定会先发 OPTIONS 预检。ThinkPHP 默认不处理,直接返回 405 或空白响应,导致真实请求被静默拦截。
- 在
handle()最开头加判断:if ($request->isOptions()) { return response('', 204); } - 返回 204 状态码,不是 200;响应体必须为空,不能带 JSON 或 HTML
- 204 响应里也要带完整 CORS 头:
Access-Control-Allow-Methods和Access-Control-Allow-Headers必须覆盖前端实际发送的字段(比如前端发了X-Token,Allow-Headers就得包含它) - 别在同一个
if分支里既返回 204 又继续执行$next($request),逻辑会错乱
带 Cookie 或 Authorization 时,Access-Control-Allow-Origin 不能是 *
这是浏览器硬性限制:只要前端设置了 credentials: 'include',后端就不能返回 Access-Control-Allow-Origin: *,否则请求直接失败,连控制台都不报错。
- 必须动态读取请求头:
$origin = $request->header('origin') - 用白名单校验:
$allowedOrigins = ['https://admin.example.com', 'http://localhost:3000'],只对匹配项设置$response->header('Access-Control-Allow-Origin', $origin) - 同时必须显式设置:
$response->header('Access-Control-Allow-Credentials', 'true') - 如果 Nginx 或 Apache 层也配置了 CORS 头,务必关掉——重复的
Access-Control-Allow-Origin会导致 “multiple values” 错误
为什么中间件写了却没生效?检查这三处
最常被忽略的是执行时机和作用范围。中间件不是“写了就跑”,它依赖注册位置、路由匹配逻辑和框架生命周期。
-
app/middleware.php中的中间件只对 HTTP 请求生效,console 命令、event、queue 不走这里 - 如果用了路由分组(如
Route::group('api', ...)),且没显式挂载该中间件,那分组内的路由也不会执行它 - 某些扩展(如
think-auth)自带异常中间件,一旦抛异常,可能跳过你设的 header —— 所以$response->header()一定要放在return $next($request)之后、return $response之前,且不要包在try/catch里
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











