news 2026/10/11 11:01:27

CodeIgniter 4 跨域资源共享(CORS)完整配置指南:过滤器、预检请求与多环境策略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CodeIgniter 4 跨域资源共享(CORS)完整配置指南:过滤器、预检请求与多环境策略
  • 后端
  • Web框架

【免费下载链接】CodeIgniter4

Open Source PHP Framework (originally from EllisLab)

项目地址:https://gitcode.com/gh_mirrors/co/CodeIgniter4
点击查看免费下载

CORS(Cross-Origin Resource Sharing,跨域资源共享)是基于 HTTP 头的安全机制,它允许服务器声明:除自身之外的哪些源(域名、协议或端口)可以被浏览器授权加载资源。CodeIgniter 4 自 4.5.0 起内置了 CORS 过滤器与辅助类(cors过滤器 +CodeIgniter\HTTP\Cors类),本文将以官方文档 cors.rst 为骨架,结合仓库源码与测试,完整讲解配置项、路由/过滤器两种启用方式、预检(Preflight)请求处理、多配置切换以及底层实现原理。读完本文,你将能为 API 或前后端分离应用正确配置、验证并调试 CORS,同时避免通配符与凭据共用、缓存Vary头缺失等常见坑点。

CORS 与 CodeIgniter 4 的解决方案

跨域资源共享机制通过在 HTTP 请求与响应中添加头部,向浏览器表明目标资源是否允许跨源共享,从而帮助防御跨站请求伪造(CSRF)与数据窃取等恶意攻击。如果你对 CORS 请求头、预检请求等基础概念尚不熟悉,建议先阅读 MDN 关于 CORS 的文档(可在 app/Config/Cors.php 头部注释中找到官方推荐链接)。

CodeIgniter 4 为开发者提供了两个层次的 CORS 支持(4.5.0 版本新增,见 v4.5.0 更新日志):

  • CORS 过滤器:实现位于 system/Filters/Cors.php,通过before/after生命周期自动注入响应头、终结预检请求;
  • CORS 辅助类:实现位于 system/HTTP/Cors.php,负责实际的头生成、源校验与配置工厂,过滤器内部即委托该类工作。

过滤器别名cors已在 system/Config/Filters.php 与 app/Config/Filters.php 的$aliases中预注册,指向CodeIgniter\Filters\Cors,开箱即用。

配置 CORS:$default配置项详解

CORS 的默认配置统一放在app/Config/Cors.php的$default属性中。完整默认配置如下(仓库实际文件 app/Config/Cors.php):

<?php namespace Config; use CodeIgniter\Config\BaseConfig; class Cors extends BaseConfig { public array $default = [ 'allowedOrigins' => [], 'allowedOriginsPatterns' => [], 'supportsCredentials' => false, 'allowedHeaders' => [], 'exposedHeaders' => [], 'allowedMethods' => [], 'maxAge' => 7200, ]; }

必须设置的三个核心项

文档明确指出,$default中以下三项是开启 CORS 的最低要求:

配置项含义对应响应头
allowedOrigins显式列出允许的 Origin(如['http://localhost:8080']、['https://www.example.com'])Access-Control-Allow-Origin
allowedHeaders显式列出允许的 HTTP 请求头(如['Authorization', 'Content-Type'])Access-Control-Allow-Headers
allowedMethods显式列出允许的 HTTP 方法(如['GET', 'POST', 'PUT', 'DELETE'])Access-Control-Allow-Methods

⚠️最小权限原则:基于 least privilege 原则,只应允许最低限度的 Origin、Methods 与 Headers,切勿贪多。

其余配置项

  • allowedOriginsPatterns:Origin 正则模式列表。文档与源码注释说明,每个模式会被包成#\A<pattern>\z#进行整串匹配,例如['https://\w+\.example\.com']可匹配https://api.example.com。匹配成功的源会动态回填到Access-Control-Allow-Origin,并追加Vary: Origin;
  • supportsCredentials:若跨源请求携带凭据(如 Cookie),必须设为true,此时会输出Access-Control-Allow-Credentials: true;
  • exposedHeaders:允许浏览器脚本读取的响应头列表(Access-Control-Expose-Headers),如['Content-Length', 'X-Kuma-Revision'];
  • maxAge:预检请求结果可被浏览器缓存的最大秒数(Access-Control-Max-Age),默认7200(2 小时)。

