Apache APISIX CSRF 插件实战:基于 Double Submit Cookie 的跨站请求伪造防护

发布时间:2026/9/14 14:53:33
Apache APISIX CSRF 插件实战:基于 Double Submit Cookie 的跨站请求伪造防护 Apache APISIX CSRF 插件实战基于 Double Submit Cookie 的跨站请求伪造防护【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisixApache APISIX 的csrf插件基于业界标准的 Double Submit Cookie双重提交 Cookie机制为你的 API 提供开箱即用的跨站请求伪造CSRF攻击防护。本文以 官方插件文档 为主线结合 插件源码 与 单元测试完整讲解插件的核心原理、配置属性、启用方式、客户端调用规范与底层实现细节。读完本文你将能够在 APISIX 中快速为指定路由开启 CSRF 防护并正确设计前端请求头与 Cookie 的携带方式。一、插件概述与防护原理csrf插件用于保护你的 API 免受 CSRF 攻击采用的方案是 Double Submit Cookie双重提交 Cookie方法。所谓 CSRF 攻击是指攻击者诱导已登录用户的浏览器向目标站点发起带有用户身份凭证如 Cookie的跨站请求从而以受害者身份执行非预期操作如转账、改密。Double Submit Cookie 的核心思路是服务端在响应中下发一个加密签名过的随机 Token写入 Cookie客户端发起不安全请求时必须把这个 Token同时放在请求头Header中一并提交服务端校验请求头中的 Token 与 Cookie 中的 Token一致且签名合法才放行请求。由于攻击者无法读取跨站响应中下发的 Cookie也无法在请求中同时伪造 Header 与 Cookie 两个值从而有效阻断 CSRF 攻击。从插件实现来看csrf.lua 中定义了安全方法集合local SAFE_METHODS {GET, HEAD, OPTIONS}插件将GET、HEAD、OPTIONS视为安全方法safe-methods这类请求不做拦截校验其余方法如POST、PUT、DELETE、PATCH等均被视为不安全方法unsafe-methods必须通过 CSRF 校验。二、插件属性Attributes详解插件的 schema 定义位于 csrf.lua与官方文档完全对应。配置属性如下表名称类型必填默认值描述namestring否apisix-csrf-token生成的 Cookie 中 Token 的名称同时作为请求头字段名expiresnumber否7200CSRF Cookie 的过期时间秒设置为0表示跳过过期时间检查keystring是无用于加密签名 Cookie 的密钥2.1 key签名密钥必填key是生成与校验 Token 签名的核心机密必须配置。源码中的签名生成函数gen_sign使用 SHA-256 对{expires, random, key}三元组做摘要得到十六进制签名串local function gen_sign(random, expires, key) local sha256 resty_sha256:new() local sign {expires: .. expires .. ,random: .. random .. ,key: .. key .. } sha256:update(sign) local digest sha256:final() return str.to_hex(digest) end这意味着key一旦泄露攻击者即可自行伪造合法 Token因此务必妥善保管。2.2 expires过期时间默认 7200 秒expires控制 Token 的有效期默认 7200 秒2 小时。源码在check_csrf_token中这样判断过期local time_now ngx_time() if conf.expires 0 and time_now - expires conf.expires then core.log.error(token has expired) return false end可以看到只有当conf.expires 0时才检查过期时间设置为0则跳过过期检查Token 永不过期仅受签名约束。2.3 nameToken 名称默认 apisix-csrf-tokenname同时决定了两处字段名服务端下发的Cookie 名称客户端必须携带的请求头名称。在_M.access中插件通过core.request.header(ctx, conf.name)读取请求头通过ctx.var[cookie_ .. conf.name]读取同名 Cookie二者必须一致。2.4 加密存储encrypt_fields插件 schema 中额外声明了encrypt_fields {key}这意味着key字段将以加密形式存储在 etcd中。这一机制在 plugin-develop.md 中有详细说明需 APISIX 版本 3.1.0通过 Admin API 新增/更新资源时APISIX 自动对encrypt_fields声明的参数加密后写入 etcd通过 Admin API 读取资源或插件运行时APISIX 自动解密。要启用该能力需在 config.yaml 中开启data_encryptionapisix: data_encryption: enable: true keyring: - edd1c9f0985e76a2 - qeddd145sfvddff4APISIX 会按keyring中的密钥顺序依次尝试解密仅针对encrypt_fields声明的字段直到某个密钥解密成功若全部失败则使用原始数据。测试用例 TEST 15: data encryption for key 验证了这一行为——从 Admin API 读回的是明文userkey而从 etcd 直接读取则是密文mt39FazQccyMqt4ctoRV7w。三、启用插件3.1 准备 Admin API 密钥APISIX Admin API 默认监听9180端口调用时需要携带X-API-KEY。管理员密钥定义在 config.yaml 的deployment.admin.admin_key中可以按官方文档的方式读取到环境变量admin_key$(yq .deployment.admin.admin_key[0].key conf/config.yaml | sed s///g)注意生产环境务必替换默认的管理员密钥。若配置文件中key为空APISIX 会自动生成并回写。3.2 为路由绑定 csrf 插件通过 Admin API 在 Route 1 上启用插件curl -i http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: $admin_key -X PUT -d { uri: /hello, plugins: { csrf: { key: edd1c9f034335f136f87ad84b625c8f1 } }, upstream: { type: roundrobin, nodes: { 127.0.0.1:9001: 1 } } }配置完成后该路由即受到 CSRF 保护使用非安全方法如POST访问会被拦截并返回 401 状态码。3.3 校验 schema插件通过_M.check_schema调用core.schema.check(schema, conf)校验配置。测试用例 TEST 1: sanity 验证了合法配置{name _csrf, expires 3600, key testkey}可以通过校验。四、工作流程与源码级原理插件挂载了两个执行阶段完整生命周期如下4.1 header_filter 阶段下发带签名的 Token每次响应时插件都会在header_filter阶段生成一个新的 CSRF Token 并写入Set-Cookiefunction _M.header_filter(conf, ctx) local csrf_token gen_csrf_token(conf) local cookie conf.name .. .. csrf_token .. ;path/;SameSiteLax;Expires .. ngx_cookie_time(ngx_time() conf.expires) core.response.add_header(Set-Cookie, cookie) endToken 的生成过程gen_csrf_token如下取一个随机数math.random()取当前时间戳ngx_time()作为签发时间即expires字段用gen_sign基于key计算 SHA-256 签名将{random, expires, sign}序列化为 JSON 后再 Base64 编码得到最终 Cookie 值。local token { random random, expires timestamp, sign sign, } local cookie ngx_encode_base64(core.json.encode(token))注意两个细节每个请求都会返回一个新 Cookie文档明确提示A new cookie is returned for each request无需服务端保存会话状态这正是 Double Submit Cookie 无状态化的体现Cookie 还附加了SameSiteLax属性进一步抑制跨站携带 Cookie 的行为。4.2 access 阶段拦截不安全方法_M.access是校验核心其判定逻辑如下function _M.access(conf, ctx) local method core.request.get_method(ctx) if core.table.array_find(SAFE_METHODS, method) then return end local header_token core.request.header(ctx, conf.name) if not header_token or header_token then return 401, {error_msg no csrf token in headers} end local cookie_token ctx.var[cookie_ .. conf.name] if not cookie_token then return 401, {error_msg no csrf cookie} end if header_token ~ cookie_token then return 401, {error_msg csrf token mismatch} end local result check_csrf_token(conf, ctx, cookie_token) if not result then return 401, {error_msg Failed to verify the csrf token signature} end end校验链路共四道关卡任一失败即返回 401关卡校验内容失败响应1方法是否属于安全方法GET/HEAD/OPTIONS是则直接放行—2请求头中是否携带 Tokenno csrf token in headers3Cookie 中是否存在同名 Tokenno csrf cookie4请求头 Token 与 Cookie Token 是否一致csrf token mismatch5Token 签名与过期时间校验Failed to verify the csrf token signature签名校验check_csrf_token内部完成三步Base64 解码 → JSON 解析出random/expires/sign→ 用同一key重算签名比对并检查过期。任意一步失败解码失败、字段缺失、过期、签名不匹配都会拒绝请求并输出对应 error log如Invalid signatures、token has expired。五、完整调用示例5.1 未携带 Token 直接 POST被拦截按上述配置启用插件后直接对/hello发起 POSTcurl -i http://127.0.0.1:9080/hello -X POST返回HTTP/1.1 401 Unauthorized ... {error_msg:no csrf token in headers}5.2 GET 获取加密 Token Cookie先发一个 GET 请求拿到带签名的 Cookiecurl -i http://127.0.0.1:9080/hello返回HTTP/1.1 200 OK Set-Cookie: apisix-csrf-tokeneyJyYW5kb20iOjAuNjg4OTcyMzA4ODM1NDMsImV4cGlyZXMiOjcyMDAsInNpZ24iOiJcL09uZEF4WUZDZGYwSnBiNDlKREtnbzVoYkJjbzhkS0JRZXVDQm44MG9ldz0ifQ;path/;ExpiresMon, 13-Dec-21 09:33:55 GMTCookie 名默认为apisix-csrf-token值即加密后的 Token。5.3 携带 Header Cookie 完成 POST后续发起不安全方法请求时必须把 Cookie 中的 Token 原样读取出来放入同名的请求头并同时携带 Cookie。使用 js-cookie 读取 Cookie、axios 发送请求的典型前端写法const token Cookie.get(apisix-csrf-token); const instance axios.create({ headers: {apisix-csrf-token: token} });请务必确保请求同时带上 Cookie如 axios 开启withCredentials或同源下默认携带。使用 curl 验证的完整写法curl -i http://127.0.0.1:9080/hello -X POST \ -H apisix-csrf-token: eyJyYW5kb20iOjAuNjg4OTcyMzA4ODM1NDMsImV4cGlyZXMiOjcyMDAsInNpZ24iOiJcL09uZEF4WUZDZGYwSnBiNDlKREtnbzVoYkJjbzhkS0JRZXVDQm44MG9ldz0ifQ \ -b apisix-csrf-tokeneyJyYW5kb20iOjAuNjg4OTcyMzA4ODM1NDMsImV4cGlyZXMiOjcyMDAsInNpZ24iOiJcL09uZEF4WUZDZGYwSnBiNDlKREtnbzVoYkJjbzhkS0JRZXVDQm44MG9ldz0ifQ请求头 Token 与 Cookie Token 一致且签名有效时返回HTTP/1.1 200 OK。5.4 各种失败场景速查t/plugin/csrf.t 中的测试用例完整覆盖了各种失败路径可作为排错参考测试用例场景结果TEST 4无任何 Token 直接 POST401no csrf token in headersTEST 5仅带 Header错误 Token无 Cookie401no csrf cookieTEST 6仅带 Cookie无 Header401no csrf token in headersTEST 7Header 与 Cookie 值不一致401csrf token mismatchTEST 8Token 签名非法401Failed to verify the csrf token signature日志Invalid signaturesTEST 11/12Token 已过期401日志token has expiredTEST 14expires0 时不过期200 放行六、删除插件需要移除 CSRF 防护时只需从路由配置中删除plugins.csrf配置块即可。APISIX 会自动热加载生效无需重启curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: $admin_key -X PUT -d { uri: /hello, upstream: { type: roundrobin, nodes: { 127.0.0.1:1980: 1 } } }七、实践建议与注意事项结合源码与官方文档在使用csrf插件时有几点需要特别留意Key 管理key是签名的唯一机密建议使用高强度随机串并结合data_encryption加密存储见 plugin-develop.md避免明文落入 etcd。每请求新 Cookie由于每次响应都会刷新 Cookie 与过期时间前端应在每次页面加载/会话开始时通过 GET 等安全方法获取最新 Token而不是长期缓存。安全方法白名单插件默认只放行GET、HEAD、OPTIONS。如果你的业务中存在其他语义上安全的自定义方法仍会被视为不安全方法并要求携带 Token。SameSiteLax 的叠加效果插件下发的 Cookie 自带SameSiteLax与现代浏览器策略配合可进一步降低 CSRF 风险但依赖 Cookie 的服务端校验逻辑如会话建议结合expires0等策略统一规划。适用场景该插件适用于传统 Web 会话/Cookie 认证场景若你的 API 完全使用无状态 Token如 JWT 放在 Authorization 头且浏览器不会自动携带凭证则 CSRF 风险本身较低可按需启用。CSRF 防护是 Web API 安全纵深防御中的关键一环。Apache APISIX 通过csrf插件将 Double Submit Cookie 机制以声明式配置的方式下沉到网关层让业务团队无需修改后端代码即可获得统一的防护能力。你可以在此基础上继续结合 更多安全类插件构建完整的网关安全体系。【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考