☰
使用 Google 服务账号签发 JWT:Istio 端用户认证测试工具 sa-jwt.py 实战指南
2026/10/14 15:53:10 网站建设 项目流程

使用 Google 服务账号签发 JWT:Istio 端用户认证测试工具 sa-jwt.py 实战指南

【免费下载链接】istioConnect, secure, control, and observe services.项目地址: https://gitcode.com/GitHub_Trending/is/istio

Istio 的RequestAuthentication(请求认证策略)依赖签发方与验签公钥(JWKS)来验证终端用户携带的 JWT。本指南以仓库中 security/tools/jwt/README.md 为骨架,完整讲解其中的sa-jwt.py工具——如何用 Google 服务账号私钥签发结构正确、可被 Istio 校验的 RS256 JWT,并深入源码说明其 payload 构造与签名原理,同时对比配套的本地私钥签发工具gen-jwt.py,帮助你快速获得可用于 Istio 端用户认证测试的合法 Token。

工具定位:为 Istio 端用户认证"造出可信 Token"

Istio 的安全模型分两层:服务间认证(mTLS)与端用户认证(end-user authentication)。后者通过security.istio.io/v1的RequestAuthentication资源声明issuer(签发方)与jwksUri(验签公钥集地址),由 istiod 拉取 JWKS 后对到达数据面的Authorization: Bearer <JWT>请求头做本地验签。要验证这一整套链路,前提是手里有一枚"由可信签发方私钥签出的 JWT"。

security/tools/jwt/ 目录正是为此准备的造 Token 工具箱,包含两类工具与一套演示数据:

文件作用
sa-jwt.py用Google 服务账号私钥签发 JWT(本文主角,对应顶层 README)
requirements.txt声明sa-jwt.py的唯一依赖google-auth
samples/gen-jwt.py用本地任意 RSA 私钥签发 JWT,并可生成配套 JWKS
samples/key.pem / samples/jwks.json演示用私钥与对应公钥集(issuer 固定为testing@secure.istio.io)
samples/demo.jwt / samples/groups-scope.jwt预签发的可直接使用测试 Token

两者覆盖两种典型场景:生产/联调中希望以真实 Google 身份体系签发(sa-jwt.py);本地功能自测中希望用自带私钥快速伪造任意 issuer 的 Token(gen-jwt.py)。

环境准备:安装依赖并取得 Google 服务账号私钥

1. 安装 Python 依赖

sa-jwt.py基于 Google 官方认证库实现,仅依赖一个包。可直接使用仓库中现成的依赖声明文件:

pip install google-auth

或使用仓库内的 requirements.txt:

pip install -r security/tools/jwt/requirements.txt

2. 创建(或选用)Google 服务账号并下载私钥

在 Google Cloud Console 的 IAM 与服务账号页面:

  1. 点击CREATE SERVICE ACCOUNT新建一个服务账号;若已存在可复用的服务账号则直接选用;
  2. 在该服务账号的下拉菜单中选择Create Key(创建密钥);
  3. 密钥类型选择默认的JSON;
  4. 浏览器会自动下载一份 JSON 格式的服务账号密钥文件(内含私钥等敏感材料),后续把它作为service_account_file位置参数传给脚本。

注意:该 JSON 密钥等同于账号私钥,请妥善保管,切勿提交进代码仓库。

sa-jwt.py 用法:从帮助输出到参数逐项解读

先查看帮助信息确认脚本的完整接口:

python sa-jwt.py -h

输出如下(README 中记录的原文,注意脚本自带 shebang,加执行权限后也可直接用./sa-jwt.py):

usage: ./sa-jwt.py [-h] [-iss ISS] [-aud AUD] [-sub SUB] [-claims CLAIMS] service_account_file Python script generates a JWT signed by a Google service account positional arguments: service_account_file The path to your service account key file (in JSON format). optional arguments: -h, --help show this help message and exit -iss ISS, --iss ISS iss claim. This should be your service account email. -aud AUD, --aud AUD aud claim. This is comma-separated-list of audiences. -sub SUB, --sub SUB sub claim. If not provided, it is set to the same as iss claim. -claims CLAIMS, --claims CLAIMS Other claims in format name1:value1,name2:value2 etc. Only string values are supported.

对照 sa-jwt.py 的参数解析代码,各参数语义如下:

参数类型语义默认/约束
service_account_file必填位置参数Google 服务账号密钥文件路径(JSON 格式)无,缺失即报错
-iss/--iss可选JWT 的iss声明,应填服务账号邮箱不传则 payload 不含iss(同时会导致sub为空)
-aud/--aud可选JWT 的aud声明,逗号分隔表示多个受众含逗号时自动解析为字符串数组(见下文)
-sub/--sub可选JWT 的sub声明不传时自动置为与iss相同
-claims/--claims可选追加自定义声明,格式name1:value1,name2:value2仅支持字符串值

源码拆解:sa-jwt.py 内部如何构造与签名

阅读 sa-jwt.py 的main()函数,可以完整还原其三步工作流:

第一步:从服务账号 JSON 加载 RSA 签名器

signer = google.auth.crypt.RSASigner.from_service_account_file( args.service_account_file)

google.auth.crypt.RSASigner.from_service_account_file会解析下载到的 JSON 密钥中的private_key字段,构造 RSA 私钥签名器——这是整个脚本能以"Google 服务账号"名义签名的关键,后续 JWT 将使用该私钥做 RS256 签名。

第二步:构造 payload,内置 iat/exp 与多项默认规则

now = int(time.time()) payload = { # expire in one hour. "exp": now + 3600, "iat": now, } if args.iss: payload["iss"] = args.iss if args.sub: payload["sub"] = args.sub else: payload["sub"] = args.iss if args.aud: if "," in args.aud: payload["aud"] = args.aud.split(",") else: payload["aud"] = args.aud if args.claims: for item in args.claims.split(","): k, v = item.split(':') payload[k] = v

源码层面体现了几条 README 未展开的硬性规则,实测时务必注意:

  • iat与exp由脚本自动填充:iat为当前 Unix 时间戳,exp = now + 3600,即Token 默认有效期仅 1 小时,且不可通过命令行覆盖;
  • sub的默认行为是"跟随"iss:-sub未指定时payload["sub"] = args.iss,保证声明一致性;
  • aud的智能处理:单个值存为字符串,含逗号时用split(",")转成字符串数组——RFC 7519 允许aud为字符串或字符串数组,Istio 验签时按数组逐一比对即可命中;
  • -claims的自定义声明仅支持字符串值:每个条目按item.split(':')切出键值对直接塞入 payload,因此无法通过该参数构造嵌套对象或数字类型。

第三步:编码并签名,输出到标准输出

signed_jwt = google.auth.jwt.encode(signer, payload) return signed_jwt ... print(main(parser.parse_args()))

google.auth.jwt.encode完成 header(alg/typ)拼接、payload Base64URL 编码与 RS256 签名,最终输出标准三段式 JWT 字符串。脚本末尾通过print()将 Token直接打印到 stdout,便于在 shell 中赋值给环境变量或重定向到文件,例如:

./sa-jwt.py /path/to/service_account.json \ -iss frod@gserviceaccount.com -aud foo,bar > token.jwt

动手示例:签发一枚可用的 Bearer Token

最小可用示例

./sa-jwt.py /path/to/service_account.json -iss frod@gserviceaccount.com -aud foo,bar

执行后 stdout 即输出完整 JWT,可立刻用于模拟带认证的请求:

TOKEN=$(./sa-jwt.py /path/to/service_account.json \ -iss frod@gserviceaccount.com -aud foo,bar) curl --header "Authorization: Bearer $TOKEN" http://${INGRESS_HOST}/headers

携带自定义声明

如需在iss/aud/sub之外追加业务声明(例如用于与授权策略request.headers或 JWT claim 条件联动),使用-claims:

./sa-jwt.py /path/to/service_account.json \ -iss frod@gserviceaccount.com -aud foo,bar \ -claims key1:value1,key2:value2

对应生成的 payload 中会包含:

{ "iss": "frod@gserviceaccount.com", "aud": ["foo", "bar"], "sub": "frod@gserviceaccount.com", "key1": "value1", "key2": "value2", "iat": 1700000000, "exp": 1700003600 }

提示:若你手头还没有 Google 服务账号,只想先在本机跑通 Istio 的 JWT 校验链路,可直接使用下一节的本地私钥方案,无需访问 Google。

姊妹工具:gen-jwt.py 与演示数据(本地自测首选)

对于不想依赖 Google 服务账号的本地开发与集成测试,仓库在 security/tools/jwt/samples/ 中提供了功能更强的gen-jwt.py,它直接用本地 PEM 私钥签名,其依赖声明于 samples/requirements.txt:

pip install jwcrypto

gen-jwt.py 的能力差异

阅读 gen-jwt.py 可见它比sa-jwt.py多出以下参数:

参数说明
key(位置参数)PEM 格式私钥路径,可用openssl genrsa -out key.pem 2048生成
-expire过期时间(秒),默认 3600(1 小时),README 演示中配合-expire=3153600000生成长期有效 Token
-jwks输出 JWKS 文件路径,把公钥导出为 JSON 公钥集
-listclaim追加数组型声明,格式key val1 val2 ...,可多次使用
-nestedclaim追加嵌套声明,自动包进名为-nestedkey(默认nested)的嵌套对象,且内部会复制一层nested-2用于测试深层嵌套 claim 场景

签发时默认 issuer 为testing@secure.istio.io,并用kid关联私钥指纹(gen-jwt.py),与 jwks.json 中导出的公钥一一对应。

开箱即用的演示 Token

samples/README.md 说明:仓库已随附一枚签名好的 demo.jwt,其 payload 固定为:

{ "exp": 4685989700, "foo": "bar", "iat": 1532389700, "iss": "testing@secure.istio.io", "sub": "testing@secure.istio.io" }

其exp被刻意设到很遥远的未来,拿起来即可测试、不会过期,例如把内容作为 Bearer Token 直接请求 ingress:

TOKEN=$(cat security/tools/jwt/samples/demo.jwt) curl --header "Authorization: Bearer $TOKEN" $INGRESS_HOST/headers -s \ -o /dev/null -w "%{http_code}\n"

自行重新生成私钥与演示数据(仅供开发者维护数据)

若需要刷新整套演示密钥/公钥集,可参照 samples/README.md 的步骤:

# 1. 重新生成 2048 位 RSA 私钥 openssl genrsa -out key.pem 2048 # 2. 由私钥导出新 JWKS 并签发新的长期演示 JWT ./gen-jwt.py key.pem -jwks=./jwks.json \ --expire=3153600000 --claims=foo:bar > demo.jwt

在 Istio 中落地:RequestAuthentication 与测试链路

关联请求认证策略

以 samples/README.md 给出的策略为例,将 issuer 指向演示数据的固定签发方、jwksUri指向公钥集文件所在地址(本地演示时需将 jwks.json 部署到 istiod 可访问的地址):

apiVersion: security.istio.io/v1 kind: RequestAuthentication metadata: name: "jwt-example" spec: selector: matchLabels: app: httpbin jwtRules: - issuer: "testing@secure.istio.io" jwksUri: "<your-hosted-path-to>/jwks.json"

规则约束了两点,恰好对应造 Token 时的参数选择:

  • issuer必须与 Token 的iss声明完全一致——这正是sa-jwt.py要求-iss传服务账号邮箱、gen-jwt.py默认固定testing@secure.istio.io的原因;
  • 验签公钥必须能匹配上签名私钥——所以签发与验签要么走 Google 服务账号体系(sa-jwt.py+ Google JWKS),要么走本地私钥体系(gen-jwt.py+ 自导出的-jwks)。

仓库中的真实验证场景

整条链路的正确性在仓库集成测试中被反复验证,例如 tests/integration/security/jwt_test.go 与 tests/integration/security/remote_jwks/remote_jwks_test.go 均通过签发合法 Token 后携带Authorization头请求受保护服务来断言 200/403 行为;tests/integration/security/ 下的requestauthn模板目录则覆盖了aud、issuer 不匹配等负向用例。若你要在本地复现,推荐组合为:gen-jwt.py用 samples/key.pem 签发 → 通过 samples/jwks.json 配置jwksUri→ 携带 Token 发起请求观察返回码。

常见问题与使用提醒

  • Token 为什么突然失效?sa-jwt.py生成的 Tokenexp固定为签发时刻 +1 小时且不可调,适合短时测试;需要长期有效的演示 Token 请改用gen-jwt.py的-expire参数(仓库演示数据即采用 3153600000 秒 ≈ 100 年)。
  • sub与iss的关系:两个脚本都遵循"不传-sub则等于iss"的默认逻辑,若你的授权策略基于sub做用户维度区分,务必显式指定。
  • 自定义声明只能是字符串:-claims走k, v = item.split(':')的朴素解析,无法表达数字、布尔或嵌套 JSON;需要复杂结构时改用gen-jwt.py的-listclaim/-nestedclaim。
  • aud的数组行为:传-aud foo,bar时会生成字符串数组["foo","bar"],Istio 校验时任一命中即可;若只传一个值则保留字符串形式,语义等价。
  • 私钥安全:无论是 Google 服务账号 JSON 密钥还是 key.pem,都属于私密材料,仅应存在于可信的测试环境。

【免费下载链接】istioConnect, secure, control, and observe services.项目地址: https://gitcode.com/GitHub_Trending/is/istio

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询