<p>ThinkPHP 8.0 跨域需正确响应 CORS 协议:推荐顺序为①用 topthink/think-cors 插件(生产首选),②手写 CorsMiddleware(调试/轻量),③路由级 allowCrossDomain(精准控制);均须校验 Origin、设 Access-Control-Allow-Origin 等头,禁用 * 配置 credentials。</p>

用官方插件 topthink/think-cors(推荐生产环境)
这是最规范、可维护性最强的方式,自动适配 ThinkPHP 8 生命周期,适合团队协作。
- 执行安装命令:composer require topthink/think-cors(需 PHP 8.0+)
- 安装后自动生成 config/cors.php,重点修改三项:
- paths:不要留 ['*'],改为精确路径,例如 ['api/*', 'v1/*']
- allowed_origins:若前端带 Cookie 或 Token(即 credentials: 'include'),必须写具体域名,如 ['https://admin.example.com', 'http://localhost:5173'],不能用 *
- supports_credentials:设为 true,与上面白名单配合生效
- 确认 app/middleware.php 中已注册该中间件,且位于数组**首位**:\think\middleware\AllowCrossDomain::class
手写 CorsMiddleware 中间件(推荐调试或轻量项目)
当插件不生效、需要动态校验 Origin 或想完全掌控逻辑时,这个方式更直接可靠。
- 生成中间件:php think make:middleware CorsMiddleware
- 编辑 app/middleware/CorsMiddleware.php,替换 handle() 方法为:
if ($request->isOptions()) {
return response('', 204)
->header('Access-Control-Allow-Origin', $origin)
->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');
}
$response = $next($request);
$response->header('Access-Control-Allow-Origin', $origin);
// 其他 header 同上
return $response;
- 其中 $origin 需动态获取并校验白名单,例如:
$origin = $request->header('origin');
$allowed = ['https://admin.example.com', 'http://localhost:3000'];
$origin = in_array($origin, $allowed) ? $origin : null;
不匹配时不要设 Access-Control-Allow-Origin,避免安全泄露 - 把 \app\middleware\CorsMiddleware::class 加入 app/middleware.php 的**最顶部**,确保早于日志、JWT 等中间件执行
路由级 allowCrossDomain(精准控制特定接口)
适用于只对部分 API 开放跨域,不影响后台、静态资源等其他路由,侵入性最低。
立即学习“PHP免费学习笔记(深入)”;
- 在 route/app.php 中定义路由时链式调用:
Route::get('api/user', 'api/User@index')->allowCrossDomain(['https://admin.example.com'], true); - 支持分组统一配置:
Route::group('api/v1', function () {
Route::get('users', 'api/User@index');
Route::post('login', 'api/Auth@login');
})->allowCrossDomain(['https://admin.example.com'], true); - 注意:true 表示启用凭据支持,此时 allowed_origins 必须是明确域名数组,不能含 *



















