Apache APISIX cors 插件详解:跨域资源共享配置、高级匹配策略与源码实现原理
Apache APISIX cors 插件详解跨域资源共享配置、高级匹配策略与源码实现原理【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix本指南围绕 Apache APISIX 云原生 API 网关中的cors插件展开系统讲解如何通过该插件在 Route/Service 上快速开启 CORS跨域资源共享覆盖全部属性参数、allow_credential与**强制通配符的安全边界、正则与元数据Metadata两种高级 Origin 匹配方式并结合 插件源码 与 测试用例 剖析其底层实现原理。读完本文你将能够独立完成从一行配置开启 CORS到精细化白名单管控 安全加固的完整落地。CORS 与 APISIX cors 插件跨域资源共享CORS是浏览器基于 HTTP 头实现的一种安全机制当页面所在域Origin与请求目标域不一致时浏览器会先发起 OPTIONS 预检请求Preflight并依据服务器返回的Access-Control-*系列响应头判断是否允许该跨域请求继续执行。传统方案需要在每个后端服务里重复编写 CORS 逻辑而 APISIX 提供的cors插件可以在网关层统一、集中地处理所有跨域场景后端服务无需任何改动。该插件完整实现了 CORS 规范中的Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Expose-Headers、Access-Control-Max-Age、Access-Control-Allow-Credentials响应头并额外支持 Resource Timing API 的Timing-Allow-Origin响应头可直接挂载到 Route 或 Service 上从源码看插件定义位于 apisix/plugins/cors.lua插件优先级priority 4000。属性配置总览cors插件的属性分为两类CORS 属性与 Resource Timing 属性。CORS 属性名称类型必填默认值说明allow_originsstring否*允许跨域的 Origin格式为scheme://host:port例如https://somedomain.com:8081。多个 Origin 用英文逗号,分隔。当allow_credential为false时可用*表示允许所有来源当allow_credential为true时可用**强制允许所有来源但会带来安全风险allow_methodsstring否*允许跨域的请求方法例如GET,POST多个方法用逗号分隔。*与**的语义同上allow_headersstring否*允许的请求头多个头用逗号分隔。*与**的语义同上expose_headersstring否无允许浏览器读取的响应头多个头用逗号分隔。未指定时插件不会修改Access-Control-Expose-Headers响应头max_ageinteger否5预检结果被浏览器缓存的最大秒数缓存期内浏览器直接使用缓存结果设置为-1表示禁用缓存。注意最大值受浏览器实现限制allow_credentialboolean否false置为true时允许请求携带 Cookie 等凭据。根据 CORS 规范此选项为true时其他属性不能使用*通配allow_origins_by_regexarray否nil通过正则匹配允许跨域的 Origin例如[.*\.test.com$]可匹配test.com的所有子域。一旦设置仅命中该正则范围的域名会被放行allow_origins不再参与判断allow_origins_by_metadataarray否nil引用插件 Metadata 中allow_origins映射的键来允许跨域。例如 Metadata 中配置了allow_origins: {EXAMPLE: https://example.com}则此处填[EXAMPLE]即可放行https://example.comResource Timing 属性名称类型必填默认值说明timing_allow_originsstring否nil允许访问资源计时信息的 Origin格式为scheme://host:port例如https://somedomain.com:8081多个 Origin 用逗号分隔。对应响应头Timing-Allow-Origintiming_allow_origins_by_regexarray否nil通过正则匹配允许访问资源计时信息的 Origin例如[.*\.test.com]可匹配test.com的所有子域。一旦设置仅命中该正则范围的域名会被放行timing_allow_origins不再参与判断两条重要安全约束allow_credential属性非常敏感必须谨慎使用。若置为true其他属性默认的*值将失效必须显式指定具体值。使用**强制通配符时你将暴露于 CSRF 等安全风险之下使用前请确认它满足你的安全级别要求。这两条规则并非仅写在文档中在 插件源码的check_schema校验逻辑 里被硬性执行当allow_credential为true时若allow_origins、allow_methods、allow_headers、expose_headers、timing_allow_origins任一为*校验直接返回错误you can not set * for other option when allow_credential is true。对应地cors 测试用例 中的 TEST 2427 验证了这五种组合均会被 Admin API 以 400 拒绝。同时源码使用正则^(\*|\*\*|null|\w://[^,](,\w://[^,])*)$cors.lua 第 26 行校验 Origin 格式cors2 测试 覆盖了合法值*、**、null、带端口的 URL 列表等与非法值如*a、x.com、缺协议头的http//y.com.uk等的完整矩阵。理解 Timing-Allow-Origin 的适用场景Timing-Allow-Origin响应头定义于 Resource Timing API但与 CORS 概念紧密相关。设想你有domain-A.com和domain-B.com两个域名你正停留在domain-A.com的页面上通过 XHR 请求domain-B.com上的资源并且需要读取该请求的计时信息如PerformanceResourceTiming。只有当你在domain-B.com上拥有跨域权限时浏览器才会向你展示这部分计时信息。因此正确步骤是先配置好 CORS 响应头再访问domain-B.com的 URL同时设置Timing-Allow-Origin浏览器才会返回所请求的计时数据。从 cors4 测试用例 可以看出allow_origins与timing_allow_origins是两套相互独立的放行集合——Origin 只命中allow_origins时仅返回 CORS 头如 TEST 14只命中timing_allow_origins时仅返回Timing-Allow-Origin如 TEST 19两者均命中才同时返回两组响应头如 TEST 17。插件 Metadata全局共享的 Origin 白名单cors插件还支持 Metadata 配置其 schema 定义了一个allow_origins对象见 cors.lua 第 35-46 行名称类型必填说明allow_originsobject否Origin 引用名与允许来源的映射表。映射的键用于插件属性allow_origins_by_metadata映射的值语义与插件属性allow_origins完全一致Metadata 的价值在于一处定义、多处引用把跨多个 Route 复用的 Origin 白名单集中维护在插件级 Metadata 中Route 侧只需通过键名引用避免在每个 Route 里重复书写冗长的 Origin 列表。从源码看process_with_allow_origins_by_metadata通过plugin.plugin_metadata(plugin_name)读取元数据并逐个键进行 Origin 匹配cors3 测试 展示了 Metadata 的典型配置形态例如{ allow_origins: { key_1: https://domain.com, key_2: https://sub.domain.com,https://sub2.domain.com, key_3: * } }需要说明的是当 Route 上同时配置了allow_origins_by_metadata与allow_origins时后者的*通配在元数据匹配场景下是无效的cors3 测试 TEST 13-14 验证了这一行为即元数据匹配优先于普通列表匹配。启用插件cors插件默认处于启用状态可以直接在指定 Route 或 Service 上挂载。以下命令在路由/hello上以全默认参数开启 CORS。首先从config.yaml中取出admin_key并存入环境变量Admin API 的鉴权配置见 conf/config.yaml生产环境请务必更换为强随机密钥admin_key$(yq .deployment.admin.admin_key[0].key conf/config.yaml | sed s///g)然后通过 Admin API 创建路由并启用插件curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: $admin_key -X PUT -d { uri: /hello, plugins: { cors: {} }, upstream: { type: roundrobin, nodes: { 127.0.0.1:8080: 1 } } }插件启用后无需重启 APISIX配置经由 etcd 下发后即实时生效。验证默认效果启用插件后向网关发起请求即可在响应中看到 CORS 响应头curl http://127.0.0.1:9080/hello -v... Server: APISIX web server Access-Control-Allow-Origin: * Access-Control-Allow-Methods: * Access-Control-Allow-Headers: * Access-Control-Max-Age: 5 ...这与 cors 测试用例 TEST 7 断言的默认行为完全一致默认配置下Access-Control-Allow-Origin、Allow-Methods、Allow-Headers均为*Access-Control-Max-Age为 5 秒。注意默认情况下不会输出Access-Control-Expose-Headers源码中仅当expose_headers非空时才设置该头见 set_cors_headers。预检请求OPTIONS的处理插件在rewrite阶段cors.lua 第 331-337 行对 OPTIONS 请求做了短路处理直接返回 200 并终止后续流程从而把 CORS 预检请求拦截在网关层避免其打到上游后端测试 TEST 14 验证了OPTIONS /hello直接返回空响应体。这也是该插件能够后端零改动实现 CORS 的关键设计之一。进阶配置场景场景一指定来源与凭据allow_credential当跨域请求需要携带 Cookie 时必须开启allow_credential并显式列出允许的来源与方法curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: $admin_key -X PUT -d { uri: /hello, plugins: { cors: { allow_origins: http://sub.domain.com,http://sub2.domain.com, allow_methods: GET,POST, allow_headers: headr1,headr2, expose_headers: ex-headr1,ex-headr2, max_age: 50, allow_credential: true } }, upstream: { type: roundrobin, nodes: { 127.0.0.1:8080: 1 } } }发起携带Origin头的请求后对应 测试 TEST 9curl http://127.0.0.1:9080/hello -H Origin: http://sub2.domain.com -v响应头应包含 Access-Control-Allow-Origin: http://sub2.domain.com Access-Control-Allow-Methods: GET,POST Access-Control-Allow-Headers: headr1,headr2 Access-Control-Expose-Headers: ex-headr1,ex-headr2 Access-Control-Max-Age: 50 Access-Control-Allow-Credentials: true同时会额外输出Vary: Origin响应头源码见 cors.lua 第 376-378 行这是 CORS 的最佳实践——告知缓存系统响应内容随Origin变化避免代理/浏览器缓存把 A 域名的 CORS 头错误复用到 B 域名。若请求的Origin不在白名单中则不会输出任何 CORS 头测试 TEST 10 验证了该行为浏览器将按 CORS 规范拦截响应。场景二强制通配**与请求头回显当allow_credential为false时可以使用**强制放行所有来源/方法/请求头。与*不同的是**会动态回显真实的来源Access-Control-Allow-Origin直接返回请求的Origin值无 Origin 时回退为*Access-Control-Allow-Methods被展开为GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,CONNECT,TRACE全量方法列表set_cors_headers 中的展开逻辑Access-Control-Allow-Headers则回显预检请求携带的Access-Control-Request-Headers头cors.lua 第 230-235 行测试 TEST 12 完整呈现了这套行为 Access-Control-Allow-Origin: https://sub.domain.com Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,CONNECT,TRACE Access-Control-Allow-Headers: req-header1,req-header2 Access-Control-Max-Age: 5场景三正则匹配子域当允许的域名规模较大或动态变化时可用allow_origins_by_regex以正则批量匹配测试 TEST 28-33 覆盖了单正则与多正则场景curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: $admin_key -X PUT -d { uri: /hello, plugins: { cors: { allow_origins: http://sub.domain.com,http://sub2.domain.com, allow_methods: GET,POST, allow_headers: headr1,headr2, expose_headers: ex-headr1,ex-headr2, max_age: 50, allow_credential: true, allow_origins_by_regex: [.*\\.test.com$, .*\\.example.org$] } }, upstream: { type: roundrobin, nodes: { 127.0.0.1:8080: 1 } } }正则模式下http://a.test.com、http://foo.example.org等命中规则即被放行且Access-Control-Allow-Origin回显请求来源未命中的http://a.test2.com等则拿不到任何 CORS 头。正则规则在配置校验阶段就会预编译cors.lua 第 196-212 行非法正则无法通过 Admin API 提交。源码级原理剖析执行阶段与优先级cors插件定义了priority 4000的执行优先级cors.lua 第 147-153 行并分别在两个阶段介入请求生命周期rewrite 阶段_M.rewrite保存原始Origin请求头到ctx.original_request_origin并短路拦截 OPTIONS 预检请求。header_filter 阶段_M.header_filter依据放行策略计算并写入 CORS 与 Timing 相关响应头。之所以要在 rewrite 阶段提前保存原始 Origin是为了抵御其他插件如 proxy-rewrite在后续阶段改写Origin请求头测试 TEST 34-35 验证了即使上游重写插件把Origin改为http://example.comCORS 匹配依然以客户端真实来源为准。多 Origin 列表的缓存优化当allow_origins包含逗号分隔的多个来源时插件通过 create_multiple_origin_cache 把列表拆分为哈希表并借助core.lrucachetype plugin的插件级缓存进行缓存避免每个请求都重复执行字符串分割与正则迭代cors.lua 第 254-263 行。匹配优先级从header_filter的控制流可以归纳出 Origin 匹配的完整优先级链若配置了allow_origins_by_metadata优先按元数据映射匹配若配置了allow_origins_by_regex则只用正则判断普通allow_origins列表被跳过否则使用allow_origins列表做精确匹配或*通配req_origin allow_origins or allow_origins *见 match_origins全部未命中时再回退尝试一次元数据匹配。正则与 Timing 相关规则遵循同一套优先级逻辑timing_allow_origins_by_regex优先于timing_allow_origins测试 cors4 TEST 25 明确验证了正则优先于列表的设计意图。覆盖上游响应头header_filter阶段使用core.response.set_header设置响应头因此即使上游后端自行输出了 CORS 头也会被插件覆盖为网关策略的最终值测试 TEST 17-23 验证了Access-Control-Allow-Origin、Allow-Methods、Allow-Headers、Expose-Headers、Max-Age、Allow-Credentials六类响应头均以上游为准被覆写。这意味着后端可以彻底移除自身的 CORS 逻辑由网关统一收敛。与鉴权插件协同得益于header_filter阶段的执行时机即使请求因鉴权失败返回 401/403CORS 响应头依然会被写入cors 测试 TEST 15-16 验证了key-auth鉴权失败返回 401 的同时仍携带完整的 CORS 头。这保证了浏览器端能正常读取错误响应避免出现鉴权失败但跨域报错的双重迷惑问题。删除插件移除cors插件只需将对应配置从插件配置中删除即可。APISIX 会自动热加载生效无需重启curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: $admin_key -X PUT -d { uri: /hello, plugins: {}, upstream: { type: roundrobin, nodes: { 127.0.0.1:8080: 1 } } }删除后请求将不再携带任何Access-Control-*响应头。安全使用建议避免*与凭据混用allow_credential: true时禁止使用*这是 CORS 规范与插件校验的双重约束务必为allow_origins、allow_methods、allow_headers显式列出精确值。慎用**强制通配**会放行任意来源并回显其 Origin使攻击者可以从任意域名发起携带 Cookie 的跨域请求显著扩大 CSRF 攻击面仅在完全可信的内网或公开只读接口场景下使用。生产环境优先使用白名单对于面向公网的 API推荐组合使用精确 Origin 列表、allow_origins_by_regex子域批量放行与 Metadata 复用机制将放行面收敛到最小。关注Vary: Origin只要allow_origins不是*插件就会自动追加Vary: Origin这有助于缓存系统正确区分不同来源的响应请勿在网关层移除该头。测试与验证资源仓库内为cors插件提供了完善的测试覆盖可作为配置行为的权威参考t/plugin/cors.t默认行为、指定来源、强制通配、OPTIONS 短路、鉴权协同、响应头覆盖、凭据与*冲突校验等基础用例t/plugin/cors2.tOrigin 格式校验矩阵与正则匹配优先级t/plugin/cors3.t插件 Metadata 的allow_origins映射与allow_origins_by_metadata引用t/plugin/cors4.ttiming_allow_origins/timing_allow_origins_by_regex的独立放行语义。结合 插件实现 阅读这些用例可以快速验证任意配置组合的预期响应头是排查线上跨域问题时的有力工具。【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考