源码中的合法性约束

在 system/HTTP/Cors.php 中有两条硬性校验,配置不合规会抛出ConfigException:

  1. 通配符只能单独使用:checkWildcard()要求若配置中使用了'*',则该数组必须恰好为['*'](元素个数为 1),否则抛错;
  2. 凭据请求禁止通配符:checkWildcardAndCredentials()规定当supportsCredentials为true时,Access-Control-Allow-Origin与Access-Control-Allow-Headers的值不得是"*"通配符(浏览器规范要求),源码注释也明确提示"不推荐使用通配符"。

启用 CORS:过滤器 + OPTIONS 路由缺一不可

启用 CORS 需要同时完成两件事:

  1. 为允许 CORS 的路由指定cors过滤器;
  2. 为 CORS 预检请求添加OPTIONS路由。

⚠️关键警告:除了 Required Filters(必需过滤器,见 app/Config/Filters.php 的$required列表)之外,控制器过滤器在路由不存在时不会执行。因此不添加 OPTIONS 路由,预检请求将直接 404,CORS 功能失效。而 CORS 过滤器会接管所有预检请求,所以 OPTIONS 路由的闭包控制器通常不会被真正调用。

方式一:在 Routes.php 中按路由组设置

在app/Config/Routes.php中为路由组绑定cors过滤器(示例源自 cors/001.php):

