必须用过滤器统一处理跨域,因options预检请求不经过控制器,直接404;corsfilter需校验origin、禁用*+credentials组合、返回204,且在filters.php中全局注册启用。

CodeIgniter 4 必须用过滤器(Filter)统一处理跨域,不能只在控制器里写 header() 或 $this->response->setHeader() —— 否则 OPTIONS 预检请求会直接 404 或返回空响应,前端卡死在 “preflight request doesn’t pass access control check”。
为什么控制器里加 header 没用
浏览器对带自定义头(如 role、Authorization)或需携带 cookie 的请求,一定会先发一个 OPTIONS 请求做预检。这个请求根本不会进你的控制器,CI4 路由层发现没有匹配的 OPTIONS 路由,就直接抛出 404 或返回空响应。你控制器里的 header() 根本没机会执行。
- 典型错误现象:
Failed to load http://api.example.com/v1/users: Response to preflight request doesn't pass access control check - 哪怕你在所有控制器方法开头都写了
header('Access-Control-Allow-Origin: *'),也救不了OPTIONS - CI4 的
Filters是唯一能覆盖所有 HTTP 方法(含OPTIONS)且在路由分发前/后执行的机制
怎么写一个安全可用的 CorsFilter
过滤器必须放在 app/Filters/ 下,类名严格匹配命名空间(如 App\Filters\CorsFilter),并在 app/Config/Filters.php 中注册并启用。
- 不要用
Access-Control-Allow-Origin: *+Access-Control-Allow-Credentials: true组合,浏览器会直接拒绝(这是硬性规范) - 生产环境务必校验
Origin请求头,只放行可信域名(比如https://admin.example.com) - 必须显式返回
Access-Control-Allow-Methods和Access-Control-Allow-Headers,否则预检失败 - 对
OPTIONS请求,直接返回204 No Content,不走后续逻辑
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\Http\RequestInterface;
use CodeIgniter\Http\ResponseInterface;
class CorsFilter implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null)
{
$origin = $request->getServer('HTTP_ORIGIN');
$allowedOrigins = ['https://localhost:5173', 'https://admin.example.com'];
if (in_array($origin, $allowedOrigins)) {
$response = service('response');
$response->setHeader('Access-Control-Allow-Origin', $origin);
$response->setHeader('Access-Control-Allow-Credentials', 'true');
$response->setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE, OPTIONS');
$response->setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, role');
$response->setHeader('Access-Control-Expose-Headers', 'Content-Length, X-Foo');
if ($request->getMethod() === 'options') {
$response->setStatusCode(204);
$response->send();
exit;
}
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
}
}
怎么在 Filters.php 中启用它
打开 app/Config/Filters.php,在 $aliases 数组里注册别名,在 $globals 或 $filters 里指定生效范围。
- 别名注册示例:
'cors' => \App\Filters\CorsFilter::class - 全局启用(推荐):
'before' => ['cors']放到$globals数组里 - 如果只想对 API 路由启用,可在
$filters里按路径配置,例如:'api/*' => ['before' => ['cors']] - 注意:类名大小写敏感,文件路径必须是
app/Filters/CorsFilter.php,不能拼错
调试时最容易忽略的三个点
很多人配完还是报错,问题往往不在代码本身,而在这些细节:
-
Access-Control-Allow-Origin值必须和请求头里的Origin完全一致(协议、域名、端口都要匹配),不能多空格、不能少斜杠 - 前端
fetch用了credentials: 'include',但后端没设Access-Control-Allow-Credentials: true,或设了却还用*当Origin - 服务器开了 OPcache 或其他缓存中间件,导致新过滤器没生效,改完记得清缓存或重启 PHP-FPM











