将外部认证用户信息转发到上游
使用 openid-connect 或 saml-auth 保 护路由时,API7 网关会先对请求进行身份认证,再将其代理到上游。有时上游服务还需要知道请求来自哪个用户。
两个插件都会将已认证用户存储在 ctx.external_user 中。可以配合 serverless-post-function 使用该值:
- 设置
ctx.consumer_name,使日志和其他插件能够使用此身份。 - 为上游服务添加请求头。
对于 OpenID Connect,插件已经可以通过请求头转发用户信息和 Token。需要设置 consumer_name 或为 SAML 构建自定义请求头时,请使用 serverless-post-function。
前置条件
- API7 企业版实例正在运行。
- 已创建网关组且网关实例正在运行。
- 一个 Dashboard Token。
- 路由已配置
openid-connect或saml-auth,且端到端身份认证成功。有关 OpenID Connect,请参阅 OAuth 2.0 和 OIDC。
设置消费者名称
在同一路由的 rewrite 阶段添加 serverless-post-function。默认情况下,它在 openid-connect 和 saml-auth 之后运行,因此函数执行时 ctx.external_user 已填充。如果自定义过插件优先级,请验证执行顺序。
以下函数对 OpenID Connect 使用 sub,对 SAML 使用 name_id,并将其写入 ctx.consumer_name:
phase: rewrite
functions:
- |
return function(conf, ctx)
local user = ctx.external_user
if type(user) ~= "table" then
return
end
local id = user.sub or user.name_id
if id then
ctx.consumer_name = id
end
end
配置下发到网关后,通过路由发送已认证请求。其他插件随后可以读取 ctx.consumer_name。如果希望在访问日志中记录该值,请在日志格式中包含 consumer_name。
将用户信息转发到上游
OpenID Connect
使用标准 OpenID Connect 请求头转发时无需使用 serverless-post-function。插件可以在上游请求中添加以下请求头:
| 标志 | 默认值 | 在上游请求中设置的请求头 |
|---|---|---|
set_userinfo_header | true | X-Userinfo——用户信息文档的 Base64 编码 JSON。 |
set_access_token_header | true | X-Access-Token——原始 Access Token。(将 access_token_in_authorization_header: true 设置为在 Authorization 中传递。) |
set_id_token_header | true | X-ID-Token——流程中存在时的原始 ID Token。 |
set_refresh_token_header | false | X-Refresh-Token——Refresh Token。仅当上游确实需要自行刷新时才启用。 |
将任一标志设置为 false 可禁止相应请求头。有关所有请求头相关选项,请参阅 openid-connect插件参考。
SAML
SAML 属性没有等效的内置请求头。请使用 serverless-post-function 读取 ctx.external_user 并明确设置请求头。先清除请求头,确保客户端提供的值不会到达上游:
phase: rewrite
functions:
- |
return function(conf, ctx)
local core = require("apisix.core")
-- 先清除客户端提供的值。
core.request.set_header(ctx, "X-SAML-NameID", nil)
core.request.set_header(ctx, "X-SAML-Userinfo", nil)
local user = ctx.external_user
if user and user.authenticated then
core.request.set_header(ctx, "X-SAML-NameID", user.name_id or "")
local userinfo = {
name_id = user.name_id,
session_index = user.session_index,
issuer = user.issuer,
attrs = user.attrs,
}
core.request.set_header(
ctx,
"X-SAML-Userinfo",
ngx.encode_base64(core.json.encode(userinfo))
)
end
end
此示例发送:
X-SAML-NameID——用于快速查找的原始 NameID。X-SAML-Userinfo——包含 NameID、会话索引、IdP 颁发者和 IdP 属性映射的 Base64 编码 JSON 文档。
请限制对上游服务的直接访问,确保仅信任经过 API7 网关的请求中的这些请求头。
在 SAML 路由上同时使用两种模式
如果路由同时需要 consumer_name 和 SAML 请求头,请将逻辑合并到一个 serverless-post-function 实例中:
phase: rewrite
functions:
- |
return function(conf, ctx)
local core = require("apisix.core")
local user = ctx.external_user
if type(user) ~= "table" then
return
end
local id = user.sub or user.name_id
if id then
ctx.consumer_name = id
end
core.request.set_header(ctx, "X-SAML-NameID", nil)
core.request.set_header(ctx, "X-SAML-Userinfo", nil)
if user.authenticated and user.name_id then
core.request.set_header(ctx, "X-SAML-NameID", user.name_id)
local userinfo = {
name_id = user.name_id,
session_index = user.session_index,
issuer = user.issuer,
attrs = user.attrs,
}
core.request.set_header(
ctx,
"X-SAML-Userinfo",
ngx.encode_base64(core.json.encode(userinfo))
)
end
end
在 OpenID Connect 路由上,只保留 consumer_name 代码块,并使用插件内置的请求头标志。
验证结果
应用配置后,确认上游收到预期用户信息,并且 consumer_name 在所需位置可用。
检查上游请求头
- 将路由指向
httpbin.org/anything等请求回显上游,使响应体列出上游收到的所有请求头。 - 通过路由发送已认证请求。
— 对于设置了
bearer_only: true的openid-connect,从 IdP 获取 Access Token,并使用-H "Authorization: Bearer <token>"调用路由。 — 对于saml-auth或交互式 OpenID Connect 流程,请在浏览器中完成登录。 - 检查上游响应中回显的请求头。
— 对于 OpenID Connect,查找
X-Userinfo、X-Access-Token,以及流程中存在时的X-ID-Token。 — 对于 SAML,查找函数设置的请求头,如X-SAML-NameID和X-SAML-Userinfo。
检查 consumer_name
如果设置了 ctx.consumer_name,请确认该值出现在部署暴露它的位置,例如访问日志字段或读取消费者身份的其他插件。
故障排除
如果上游请求头或 consumer_name 未按预期出现,请检查 ctx.external_user 实际包含的内容。将以下 serverless-post-function 挂载到路由 ,发送一个已认证请求,然后读取网关错误日志:
phase: rewrite
functions:
- |
return function(conf, ctx)
local core = require("apisix.core")
core.log.warn("ctx.external_user: ", core.json.encode(ctx.external_user, true))
end
具体字段集取决于 IdP、请求的作用域,以及 IdP 端配置的声明或属性映射器。典型字段如下:
| 插件 | 常见字段 |
|---|---|
openid-connect | sub、email、email_verified、name、preferred_username、given_name、family_name、iss、aud,以及 IdP 返回的额外声明,例如 Keycloak 的 realm_access 和 resource_access,或 Auth0 的 roles。 |
saml-auth | name_id、session_index、issuer、attrs(IdP 属性映射)、authenticated。 |
如果值为空或为 nil,说明身份认证插件没有在 serverless-post-function 之前运行,或请求在到达 rewrite 阶段前已被拒绝。请检查路由插件列表和 IdP 端配置。如果值已填充,但读取的字段(sub、name_id 或自定义声明)缺失,请调整 IdP 的声明或属性映射器,或从输出中读取其他字段。
如果配置看起来正确但未发生变化,请等待几秒,让最新配置下发到网关后重试。
后续步骤
- OAuth 2.0 和 OIDC——端到端配置由 IdP 支持的路由。
- 消费者和凭证——了解 API7 网关如何原生跟踪身份。
- 在访问日志中包含消费者标签——在网关访问日志中呈现消费者身份及任意消费者标签。
serverless-functions插件参考——了解 Lua 代码片段可用的执行阶段和ctxAPI。