Traefik Hub API Gateway 的 JWT 认证中间件全指南:从共享密钥验签、IdP/JWKS 集成到 Claims 级授权控制
本文基于当前仓库 docs/content/secure/secure-api-access-with-jwt.md 及其配套参考文档 docs/content/reference/routing-configuration/http/middlewares/jwt.md 展开。它面向使用 Traefik Hub API Gateway 的开发者,讲解如何通过 JWT 认证中间件为 Kubernetes Ingress 之上的 API 增加令牌级安全防护。读完本文,你将掌握:四种 JWT 验签来源(
signingSecret/publicKey/jwksFile/jwksUrl)的选型与配置、如何把 Kubernetes Secret 作为签名密钥喂给中间件、如何对接 Keycloak / Azure AD 等身份提供商(IdP),以及如何利用claims规则把令牌中的声明(claim)变成细粒度的授权决策。
适用范围与前提:根据本文档的说明,该中间件属于 Traefik Hub 的独有能力,以 spec.plugin.jwt 的插件形态运行在 Hub API Gateway 上,并非 Traefik OSS 内核内置组件(当前仓库的 pkg/middlewares 目录中不存在对应实现)。不过其基于的 traefik.io/v1alpha1 CRD 机制、urn:k8s:secret:... 密钥引用解析能力,在仓库的 Kubernetes CRD provider 源码中有完整的实现可查证。文章中的全部配置示例与行为描述均以本仓库文档为准。
JWT 中间件的职责与默认行为
在 Traefik Hub API Gateway 中,JWT(JSON Web Token,RFC 7519 定义)认证中间件负责校验访问请求中携带的令牌:它验证请求的 Authorization 头中是否存在合法的 Bearer 令牌(形如 Authorization: Bearer <JWT>),并校验其签名与有效期,从而决定该请求是否被放行到上游应用。
默认(不做任何额外配置)情况下,该中间件只做两件事:
- 校验 JWT 的签名——确保令牌确实由受信任方签发、未被篡改;
- 校验标准声明——如果令牌中存在
nbf(not before)、exp(expiration)、iat(issued at)这三个标准声明,则检查其时间有效性。
需要自定义声明校验时,可通过 Claims 授权 一节介绍的 claims 选项实现。
令牌的三种携带方式
绝大多数场景下,客户端通过 Authorization 头发送令牌。参考文档特别说明:中间件永远优先在 Authorization 头中查找令牌;对于无法把令牌放进 Authorization 头的应用,才允许通过 tokenKey 选项指定一个 query 参数 / 表单参数名来传递 JWT。
⚠️ 安全提示(见参考文档
tokenKey字段):把令牌放进 URL query 或表单虽然被支持,但不符合 RFC 6750 §2 的推荐做法(令牌可能出现在日志与浏览器历史中),应仅在确实无法使用Authorization头时才开启。
令牌验签的四种来源与“单一来源”约束
中间件通过以下四种途径之一取得验签所需的密钥材料,对应四个互斥的配置项:
| 配置项 | 含义 | 典型场景 |
|---|---|---|
signingSecret |
直接配置用于 HMAC 对称签名的共享密钥(secret 值) | 内部服务之间用共享密钥签发的 HS256 类令牌 |
publicKey |
配置公钥,客户端用对应私钥签名、网关用公钥验签 | 非对称 RS/ES 签名体系(自管密钥对) |
jwksFile |
配置一个 JWK(JSON Web Key,RFC 7517)集合文件,可填文件路径或直接内联 JWK Set 内容 | 静态导入身份提供商的密钥集合 |
jwksUrl |
配置提供 JWK Set 的服务 URL,动态拉取密钥 | 对接 Keycloak、Azure AD 等 IdP(见下文) |
⚠️ 单一来源约束:以上四者最多只能设置一个。中间件只会用配置中指定的这一种来源校验收到的令牌,校验通过即放行请求。若同时配置多个来源,会产生歧义并违背中间件的设计约束。
此外,若配置的是共享密钥,还可用 signingSecretBase64Encoded 开关(默认 false)声明该 signingSecret 是否为 base64 编码——置为 true 时中间件会先做 base64 解码再用于验签。
实战一:用 Kubernetes Secret 中的共享密钥验证 JWT
当密钥以 Kubernetes Secret 形式托管时,可让中间件以 URN 形式引用该 Secret,从而避免把明文密钥写死在 Middleware 清单里。整套配置由四个 YAML 片段组成。
1. Middleware:引用 Secret
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-jwt
namespace: apps
spec:
plugin:
jwt:
signingSecret: "urn:k8s:secret:jwt:signingSecret"
2. Kubernetes Secret:存放密钥明文
apiVersion: v1
kind: Secret
metadata:
name: jwt
namespace: apps
stringData:
signingSecret: mysuperlongsecret
3. IngressRoute:把中间件挂到路由上
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: my-app
namespace: apps
spec:
entryPoints:
- websecure
routes:
- match: Path(`/my-app`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: test-jwt
4. 被保护的后端服务与部署
kind: Deployment
apiVersion: apps/v1
metadata:
name: whoami
namespace: apps
spec:
replicas: 3
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: traefik/whoami
---
apiVersion: v1
kind: Service
metadata:
name: whoami
namespace: apps
spec:
ports:
- port: 80
name: whoami
selector:
app: whoami
三段资源的协作关系是:IngressRoute(my-app)把指向 Path(/my-app) 的请求交给 test-jwt 中间件先做验签;中间件按 URN 从同命名空间的 jwt Secret 中取出 signingSecret 字段的值(上例为 mysuperlongsecret)作为 HMAC 密钥校验 Authorization: Bearer ... 令牌;校验通过后才把请求转发给 whoami 服务(镜像 traefik/whoami)。
底层机制:URN 密钥引用的源码实现
urn:k8s:secret:[secretName]:[dataKey] 这种“同命名空间 Secret 引用”并非 JWT 插件独有,而是 Kubernetes CRD provider 对 所有插件类型 Middleware 提供的通用解析能力。从源码看(pkg/provider/kubernetes/crd/kubernetes.go):
createPluginMiddleware(L745-L769)在把 Middleware 的spec.plugin序列化进动态配置前,会遍历每个插件的所有配置值并调用loadSecretKeys;loadSecretKeys(L771-L797)会对字符串、字符串数组、任意嵌套的 map 做递归扫描——只要某个字符串值以urn:k8s:secret:前缀开头,就触发 Secret 读取;getSecretValue(L799-L822)解析 URN:格式必须为 5 段(urn : k8s : secret : 名称 : 键),随后从 Middleware 所在的同一命名空间 取该 Secret,再按数据键取值;Secret 不存在、键不存在或 URN 格式错误都会直接返回错误。
仓库测试固件 pkg/provider/kubernetes/crd/fixtures/with_plugin_read_secret.yml 给出了一个最小可读的对照样例:plugin.test-secret.secret: urn:k8s:secret:name:key 指向 name Secret 的 key 数据项(dGhpc19pc190aGVfc2VjcmV0)。这意味着 signingSecret、以及后文 clientConfig.tls 下的证书字段,都可以放心地以 Secret 方式托管,由网关侧自动解析注入。
实战二:通过 Identity Provider(IdP)验证 JWT
当令牌由 Keycloak、Azure AD 等外部身份提供商签发时,你往往没有对称共享密钥,而应该拉取 IdP 发布的 JWK Set(内含用于验签的公钥)。此时配置 jwksUrl 指向 IdP 的证书端点即可。参考文档给出了两个可直接套用的例子。
Keycloak 场景(含 Claims 与请求头转发)
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-jwt
namespace: apps
spec:
plugin:
jwt:
# Replace KEYCLOAK_URL and REALM_NAME with your values
jwksUrl: https://KEYCLOAK_URL/realms/REALM_NAME/protocol/openid-connect/certs
# Forward the content of the claim grp in the header Group
forwardHeaders:
Group: grp
# Check the value of the claim grp before sending the request to the backend
claims: Equals(`grp`, `admin`)
上例同时演示了两个高频能力:
jwksUrl:Keycloak 的标准 OIDC 证书端点路径为https://<KEYCLOAK_URL>/realms/<REALM_NAME>/protocol/openid-connect/certs,需把两个占位符替换成真实值;forwardHeaders与claims:网关验签通过后,把令牌中grp声明的值写入请求头Group转发给后端;同时在转发前用claims: Equals(grp, admin)做一层授权闸门(详见下一节)。
Azure AD 场景
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-jwt
namespace: apps
spec:
plugin:
jwt:
jwksUrl: https://login.microsoftonline.com/common/discovery/v2.0/keys
Azure AD v2.0 的全局签名密钥端点为 https://login.microsoftonline.com/common/discovery/v2.0/keys。对应的 IngressRoute 与 Service & Deployment 清单与“实战一”完全相同(中间件名称改为 test-jwt 即可),此处不再重复列出。
性能提示:参考文档指出,通过
jwksUrl获取的密钥会按 HTTP 响应中的 Cache-Control 头决定是否缓存,从而避免每个请求都回源 IdP 拉取密钥。
基于 Claims 的授权校验与请求放行决策
JWT 以键值对(claim)形式携带元数据,例如用户所属组、作用域(scope)、来源等。claims 选项把这些元数据变成“是否放行”的判据,是实现声明式授权(claims-based authorization)的关键。
支持的校验函数
| 函数 | 作用 | 示例 |
|---|---|---|
Equals |
校验 key 的值等于 value |
Equals(\grp`, `admin`)` |
Prefix |
校验 key 的值以 value 为前缀 |
Prefix(\referrer`, `http://example.com`)` |
Contains(字符串) |
校验 key 的字符串值包含 value |
Contains(\referrer`, `/foo/`)` |
Contains(数组) |
校验 key 对应的数组包含 value |
Contains(\areas`, `home`)` |
SplitContains |
把 key 的值按分隔符切分后,判断其中是否含有 value |
SplitContains(\scope`, ` `, `writer`)` |
OneOf |
校验 key 数组包含传入值中的至少一个 |
OneOf(\areas`, `office`, `lab`)` |
布尔组合操作符
多个函数可用逻辑操作符自由组合:
| 操作符 | 语义 | 示例 |
|---|---|---|
&& |
两个函数均为真才通过 | Equals(\grp`, `admin`) && Equals(`active`, `true`)` |
|| |
任一函数为真即通过 | Equals(\grp`, `admin`) || Equals(`active`, `true`)` |
! |
逻辑取反 | !Equals(\grp`, `testers`)` |
下面这份声明数据可以让上述所有示例函数都返回 true,适合用来逐条推演规则语义:
{
"active": true,
"grp": "admin",
"scope": "reader writer deploy",
"referrer": "http://example.com/foo/bar",
"areas": [
"office",
"home"
]
}
例如 SplitContains(\scope`, ` `, `writer`)即把"reader writer deploy"按空格拆分为["reader", "writer", "deploy"]后再判断是否含writer`。
嵌套声明与特殊字符转义
claims 支持点号(.)路径访问嵌套声明。例如 key = user.name,会取到如下 JSON 中的 "John Snow":
{
"active": true,
"grp": "admin",
"scope": "reader writer deploy",
"referrer": "http://example.com/foo/bar",
"areas": [
"office",
"home"
],
"user": {
"name": "John Snow",
"status": "undead"
}
}
两个边界情况需要转义规则:
- 若 key 本身含有
.,则必须用\.转义,例如my\.key,避免被误解析为嵌套路径; - 若 key 本身含有
\,则必须写为双反斜杠\\。
与后端共享身份信息:forwardHeaders 与 usernameClaim
forwardHeaders:把令牌声明映射为转发给后端的 HTTP 头。例如 Keycloak 示例中的Group: grp表示“取grp声明填入Group头”。注意:声明缺失时对应的头会以空值转发,而不是报错。usernameClaim:指定某个声明用于在访问日志中填充clientusername字段,便于审计“谁在调用哪个 API”。
完整字段矩阵可查阅 jwt.md 参考页,其配置选项表覆盖了 signingSecret、publicKey、jwksFile、jwksUrl、forwardAuthorization、tokenKey、claims、usernameClaim、forwardHeaders 以及 clientConfig.tls.* / clientConfig.timeoutSeconds / clientConfig.maxRetries 等全部选项及其默认值。
高级:与授权服务器之间的 mTLS(clientConfig)
当 Hub API Gateway 与第三方软件(如 IdP、Vault 类系统)通信需要双向 TLS 时,通过 clientConfig.tls 配置客户端证书:
| 字段 | 说明 |
|---|---|
clientConfig.tls.ca |
PEM 编码的 CA 证书包,或引用 Secret 的 URN |
clientConfig.tls.cert |
PEM 编码的客户端证书,或引用 Secret 的 URN |
clientConfig.tls.key |
PEM 编码的客户端私钥,或引用 Secret 的 URN |
clientConfig.tls.insecureSkipVerify |
关闭对授权服务器的 TLS 证书校验,默认 false;仅供测试,生产环境强烈不建议 |
clientConfig.timeoutSeconds |
向授权服务器发起请求的超时时间,默认 5 秒 |
clientConfig.maxRetries |
授权服务器请求失败时的重试次数,默认 3 |
这些证书字段同样支持 urn:k8s:secret:[name]:[valueKey] 形式的 Secret 引用(Secret 须与 Middleware 同命名空间)。一个完整示例:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-jwt
spec:
plugin:
jwt:
clientConfig:
tls:
ca: "urn:k8s:secret:tls:ca"
cert: "urn:k8s:secret:tls:cert"
key: "urn:k8s:secret:tls:key"
insecureSkipVerify: true
对应的 tls Secret 把 ca、cert、key 三个数据项分别存放 PEM 块(证书块用 |- 块标量保留换行,参考文档给出了可直接套用的完整 PEM 样例)。
JWKS 校验的边界行为(可直接验证的约定)
参考文档对 JWKS 场景明确了三条边界行为,排查 401 时非常有用:
- JWT 头部带
kid:当 JWT 头中包含kid(Key ID)时,中间件会期望从jwksFile/jwksUrl提供的 JWK 集合中按该kid找到对应 JWK 来完成验签; - 找不到匹配的 JWK:若按
kid找不到对应 JWK,中间件直接返回401 Unauthorized; iss声明缺失:使用jwksUrl校验时,若待校验的 JWT 缺少iss(签发者)声明,中间件同样返回401 Unauthorized。
因此对接 IdP 时,请确保你的 IdP 在签发的令牌中同时携带 kid 头与 iss 声明,且 jwksUrl 指向能覆盖该签发者的 JWK 端点(多租户场景尤其要核对端点路径)。
与 OIDC 认证的定位差异
如果你需要的是“浏览器重定向登录”的完整认证流程(中间件把未认证用户重定向到 IdP 登录页,登录成功后再回跳并放行),那是 OIDC 认证中间件的职责,相关指南见 secure-api-access-with-oidc.md;而本文的 JWT 中间件解决的是“令牌已存在、如何验签与授权”的问题,两者可互补使用。更完整的选项矩阵(含每项默认值与语义说明)始终以 reference 页 jwt.md 为最终依据。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00