

使用 Istio 进行 JWT 身份验证(充当 API 网关)
source link: https://ryan4yin.space/posts/use-istio-for-jwt-auth/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文基于 Istio1.5 编写测试
Istio 支持使用 JWT 对终端用户进行身份验证(Istio End User Authentication),支持多种 JWT 签名算法。
目前主流的 JWT 算法是 RS256/ES256。(请忽略 HS256,该算法不适合分布式 JWT 验证)
这里以 RSA256 算法为例进行介绍,ES256 的配置方式也是一样的。
1. 介绍 JWK 与 JWKS
Istio 要求提供 JWKS 格式的信息,用于 JWT 签名验证。因此这里得先介绍一下 JWK 和 JWKS.
JWKS ,也就是 JWK Set,json 结构如下:
{
"keys": [
<jwk-1>,
<jwk-2>,
...
]}
JWKS 描述一组 JWK 密钥。它能同时描述多个可用的公钥,应用场景之一是密钥的 Rotate.
而 JWK,全称是 Json Web Key,它描述了一个加密密钥(公钥或私钥)的各项属性,包括密钥的值。
Istio 使用 JWK 描述验证 JWT 签名所需要的信息。在使用 RSA 签名算法时,JWK 描述的应该是用于验证的 RSA 公钥。
一个 RSA 公钥的 JWK 描述如下:
{
"alg": "RS256", # 算法「可选参数」
"kty": "RSA", # 密钥类型
"use": "sig", # 被用于签名「可选参数」
"kid": "NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk1RTM2Qg", # key 的唯一 id
"n": "yeNlzlub94YgerT030codqEztjfU_S6X4DbDA_iVKkjAWtYfPHDzz_sPCT1Axz6isZdf3lHpq_gYX4Sz-cbe4rjmigxUxr-FgKHQy3HeCdK6hNq9ASQvMK9LBOpXDNn7mei6RZWom4wo3CMvvsY1w8tjtfLb-yQwJPltHxShZq5-ihC9irpLI9xEBTgG12q5lGIFPhTl_7inA1PFK97LuSLnTJzW0bj096v_TMDg7pOWm_zHtF53qbVsI0e3v5nmdKXdFf9BjIARRfVrbxVxiZHjU6zL6jY5QJdh1QCmENoejj_ytspMmGW7yMRxzUqgxcAqOBpVm0b-_mW3HoBdjQ",
"e": "AQAB"
}
RSA 是基于大数分解的加密/签名算法,上述参数中,e
是公钥的模数(modulus),n
是公钥的指数(exponent),两个参数都是 base64 字符串。
JWK 中 RSA 公钥的具体定义参见 RSA Keys - JSON Web Algorithms (JWA)
2. JWK 的生成
要生成 JWK 公钥,需要先生成私钥,生成方法参见 JWT 签名算法 HS256、RS256 及 ES256 及密钥生成。
公钥不需要用上述方法生成,因为我们需要的是 JWK 格式的公钥。后面会通过 python 生成出 JWK 公钥。
上面的命令会将生成出的 RSA 私钥写入 key.pem 中,查看一下私钥内容。
ryan@RYAN-MI-DESKTOP:~/istio$ cat key.pem
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAt1cKkQqPh8iOv5BhKh7Rx6A2+1ldpO/jczML/0GBKu4X+lHr
Y8YbJrt29jyAXlWM8vHC7tXsqgUG+WziRD0D8nhnh10XC14SeH+3mVuBqph+TqhX
TWsh9gtAIbeUHJjEI4I79QK4/wquPHHIGZBQDQQnuMh6vAS3VaUYJdEIoKvUBnAy
Y35kJZgyJSbrxLsEExL2zujUD/OY+/In2bq/3rFtDGNlgHyC7Gu2zXSXvfOA4O5m
9BBXOc7eEqj7PoOKNaTxLN3YcuRtgR6NIXL4KLb6oyvIzoeiprt4+9q7sc3Dnkc5
EV9kwWlEW2DHzhP6VYca0WXIIXc53U1AM3ewxwIDAQABAoIBABIKhaaqJF+XM7zU
B0uuxrPfJynqrFVbqcUfQ9H1bzF7Rm7CeuhRiUBxeA5Y+8TMpFcPxT/dWzGL1xja
RxWx715/zKg8V9Uth6HF55o2r/bKlLtGw3iBz1C34LKwrul1eu+HlEDS6MNoGKco
BynE0qvFOedsCu/Pgv7xhQPLow60Ty1uM0AhbcPgi6yJ5ksRB1XjtEnW0t+c8yQS
nU3mU8k230SdMhf4Ifud/5TPLjmXdFpyPi9uYiVdJ5oWsmMWEvekXoBnHWDDF/eT
VkVMiTBorT4qn+Ax1VjHL2VOMO5ZbXEcpbIc3Uer7eZAaDQ0NPZK37IkIn9TiZ21
cqzgbCkCgYEA5enHZbD5JgfwSNWCaiNrcBhYjpCtvfbT82yGW+J4/Qe/H+bY/hmJ
RRTKf0kVPdRwZzq7GphVMWIuezbOk0aFGhk/SzIveW8QpLY0FV/5xFnGNjV9AuNc
xrmgVshUsyQvr1TFkbdkC6yuvNgQfXfnbEoaPsXYEMCii2zqdF5lWGUCgYEAzCR2
6g8vEQx0hdRS5d0zD2/9IRYNzfP5oK0+F3KHH2OuwlmQVIo7IhCiUgqserXNBDef
hj+GNcU8O/yXLomAXG7VG/cLWRrpY8d9bcRMrwb0/SkNr0yNrkqHiWQ/PvR+2MLk
viWFZTTp8YizPA+8pSC/oFd1jkZF0UhKVAREM7sCgYB5+mfxyczFopyW58ADM7uC
g0goixXCnTuiAEfgY+0wwXVjJYSme0HaxscQdOOyJA1ml0BBQeShCKgEcvVyKY3g
ZNixunR5hrVbzdcgKAVJaR/CDuq+J4ZHYKByqmJVkLND4EPZpWSM1Rb31eIZzw2W
5FG8UBbr/GfAdQ6GorY+CQKBgQCzWQHkBmz6VG/2t6AQ9LIMSP4hWEfOfh78q9dW
MDdIO4JomtkzfLIQ7n49B8WalShGITwUbLDTgrG1neeQahsMmg6+X99nbD5JfBTV
H9WjG8CWvb+ZF++NhUroSNtLyu+6LhdaeopkbQVvPwMArG62wDu6ebv8v/5MrG8o
uwrUSwKBgQCxV43ZqTRnEuDlF7jMN+2JZWhpbrucTG5INoMPOC0ZVatePszZjYm8
LrmqQZHer2nqtFpyslwgKMWgmVLJTH7sVf0hS9po0+iSYY/r8e/c85UdUreb0xyT
x8whrOnMMODCAqu4W/Rx1Lgf2vXIx0pZmlt8Df9i2AVg/ePR6jO3Nw==
-----END RSA PRIVATE KEY-----
接下来通过 Python 编程生成 RSA Public Key 和 JWK(jwk 其实就是公钥的另一个表述形式而已):
# 需要先安装依赖: pip install jwcrypto
from jwcrypto.jwk import JWK
from pathlib import Path
private_key = Path("key.pem").read_bytes()
jwk = JWK.from_pem(private_key)
# 导出公钥 RSA Public Key
public_key = jwk.public().export_to_pem()
print(public_key)
print("="*30)
# 导出 JWK
jwk_bytes = jwk.public().export()
print(jwk_bytes)
Istio 需要 JWK 进行 JWT 验证,而我们手动验证 JWT 时一般需要用到 Public Key. 方便起见,上述代码把这两个都打印了出来。内容如下:
# Public Key 内容,不包含这行注释
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt1cKkQqPh8iOv5BhKh7R
x6A2+1ldpO/jczML/0GBKu4X+lHrY8YbJrt29jyAXlWM8vHC7tXsqgUG+WziRD0D
8nhnh10XC14SeH+3mVuBqph+TqhXTWsh9gtAIbeUHJjEI4I79QK4/wquPHHIGZBQ
DQQnuMh6vAS3VaUYJdEIoKvUBnAyY35kJZgyJSbrxLsEExL2zujUD/OY+/In2bq/
3rFtDGNlgHyC7Gu2zXSXvfOA4O5m9BBXOc7eEqj7PoOKNaTxLN3YcuRtgR6NIXL4
KLb6oyvIzoeiprt4+9q7sc3Dnkc5EV9kwWlEW2DHzhP6VYca0WXIIXc53U1AM3ew
xwIDAQAB
-----END PUBLIC KEY-----
# jwk 内容
{
'e': 'AQAB',
'kid': 'oyYwZSLCLVVPHdVp0jXIcLNpGn6dMCumlY-6wSenmFo',
'kty': 'RSA',
'n': 't1cKkQqPh8iOv5BhKh7Rx6A2-1ldpO_jczML_0GBKu4X-lHrY8YbJrt29jyAXlWM8vHC7tXsqgUG-WziRD0D8nhnh10XC14SeH-3mVuBqph-TqhXTWsh9gtAIbeUHJjEI4I79QK4_wquPHHIGZBQDQQnuMh6vAS3VaUYJdEIoKvUBnAyY35kJZgyJSbrxLsEExL2zujUD_OY-_In2bq_3rFtDGNlgHyC7Gu2zXSXvfOA4O5m9BBXOc7eEqj7PoOKNaTxLN3YcuRtgR6NIXL4KLb6oyvIzoeiprt4-9q7sc3Dnkc5EV9kwWlEW2DHzhP6VYca0WXIIXc53U1AM3ewxw'
}
4. 测试密钥可用性
接下来在 jwt.io 中填入测试用的公钥私钥,还有 Header/Payload。一是测试公私钥的可用性,二是生成出 JWT 供后续测试 Istio JWT 验证功能的可用性。
可以看到左下角显示「Signature Verified」,成功地生成出了 JWT。后续可以使用这个 JWT 访问 Istio 网关,测试 Istio JWT 验证功能。
5. 启用 Istio 的身份验证
编写 istio 配置:
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
name: "jwt-example"
namespace: istio-system # istio-system 名字空间中的配置,默认情况下会应用到所有名字空间
spec:
selector:
matchLabels:
istio: ingressgateway
jwtRules:
# issuer 即签发者,需要和 JWT payload 中的 iss 属性完全一致。
- issuer: "[email protected]"
jwks: |
{
"keys": [
{
"e": "AQAB",
"kid": "oyYwZSLCLVVPHdVp0jXIcLNpGn6dMCumlY-6wSenmFo", # kid 需要与 jwt header 中的 kid 完全一致。
"kty": "RSA",
"n": "t1cKkQqPh8iOv5BhKh7Rx6A2-1ldpO_jczML_0GBKu4X-lHrY8YbJrt29jyAXlWM8vHC7tXsqgUG-WziRD0D8nhnh10XC14SeH-3mVuBqph-TqhXTWsh9gtAIbeUHJjEI4I79QK4_wquPHHIGZBQDQQnuMh6vAS3VaUYJdEIoKvUBnAyY35kJZgyJSbrxLsEExL2zujUD_OY-_In2bq_3rFtDGNlgHyC7Gu2zXSXvfOA4O5m9BBXOc7eEqj7PoOKNaTxLN3YcuRtgR6NIXL4KLb6oyvIzoeiprt4-9q7sc3Dnkc5EV9kwWlEW2DHzhP6VYca0WXIIXc53U1AM3ewxw"
}
]
}
# jwks 或 jwksUri 二选其一
# jwksUri: "http://nginx.test.local/istio/jwks.json"
现在 kubectl apply
一下,JWT 验证就添加到全局了。
可以看到 jwtRules 是一个列表,因此可以为每个 issuers 配置不同的 jwtRule.
对同一个 issuers(jwt 签发者),可以通过 jwks 设置多个公钥,以实现JWT签名密钥的轮转。 JWT 的验证规则是:
- JWT 的 payload 中有 issuer 属性,首先通过 issuer 匹配到对应的 istio 中配置的 jwks。
- JWT 的 header 中有 kid 属性,第二步在 jwks 的公钥列表中,中找到 kid 相同的公钥。
- 使用找到的公钥进行 JWT 签名验证。
6. 启用 Payload 转发/Authorization 转发
默认情况下,Istio 在完成了身份验证之后,会去掉 Authorization 请求头再进行转发。
这将导致我们的后端服务获取不到对应的 Payload,无法判断 End User 的身份。
因此我们需要启用 Istio 的 Authorization 请求头的转发功能,在前述的 RequestAuthentication
yaml 配置中添加一个参数就行:
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
name: "jwt-example"
namespace: istio-system
spec:
selector:
matchLabels:
istio: ingressgateway
jwtRules:
- issuer: "[email protected]"
jwks: |
{
"keys": [
{
"e": "AQAB",
"kid": "oyYwZSLCLVVPHdVp0jXIcLNpGn6dMCumlY-6wSenmFo",
"kty": "RSA",
"n": "t1cKkQqPh8iOv5BhKh7Rx6A2-1ldpO_jczML_0GBKu4X-lHrY8YbJrt29jyAXlWM8vHC7tXsqgUG-WziRD0D8nhnh10XC14SeH-3mVuBqph-TqhXTWsh9gtAIbeUHJjEI4I79QK4_wquPHHIGZBQDQQnuMh6vAS3VaUYJdEIoKvUBnAyY35kJZgyJSbrxLsEExL2zujUD_OY-_In2bq_3rFtDGNlgHyC7Gu2zXSXvfOA4O5m9BBXOc7eEqj7PoOKNaTxLN3YcuRtgR6NIXL4KLb6oyvIzoeiprt4-9q7sc3Dnkc5EV9kwWlEW2DHzhP6VYca0WXIIXc53U1AM3ewxw"
}
]
}
# ===================== 添加如下参数===========================
forwardOriginalToken: true # 转发 Authorization 请求头
加了转发后,流程图如下(需要 mermaid 渲染):
1. AuthorizationPolicy
Istio 的 JWT 验证规则,默认情况下会直接忽略不带 Authorization 请求头的流量,因此这类流量能直接进入网格内部。如果需要禁止不带 Authorization 头的流量,需要额外配置 AuthorizationPolicy 策略。
RequestsAuthentication 验证失败的请求,Istio 会返回 401 状态码。 AuthorizationPolicy 验证失败的请求,Istio 会返回 403 状态码。
这会导致在使用 AuthorizationPolicy 禁止了不带 Authorization 头的流量后,这类请求会直接被返回 403。。。在使用 RESTful API 时,这种情况可能会造成一定的问题。
2. Response Headers
RequestsAuthentication 不支持自定义响应头信息,这导致对于前后端分离的 Web API 而言,
一旦 JWT 失效,Istio 会直接将 401 返回给前端 Web 页面。
因为响应头中不包含 Access-Crontrol-Allow-Origin
,响应将被浏览器拦截!
这可能需要通过 EnvoyFilter 自定义响应头,添加跨域信息。
Recommend
-
63
众成翻译(zcfy.cc),中国最好的技术翻译社区,最懂译者的翻译平台,奇虎360最大前端团队“奇舞团”出品。Zhongcheng Tanslation(zcfy.cc), the best technical translation community and the most translator-friendly translation platform in China. Developed...
-
55
工作原理 前端页面进行登录操作, 将用户名与密码发给服务器; 服务器进行效验, 通过后生成token, 包含信息有密钥, uid, 过期时间, 一些随机算法等 ,然后返回给前端 前端将token保存在本地中, 建议使用localstorage进行保存. 下
-
22
在istio中部署tls网关 ...
-
6
使用 OAuth 2.0 进行 Kafka 身份验证 在 Strimzi 0.14.0 中,我们为 Kafka 代理支持的标准集添加了一个额外的身份验证选项。您的 Kafka 客户端现在可以在与 Kafka 代理建立会话时使用基于 OAuth 2.0 令牌的身份验证。通过这种身份验证,Kafka 客户端和...
-
8
基于gRPC、API网关和身份验证的Go微服务源码项目 这个案例是一个小型电子商务系统,开发了3个微服务和1个API网关,处理传入的HTTP请求...
-
8
使用领域驱动设计DDD和CQRS实现身份验证的微服务源码项目 这是一个使用领驱动设计...
-
2
新研究表明苹果 AirPods Pro 可充当助听器使用 2022-11-18 08:00:36 来源:
-
8
使用PHP实现双因素身份验证 (2FA) 我们使用google2fa-qrcode组件(https://github.com/antonioribeiro/google2fa-qrcode)来实现: 安装google2fa-qrcode:
-
8
Istio 多集群身份验证与 mTLS 连接解析 · Jimmy Song 云原生博客 这篇博客为你深度解...
-
5
【笔记】MacOS 上强制使用 TouchID 或 AppleWatch 验证身份 捕捉一只爱折腾的绯鞠 美人画卷 - 大神慧 ...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK