首页
/ Traefik Hub API Gateway 的 JWT 认证中间件全指南:从共享密钥验签、IdP/JWKS 集成到 Claims 级授权控制

Traefik Hub API Gateway 的 JWT 认证中间件全指南:从共享密钥验签、IdP/JWKS 集成到 Claims 级授权控制

2026-09-07 23:50:11作者:卓炯娓

本文基于当前仓库 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>),并校验其签名与有效期,从而决定该请求是否被放行到上游应用。

默认(不做任何额外配置)情况下,该中间件只做两件事:

  1. 校验 JWT 的签名——确保令牌确实由受信任方签发、未被篡改;
  2. 校验标准声明——如果令牌中存在 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

三段资源的协作关系是:IngressRoutemy-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):

  • createPluginMiddlewareL745-L769)在把 Middleware 的 spec.plugin 序列化进动态配置前,会遍历每个插件的所有配置值并调用 loadSecretKeys
  • loadSecretKeysL771-L797)会对字符串、字符串数组、任意嵌套的 map 做递归扫描——只要某个字符串值以 urn:k8s:secret: 前缀开头,就触发 Secret 读取;
  • getSecretValueL799-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,需把两个占位符替换成真实值;
  • forwardHeadersclaims:网关验签通过后,把令牌中 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 本身含有 \,则必须写为双反斜杠 \\

与后端共享身份信息:forwardHeadersusernameClaim

  • forwardHeaders:把令牌声明映射为转发给后端的 HTTP 头。例如 Keycloak 示例中的 Group: grp 表示“取 grp 声明填入 Group 头”。注意:声明缺失时对应的头会以空值转发,而不是报错。
  • usernameClaim:指定某个声明用于在访问日志中填充 clientusername 字段,便于审计“谁在调用哪个 API”。

完整字段矩阵可查阅 jwt.md 参考页,其配置选项表覆盖了 signingSecretpublicKeyjwksFilejwksUrlforwardAuthorizationtokenKeyclaimsusernameClaimforwardHeaders 以及 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 把 cacertkey 三个数据项分别存放 PEM 块(证书块用 |- 块标量保留换行,参考文档给出了可直接套用的完整 PEM 样例)。

JWKS 校验的边界行为(可直接验证的约定)

参考文档对 JWKS 场景明确了三条边界行为,排查 401 时非常有用:

  1. JWT 头部带 kid:当 JWT 头中包含 kid(Key ID)时,中间件会期望从 jwksFile / jwksUrl 提供的 JWK 集合中按该 kid 找到对应 JWK 来完成验签;
  2. 找不到匹配的 JWK:若按 kid 找不到对应 JWK,中间件直接返回 401 Unauthorized
  3. 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 为最终依据。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388