Apache APISIX forward-auth 插件详解:将认证逻辑下沉到外部服务
【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix
forward-auth是 Apache APISIX 提供的经典外部认证插件,它把身份认证与授权逻辑从网关中剥离出来,交由一个专门的外部认证服务处理。通过本文,你将掌握该插件的全部配置属性、请求头转发规则、三种典型响应处理方式(放行 / 透传响应头 / 失败拦截),并能基于 插件源码 与 测试用例 深入理解其底层实现,快速在自己的 Route 上落地部署。
插件机制概述
forward-auth实现的是经典的"外部认证(Forward Authentication)"模型:APISIX 在收到用户请求后,先将请求转发给一个独立的认证服务,同时阻塞原始请求;只有当认证服务返回 2xx 状态码时才放行原始请求,否则由网关将认证服务的响应(状态码与自定义响应头)直接替换给客户端,实现自定义错误信息或重定向到登录页等场景。
这种架构将认证与授权逻辑与 API 网关彻底解耦,认证策略的变更只需修改外部服务,无需动网关配置。从源码看,该插件的名称为forward-auth、版本为 0.1,优先级为 2002(apisix/plugins/forward-auth.lua#L72-L77),在插件执行链中处于较高的执行顺序。
属性(Attributes)详解
插件的完整配置项如下表所示(默认值、取值范围均与 schema 定义 保持一致):
| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
|---|---|---|---|---|---|
| uri | string | 是 | 认证服务(authorization service)的地址,例如https://localhost:9188。这是唯一必填项,schema 校验为required = {"uri"} | ||
| ssl_verify | boolean | 否 | true | 当设置为true时,验证 SSL 证书;请求 HTTPS 认证服务时建议保持默认开启 | |
| request_method | string | 否 | GET | ["GET","POST"] | 客户端向认证服务发送请求的方法。当设置为POST时,会将客户端request body一并转发给认证服务 |
| request_headers | array[string] | 否 | 需要由客户端转发到认证服务的请求头白名单。如果没有设置,则只发送 APISIX 自动生成的 headers(如X-Forwarded-XXX系列) | ||
| upstream_headers | array[string] | 否 | 认证通过时,认证服务响应中需要转发给 Upstream 的响应头。如果不设置则不转发任何响应头 | ||
| client_headers | array[string] | 否 | 认证失败时,由认证服务向客户端发送的响应头(用于自定义错误提示、重定向 Location 等)。如果不设置则不转发任何响应头 | ||
| timeout | integer | 否 | 3000ms | [1, 60000]ms | 认证服务 HTTP 调用超时时间(毫秒) |
| keepalive | boolean | 否 | true | 是否启用 HTTP 长连接,为多个请求复用连接 | |
| keepalive_timeout | integer | 否 | 60000ms | [1000, ...]ms | 长连接空闲超时时间(毫秒),超过后连接被关闭 |
| keepalive_pool | integer | 否 | 5 | [1, ...] | 长连接池大小上限 |
| allow_degradation | boolean | 否 | false | 当设置为true时,允许在认证服务器不可用时跳过认证直接放行(降级容错) | |
| status_on_error | integer | 否 | 403 | [200,...,599] | 认证服务出现网络错误时返回给客户端的 HTTP 状态码,默认 403 |
其中request_method的枚举约束、timeout的 [1, 60000] 毫秒区间、status_on_error的 [200, 599] 区间,都直接来源于 schema 定义。在 sanity 测试 中可以看到,缺失uri会报property "uri" is required,uri传数字会报类型错误,request_method传PUT会报matches none of the enum values,request_headers传字符串而非数组也会被拒绝——说明这些参数在配置阶段即被严格校验。
安全相关的配置校验
在check_schema阶段,插件除了做基础 schema 校验外,还调用了两条安全检查逻辑(apisix/plugins/forward-auth.lua#L80-L86):
core.utils.check_https({"uri"}, conf, _M.name):检测uri中是否混用http://与https://等不安全写法(apisix/core/utils.lua#L423-L444);core.utils.check_tls_bool({"ssl_verify"}, conf, _M.name):当ssl_verify被显式设为false时输出安全风险告警日志(apisix/core/utils.lua#L447-L462),提示关闭 TLS 校验属于安全风险。
数据定义:网关自动生成的转发请求头
APISIX 在调用认证服务时,会自动生成并发送以下五类请求头(apisix/plugins/forward-auth.lua#L90-L96),帮助认证服务还原原始请求的上下文:
| Scheme | HTTP Method | Host | URI | Source IP |
|---|---|---|---|---|
| X-Forwarded-Proto | X-Forwarded-Method | X-Forwarded-Host | X-Forwarded-Uri | X-Forwarded-For |
它们的取值分别来自core.request.get_scheme(ctx)、core.request.get_method()、core.request.get_host(ctx)、ctx.var.request_uri与core.request.get_remote_client_ip(ctx)。值得注意的是:这些生成的头字段优先于request_headers白名单中的同名配置——在 TEST 6 中,客户端即使伪造了X-Forwarded-Host: apisix.apache.org,认证服务收到的仍然是网关计算的X-Forwarded-Host: localhost,从测试断言response_body_unlike可以看出客户端伪造值被忽略,保证了认证请求上下文不可被客户端篡改。
当request_method设置为POST时,插件还会额外透传Content-Length、Expect、Transfer-Encoding、Content-Encoding等与请求体相关的头字段(apisix/plugins/forward-auth.lua#L98-L103)。这在 forward-auth2.t 的测试中有专门验证:POST模式下认证服务能收到Content-Length/Transfer-Encoding/Content-Encoding,而GET模式下则不会收到。
使用示例:完整上手流程
下面以官方文档示例为主线,完整演示从搭建认证服务到验证三种认证结果的整个流程。
前提准备:获取 admin_key
Admin API 请求需要携带X-API-KEY头,可以从config.yaml中提取并存入环境变量:
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')第一步:搭建外部认证服务
这里使用 APISIX 自身的 serverless-pre-function 插件模拟一个认证服务:它读取请求的Authorization头,值为123时认证通过(返回 200),值为321时认证通过并附带X-User-ID响应头,否则返回 403 并携带Location重定向头:
curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/auth' \ -H "X-API-KEY: $admin_key" \ -H 'Content-Type: application/json' \ -d '{ "uri": "/auth", "plugins": { "serverless-pre-function": { "phase": "rewrite", "functions": [ "return function (conf, ctx) local core = require(\"apisix.core\"); local authorization = core.request.header(ctx, \"Authorization\"); if authorization == \"123\" then core.response.exit(200); elseif authorization == \"321\" then core.response.set_header(\"X-User-ID\", \"i-am-user\"); core.response.exit(200); else core.response.set_header(\"Location\", \"http://example.com/auth\"); core.response.exit(403); end end" ] } } }'第二步:在 Route 上启用 forward-auth 插件
将forward-auth插件挂载到目标 Route(/headers)上,并把上游指向httpbin.org。这里的配置同时用到了三类头白名单:
curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/1' \ -H "X-API-KEY: $admin_key" \ -d '{ "uri": "/headers", "plugins": { "forward-auth": { "uri": "http://127.0.0.1:9080/auth", "request_headers": ["Authorization"], "upstream_headers": ["X-User-ID"], "client_headers": ["Location"] } }, "upstream": { "nodes": { "httpbin.org:80": 1 }, "type": "roundrobin" } }'配置含义:客户端请求中的Authorization头会透传给认证服务;认证通过时,认证服务响应里的X-User-ID头会被注入到发往 Upstream 的请求中;认证失败时,认证服务响应里的Location头会被回传给客户端。
第三步:验证三种认证结果
场景一:认证通过,正常转发到 Upstream
curl http://127.0.0.1:9080/headers -H 'Authorization: 123'{ "headers": { "Authorization": "123", "Next": "More-headers" } }认证服务返回 200,原始请求被放行,Upstream(httpbin)正常返回其收到的请求头。
场景二:认证通过,且把认证服务的响应头透传给 Upstream
curl http://127.0.0.1:9080/headers -H 'Authorization: 321'{ "headers": { "Authorization": "321", "X-User-ID": "i-am-user", "Next": "More-headers" } }由于配置了upstream_headers: ["X-User-ID"],认证服务返回的X-User-ID: i-am-user被 core.request.set_header 写入转发请求,最终出现在 Upstream 收到的请求头中——这是典型的"认证服务向业务服务传递用户身份"模式。
场景三:认证失败,自定义响应回给客户端
curl -i http://127.0.0.1:9080/headersHTTP/1.1 403 Forbidden Location: http://example.com/auth认证服务返回非 2xx(这里是 403),插件立刻终止原始请求,将认证服务的状态码与client_headers白名单中的Location头替换给客户端。Location头可用于实现"重定向到登录页"的经典场景。
底层实现:access 阶段的关键逻辑
forward-auth的全部认证逻辑集中在access阶段(apisix/plugins/forward-auth.lua#L89-L167),其核心流程可概括为:
- 组装请求头:生成
X-Forwarded-*系列头,追加request_headers白名单中的客户端请求头,POST模式下再补充请求体相关头; - 发起认证调用:通过
resty.http的request_uri向conf.uri发起请求,ssl_verify、keepalive、timeout等参数一并生效;POST模式下优先使用get_client_body_reader()流式转发请求体,失败时回退到core.request.get_body()(apisix/plugins/forward-auth.lua#L121-L132),这保证了 11MB 级别的大请求体也能被正确转发——对应 TEST 14:test large body; - 异常降级处理:如果认证调用本身失败(网络错误、连接被拒等),且
allow_degradation为true,则直接放行(跳过认证);否则记录告警日志并以status_on_error指定的状态码(默认 403)结束请求(apisix/plugins/forward-auth.lua#L139-L145)。TEST 11 与 TEST 12 分别验证了认证服务不可用时的"返回 403"与"降级放行 200"两种行为; - 按状态码分流:
res.status >= 300时视为认证失败,把client_headers白名单中的响应头设置到客户端响应并返回认证服务的状态码与 body;2xx 时则把upstream_headers白名单中的响应头注入后续的 Upstream 请求(apisix/plugins/forward-auth.lua#L147-L166)。
注意:
client_headers与upstream_headers的生效是有条件的——测试 TEST 7 和 TEST 8 表明,当 Route 未配置对应白名单时,认证服务返回的头不会被透传,白名单即"转发开关"。
另外,keepalive相关的keepalive_timeout、keepalive_pool只有在conf.keepalive为true时才会被写入请求参数(apisix/plugins/forward-auth.lua#L134-L137),与属性表中的默认值语义一致。
删除插件
需要禁用forward-auth插件时,只需将 Route 配置中的plugins置空(或移除该插件的 JSON 配置块)重新 PUT 即可。APISIX 会自动热加载配置变更,无需重启服务:
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d ' { "methods": ["GET"], "uri": "/hello", "plugins": {}, "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }'小结
forward-auth插件的核心价值在于:把认证/授权从网关中抽离为独立服务,通过X-Forwarded-*系列头还原请求上下文,借助request_headers/upstream_headers/client_headers三组白名单精确控制"客户端 → 认证服务 → Upstream → 客户端"各环节的信息流动,同时用timeout、keepalive、allow_degradation、status_on_error覆盖了超时、连接复用与故障降级等生产环境关键诉求。
若需深入源码,可重点阅读 插件实现(schema 校验与 access 逻辑)、工具校验函数(HTTPS 与 TLS 安全检查),以及 forward-auth.t 与 forward-auth2.t 两个测试文件(覆盖 header 转发、POST 大请求体、降级与错误码等 19 个测试场景)。
【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考