CodeIgniter 4 必须用过滤器统一注入 CORS 响应头,否则 OPTIONS 预检请求因未执行控制器逻辑而失败;需在 Filters.php 注册 CorsFilter,前置处理并校验 Origin 白名单,禁用 * 与 credentials 共存。

直接结论:CodeIgniter 4 必须用过滤器(Filter)统一注入 CORS 响应头,不能只在控制器里写 header() 或 $this->output->set_header() —— 否则 OPTIONS 预检请求会失败,前端仍报跨域错误。
为什么 header("Access-Control-Allow-Origin: *") 在控制器里无效
浏览器发起跨域请求前,会先发一个 OPTIONS 请求做预检。这个请求不经过你的控制器逻辑,而是由框架路由层直接拦截处理。如果你只在某个控制器方法里加 header(),OPTIONS 请求根本不会执行那行代码,响应体为空、响应头缺失,前端就卡死在这里。
- 常见现象:
Failed to load http://api.example.com/v1/users: Response to preflight request doesn't pass access control check - 本质原因:
OPTIONS是 HTTP 方法,不是你定义的任意路由,它需要在请求进入控制器前就被响应 - CI4 的
Filters是唯一能覆盖所有请求(含OPTIONS)的机制,且支持前置/后置两个时机
怎么正确配置 CorsFilter 并启用它
过滤器必须放在 app/Filters/ 下,并在 app/Config/Filters.php 中注册别名和全局生效规则。注意命名空间和类名大小写 —— CI4 默认严格区分。
- 新建
app/Filters/CorsFilter.php,内容如下:
<?php namespace App\Filters;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;
class CorsFilter implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null)
{
$response = service('response');
$response->setHeader('Access-Control-Allow-Origin', '*');
$response->setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
$response->setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
$response->setHeader('Access-Control-Allow-Credentials', 'true');
$response->setHeader('Access-Control-Max-Age', '86400');
// 如果是 OPTIONS 请求,立即返回,不往下走
if ($request->getMethod() === 'options') {
$response->setStatusCode(200);
return $response;
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
// 可选:后置再补一次头(防漏),但通常前置已足够
}
}
- 在
app/Config/Filters.php中添加别名和全局规则:
public $aliases = [
// ... 其他别名
'cors' => \App\Filters\CorsFilter::class,
];
public $globals = [
'before' => [
'cors' => ['except' => ['/api/docs', '/health']],
],
'after' => [],
];
- 注意:
except数组里的路径必须是完整 URI 路径,不含域名,且开头不带/时会匹配失败
生产环境必须禁用 * 并校验 Origin
Access-Control-Allow-Origin: * 和 Access-Control-Allow-Credentials: true 是互斥的。一旦你前端带了 Cookie 或 Authorization 头,就必须显式指定 Origin,否则浏览器拒绝响应。
- 错误写法:
$response->setHeader('Access-Control-Allow-Origin', '*');+$response->setHeader('Access-Control-Allow-Credentials', 'true'); - 正确做法:从
$request->getServer('HTTP_ORIGIN')读取,比对白名单数组,只对匹配的 Origin 返回对应值 - 示例片段(替换
before()中的 Origin 设置部分):
$origin = $request->getServer('HTTP_ORIGIN');
$allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];
if (in_array($origin, $allowedOrigins)) {
$response->setHeader('Access-Control-Allow-Origin', $origin);
}
- 不要信任
HTTP_ORIGIN的任意值,必须白名单校验;否则等于开放 CSRF 攻击面 - 如果用了 Nginx / Apache 反向代理,确保
Origin头没被丢弃(检查proxy_pass_request_headers on;)
RestServer 扩展包的 check_cors 配置容易踩坑
如果你用的是 chriskacerguis/codeigniter-restserver,它自带 CORS 开关,但默认行为和原生 CI4 冲突 —— 它只处理 RestController 子类,不接管普通控制器或静态资源。
- 关键配置在
app/Config/Rest.php:
$config['check_cors'] = true; $config['allow_any_cors_domain'] = false; $config['allowed_cors_origins'] = ['https://app.example.com']; $config['allowed_cors_headers'] = ['Content-Type', 'Authorization'];
- 陷阱:
check_cors开启后,它会在_check_cors()方法中直接exit(),跳过你自定义的过滤器;所以要么全用 RestServer 的 CORS,要么全不用 - 建议:新项目别混用;若已用 RestServer,就删掉自定义
CorsFilter,只靠它的配置
真正容易被忽略的是:CORS 不是“加个头就完事”,它是一整套请求-响应协同机制。OPTIONS 预检是否返回 200、Origin 是否动态校验、Credentials 是否与 * 冲突、反向代理是否透传头 —— 这四个点只要一个断链,前端就静默失败,连控制台都看不到具体错误。