use CodeIgniter\Router\RouteCollection; $routes->group('', ['filter' => 'cors'], static function (RouteCollection $routes): void { $routes->resource('product'); // 为预检请求添加 OPTIONS 路由 $routes->options('product', static function () { // 如需处理普通的非预检 OPTIONS 请求,可在此实现逻辑 $response = response(); $response->setStatusCode(204); $response->setHeader('Allow:', 'OPTIONS, GET, POST, PUT, PATCH, DELETE'); return $response; }); $routes->options('product/(:any)', static function () {}); });

这里$routes->options('product/(:any)', ...)覆盖了资源路由的所有子路径预检;$routes->options('product', ...)中演示了如何处理非预检的普通 OPTIONS 请求(返回204并附带Allow头)。

方式二:在 Config/Filters.php 中按 URI 路径设置

也可以改用app/Config/Filters.php的$filters属性按 URI 模式匹配(示例源自 cors/002.php):

namespace Config; use CodeIgniter\Config\Filters as BaseFilters; class Filters extends BaseFilters { public array $filters = [ 'cors' => [ 'before' => ['api/*'], 'after' => ['api/*'], ], ]; }

同样必须补充 OPTIONS 路由(示例源自 cors/003.php):

use CodeIgniter\Router\RouteCollection; $routes->group('', ['filter' => 'cors'], static function (RouteCollection $routes): void { $routes->options('api/(:any)', static function () {}); });

注意此方式将过滤器配置在路由之外的$filters属性中,所有匹配api/*的请求(无论 GET、POST 还是 OPTIONS)都会经过cors过滤器。

多配置:cors:api与过滤器参数

当不同路由组需要不同策略时,可为app/Config/Cors.php添加新的属性作为独立配置。属性名即配置名,例如新增$api(示例源自 cors/004.php):

namespace Config; use CodeIgniter\Config\BaseConfig; class Cors extends BaseConfig { // ... $default ... public array $api = [ 'allowedOrigins' => ['https://app.example.com'], 'allowedOriginsPatterns' => [], 'supportsCredentials' => true, 'allowedHeaders' => ['Authorization', 'Content-Type'], 'exposedHeaders' => [], 'allowedMethods' => ['GET', 'POST', 'PUT', 'DELETE'], 'maxAge' => 7200, ]; }

然后在路由过滤器参数中以cors:api的形式指定该配置名(示例源自 cors/005.php):

use CodeIgniter\Router\RouteCollection; $routes->group('api', ['filter' => 'cors:api'], static function (RouteCollection $routes): void { $routes->resource('user'); $routes->options('user', static function () {}); $routes->options('user/(:any)', static function () {}); });

其底层机制是:过滤器before()收到参数数组$arguments后,调用CorsService::factory($arguments[0])按名字取出对应配置(见 system/Filters/Cors.php 与 system/HTTP/Cors.php 的factory())。同样的参数语法也可用于$filters属性,例如'cors:api' => ['before' => ['api/*'], 'after' => ['api/*']],即文档中提到的 过滤器参数 功能(自 4.4.0 起支持,4.6.0 起spark输出表中会显示过滤器参数)。

验证配置:spark routes 与 spark filter:check

配置完成后,可用spark命令核对路由与过滤器:

php spark routes

该命令会列出全部路由及其绑定的过滤器(含闭包路由、自动路由与过滤器信息,详见 路由文档)。由于路由正则表达式可能导致过滤器显示不准,文档还推荐使用更精确的过滤器检查命令(详见 过滤器文档):

php spark filter:check get /

输出会以表格形式呈现Method | Route | Before Filters | After Filters,并列出实际执行的过滤器类名(自 4.6.0 起同时显示过滤器参数)。

源码级原理:CodeIgniter\HTTP\Cors 的工作方式

CORS 辅助类的完整实现在 system/HTTP/Cors.php,以下三个公开方法对应文档"类参考"部分:

isPreflightRequest(IncomingRequest $request): bool

判断请求是否为预检请求,判定条件只有两个(源码 L80-L84):

return $request->is('OPTIONS') && $request->hasHeader('Access-Control-Request-Method');

即:请求方法为OPTIONS且携带Access-Control-Request-Method头。这正好被 tests/system/HTTP/CorsTest.php 中的testIsPreflightRequestTrue/False用例覆盖:仅有OPTIONS方法但无该头时返回false。

handlePreflightRequest(RequestInterface $request, ResponseInterface $response): ResponseInterface

处理预检请求(源码 L89-L103)。流程为:

  1. 设置响应状态码204 No Content;
  2. 调用setAllowOrigin()校验请求 Origin 并输出Access-Control-Allow-Origin;
  3. 仅当Origin 校验通过(响应中已有该头)时,才依次输出Access-Control-Allow-Headers、Access-Control-Allow-Methods、Access-Control-Max-Age与(如启用)Access-Control-Allow-Credentials。

Origin 匹配有三个分支(源码 L129-L170),与测试用例一一对应:

  • 单一 Origin:allowedOrigins恰好 1 个且无正则时,直接回显该值(测试testHandlePreflightRequestSingleAllowedOrigin);
  • 多个 Origin:从请求的Origin头精确匹配,命中则回显该 Origin 并追加Vary: Origin;未命中则不输出任何 CORS 头(测试testHandlePreflightRequestMultipleAllowedOriginsAllowed/NotAllowed);
  • 正则模式:依次用#\A<pattern>\z#匹配,命中则回显并追加Vary: Origin(测试testHandlePreflightRequestAllowedOriginsPatternsAllowed/NotAllowed)。

Vary: Origin的存在意义在于:响应内容随 Origin 变化,必须告知 CDN/代理缓存按 Origin 区分缓存条目,防止跨源缓存污染。

addResponseHeaders(RequestInterface $request, ResponseInterface $response): ResponseInterface

为普通(非预检)跨源请求添加响应头(源码 L209-L219):设置Access-Control-Allow-Origin后,若校验通过再补充Access-Control-Allow-Credentials与Access-Control-Expose-Headers。注意普通请求不会输出Allow-Headers/Allow-Methods(测试testAddResponseHeadersSingleAllowedOriginSimpleRequest已断言这两个头不存在)。

过滤器如何衔接:before / after 与 Vary 头

system/Filters/Cors.php 的before()中,若判定为预检请求则直接返回handlePreflightRequest()的结果并短路后续控制器执行;同时无论预检还是普通 OPTIONS 请求,都会追加Vary: Access-Control-Request-Method头(源码注释解释:若有 CDN 等中间缓存,普通 OPTIONS 与有效预检请求会被分开缓存,避免错误命中)。after()则通过hasResponseHeaders()判断响应是否已被(例如其他过滤器)注入过 CORS 头,避免重复添加——这正是 v4.6.1 更新日志 中"修复其他过滤器在 before 阶段返回响应对象时 CORS 头未添加"问题的设计所在。

类参考速查

以下为 system/HTTP/Cors.php 对外公开的 API 摘要:

方法签名说明
addResponseHeadersaddResponseHeaders(RequestInterface $request, ResponseInterface $response): ResponseInterface为跨源请求添加 CORS 响应头
handlePreflightRequesthandlePreflightRequest(RequestInterface $request, ResponseInterface $response): ResponseInterface处理预检请求(返回204及全套 CORS 头)
isPreflightRequestisPreflightRequest(IncomingRequest $request): bool判断请求是否为预检请求(OPTIONS+Access-Control-Request-Method)

除此之外,构造函数与factory(string $configName = 'default')支持直接以配置数组或CorsConfig实例初始化,hasResponseHeaders()供过滤器判断头部是否已设置。完整行为可对照单元测试 tests/system/HTTP/CorsTest.php 阅读,其中覆盖了单源/多源/正则匹配、凭据、暴露头、Vary头合并等全部场景。

小结

在 CodeIgniter 4 中启用 CORS 遵循一条固定链路:先在 app/Config/Cors.php 配置$default(或自定义命名配置),再在 app/Config/Routes.php / app/Config/Filters.php 绑定cors(或cors:配置名)过滤器,最后务必为路由补充 OPTIONS 预检路由。配置完成后用php spark routes与php spark filter:check验证,配合本文给出的源码分支与测试用例,即可快速定位 Origin 未匹配、凭据与通配符冲突、缓存Vary头缺失等绝大多数跨域问题。

  • 后端
  • Web框架

【免费下载链接】CodeIgniter4

Open Source PHP Framework (originally from EllisLab)

项目地址:https://gitcode.com/gh_mirrors/co/CodeIgniter4
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 11:00:16

GOOSE-LightGBM多变量分类预测:鹅优化算法调参的Matlab实现与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/11 10:58:22

C#零配置网络:链路本地地址与mDNS服务发现实战

简介&#xff1a;ZeroConfiOS是一个面向C#开发者、聚焦网络服务自动化部署的开源工具库&#xff0c;专为解决动态网络环境下服务发布与IP地址自适应配置难题而设计&#xff0c;适用于物联网设备、跨平台微服务及多网卡场景下的快速集成。资源包共43个文件&#xff0c;含32个核心…

作者头像 李华
网站建设 2026/10/11 10:56:53

AIGC行业应用场景实战:从重复劳动到人机协同的落地方法论

不少人一听到“AIGC行业应用场景”这个词&#xff0c;第一反应就是“让AI写文案、画图”&#xff0c;然后就没有然后了。我在帮几家不同业务的公司做过AI落地之后&#xff0c;最深的一个感觉是&#xff1a;AIGC真正值钱的地方&#xff0c;从来不在“它能生成什么”&#xff0c;…

作者头像 李华
网站建设 2026/10/11 10:56:31

HarmonyOS 7 PickerController:超分预览另存凭证绑定与补偿【鸿蒙心迹】

图片增强完成后&#xff0c;最先冒出来的按钮通常是“保存到相册”。但按钮后面还有一个需要设计的边界&#xff1a;用户正在看的是哪一张原图、哪些增强文件属于这次编辑、当前的保存动作准备覆盖原图还是另存为新图。如果这几个关系没有锁住&#xff0c;一次回调迟到就可能把…

作者头像 李华
网站建设 2026/10/11 10:56:29

HarmonyOS 7 WindowAvoidArea:多形态工具栏避让回算与退订【鸿蒙心迹】

沉浸式页面里的工具栏&#xff0c;往往并不是被某一个系统栏“挡住”&#xff0c;而是页面把几次不同窗口形态的避让值当成同一份累积账本。窄窗口时底部要让出24vp&#xff0c;展开后变成16vp&#xff0c;代码却始终用历史最大值24。按钮当然不会被挡住&#xff0c;但会无缘无…

作者头像 李华
网站建设 2026/10/11 10:53:51

能源制造行业的装配动画,为什么做起来总是慢半拍

在能源制造行业&#xff0c;产品往往具有大型化、定制化、结构复杂的特点。以风电齿轮箱、核电阀门、储能装备为例&#xff0c;一台设备涉及数百甚至上千个零部件&#xff0c;装配精度要求高&#xff0c;工艺步骤复杂。装配动画在这些场景中&#xff0c;已经成为生产指导、员工…

作者头像 李华