惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
IT之家
IT之家
The Cloudflare Blog
Apple Machine Learning Research
Apple Machine Learning Research
WordPress大学
WordPress大学
N
Netflix TechBlog - Medium
阮一峰的网络日志
阮一峰的网络日志
P
Proofpoint News Feed
L
LangChain Blog
博客园 - Franky
美团技术团队
J
Java Code Geeks
Microsoft Security Blog
Microsoft Security Blog
博客园 - 叶小钗
小众软件
小众软件
Y
Y Combinator Blog
B
Blog RSS Feed
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
D
Docker
Hugging Face - Blog
Hugging Face - Blog
Jina AI
Jina AI
罗磊的独立博客
大猫的无限游戏
大猫的无限游戏
Vercel News
Vercel News

东东's Blog

Memos: MacOS 下编译安装 Aseprite 脚本 日本关西系列|Day 2 京都的半日闲逛 美国Apple官网购买礼品卡订阅 ChatGPT Plus 北京・中关村森林公园(2026) 日本关西系列|Day 1 抵达临空城与大阪首日 Memos: 查询 Apple ID 注册时间 Memos: Ghostty 开箱即用配置 代码考古:用 gitcharts 挖掘 Git 仓库的演变轨迹 烹饪日记:香煎罗非鱼 Memos: 新时代程序员的顶级焦虑 Memos: 博客新增图文布局和轮播图效果支持 Memos: 最适合空气炸锅烤着吃的红薯品类 Memos: 记录「95分」好吃的‘小帅香菇面’ 妻子爷爷的‘朝鲜军功奖章’ Memos: 博客切换为 Shiki 代码高亮方案 Memos: 博客新增划线、重点及荧光笔效果支持 记录博客字体分包与字体子集化 Memos: 邻座吃饭的一家三口 人在囧途之哈囧 哈尔滨・乡村的冬季 查看香烟生产日期 哈尔滨・东北虎林园 Memos: 来自日本的 ndjp 提供免费的三级子域名 Memos: 刚听说 autojump, 真的好用 Memos: 体验 OpenCode + Superpowers + GPT 5.2 开发需求 Memos: 杰我睿爆雷 GoReleaser 自动发布 Go 镜像到 DockerHub & GitHub Release 初识 Volta & Corepack 前端版本管理工具 部署 Beszel 把 “小鸡们” 归拢起来 Memos: Claude Code in Action 中文版教程
再思 JWT 的使用场景和算法选择
2024-10-06 · via 东东's Blog

在使用第三方服务的时候,我们总会看到其提供 API Key,这是一种简单的验证用户身份的方法,服务端存储着这个 API Key,可以通过 Key 来确定用户权限、吊销 Key 等

相比于 API Key,JWT 有自己的特点和适用场景,如果你不太了解 JWT,可以阅读阮一峰的:《JSON Web Token 入门教程》

回顾 JWT 特点

自包含

JWT 是一个自包含的 Token,它包含了所有必要的信息,不需要服务端存储 Token 的状态。JWT 的载荷(payload)部分可以包含用户信息、权限信息、有效期等。

无状态

由于 JWT 自包含所有必要的信息,服务端不需要存储 Token 的状态,所以 JWT 非常适合用于无状态的应用架构,减少了服务端的负担。

传输安全

JWT 使用密钥进行签名,可以验证 Token 的合法性和完整性,签名或加密确保了 JWT 在传输过程中不会被篡改。

扩展性

JWT 的载荷部分是一个 JSON 对象,可以包含自定义的声明(claims),这使得 JWT 非常灵活,可以根据具体需求扩展其功能。

注意事项:在 JWT 中,避免包含密码等敏感信息,JWT 可防篡改但内容可见。

不可撤销

JWT 一旦生成,无法单独撤销。如果 JWT 被泄露,攻击者可以滥用 Token,直到 Token 过期,因此,JWT 的有效期通常设置得较短,以减少风险。(撤销需要借助服务器有状态机制)

例举一种 JWT 场景误用

以 HS256 算法为例子,以下是生成和验证的代码示例

import time
import jwt

KEY_SECRET = "abncdsr462*********zk52hav3x86dm"

# 生成 JWT 令牌
def generate_jwt_token(key_secret, algorithm="HS256"):
    payload = {
        "iat": int(time.time())
    }
    token = jwt.encode(payload, key_secret, algorithm=algorithm)
    return token

# 校验 JWT 令牌
def verify_jwt_token(token, key_secret, algorithm="HS256"):
    try:
        payload = jwt.decode(token, key_secret, algorithms=[algorithm])
        return payload
    except jwt.InvalidTokenError:
        return None

# 生成令牌
t = generate_jwt_token(KEY_SECRET)
print(f"生成的 JWT:", t)

# 校验令牌
verified_payload = verify_jwt_token(t, KEY_SECRET)
if verified_payload:
    print(f"JWT 验证成功:", verified_payload)
else:
    print("JWT 验证失败")

正确的 JWT 使用场景,会将 JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3Mjc2NjU2MTR9.ItXRitJXiwcDx7X1PHeilgwAs9vt4xaa1lHNKgC3wak 提供给客户端

客户端调用服务端时将其再传给服务端,服务端可以验证这个 Token 没有被篡改、同时从中获取信息

一个错误的用法是将 HS256 的 KEY_SECRET 提供给外部平台,由外部平台生成 JWT。这种方式相当于将 JWT 机制降级为 API Key,使得 JWT 的安全性从“JWT 丢失则其权限会被滥用” 降级为 “API Key 泄露则攻击者可无限制地伪造 JWT,可以对系统进行任意请求”,这属于比较严重的安全漏洞

所以,当决定采用 JWT,就应尽量遵守其规范,避免 “新奇” 的使用方式

不同 JWT 算法使用示例(Python3)

JWT 支持 HS256(密钥)、RS256(基于 RSA)、ES256(基于椭圆曲线)、PS256(基于 RSA-PSS)等加密算法,数字表示位数,越长越安全,除 256 外还支持 384 和 512

HS256 在上文已提供示例,它比较简单、使用的是密钥字符串,也是最常用的 JWT 加密算法,另外三者基于的公钥密码对

生成 RS256 公钥密码对

# 私钥
$ openssl genpkey -algorithm RSA -out rsa_private_key.pem -pkeyopt rsa_keygen_bits:2048

# 公钥
$ openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

生成 ES256 公钥密码对

# 私钥
$ openssl ecparam -name prime256v1 -genkey -noout -out ec_private.pem

# 公钥
$ openssl ec -in ec_private.pem -pubout -out ec_public.pem

这里选择的曲线进一步说明,prime256v1 P-256,Go 语言的 x509 包默认支持的椭圆曲线是 P-256 和 P-384,另一种常见的曲线是 secp256k1,它是比特币和其他加密货币中常用的椭圆曲线,Go 语言的 x509 包默认不支持它,Python3 是支持的

生成 PS256 公钥密码对

# 私钥
$ openssl genpkey -algorithm RSA -out ps_private_key.pem -pkeyopt rsa_keygen_bits:2048

# 公钥
$ openssl rsa -in ps_private_key.pem -pubout -out ps_public_key.pem

代码示例

import time
import jwt
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend

# 定义常量
RSA_PRIVATE_KEY_PATH = "./id_rsa"
RSA_PUBLIC_KEY_PATH = "./id_rsa_pem.pub"
EC_PRIVATE_KEY_PATH = "./ec_private.pem"
EC_PUBLIC_KEY_PATH = "./ec_public.pem"
PS_PRIVATE_KEY_PATH = "./ps_private_key.pem"
PS_PUBLIC_KEY_PATH = "./ps_public_key.pem"

# 加载私钥
def load_private_key(private_key_path):
    with open(private_key_path, "rb") as key_file:
        private_key = serialization.load_pem_private_key(
            key_file.read(),
            password=None,
            backend=default_backend()
        )
    return private_key

# 加载公钥
def load_public_key(public_key_path):
    with open(public_key_path, "rb") as key_file:
        public_key = serialization.load_pem_public_key(
            key_file.read(),
            backend=default_backend()
        )
    return public_key

# 生成 JWT 令牌
def generate_jwt_token(private_key, algorithm):
    payload = {
        "iat": int(time.time())  # 使用秒级时间戳
    }
    return jwt.encode(payload, private_key, algorithm=algorithm)

# 验证 JWT
def verify_jwt_token(token, public_key, algorithm):
    try:
        decoded = jwt.decode(token, public_key, algorithms=[algorithm])
        return decoded
    except jwt.InvalidTokenError:
        return None

# 主函数
def __main__():
    algorithms = [
        ("RS256", RSA_PRIVATE_KEY_PATH, RSA_PUBLIC_KEY_PATH),
        ("ES256", EC_PRIVATE_KEY_PATH, EC_PUBLIC_KEY_PATH),
        ("PS256", PS_PRIVATE_KEY_PATH, PS_PUBLIC_KEY_PATH),
    ]

    for algorithm, private_key_path, public_key_path in algorithms:
        # 加载私钥和公钥
        private_key = load_private_key(private_key_path)
        public_key = load_public_key(public_key_path)

        # 生成 JWT
        jwt_token = generate_jwt_token(private_key, algorithm)
        print(f"生成的 {algorithm} JWT:", jwt_token)

        # 验证 JWT
        decoded_payload = verify_jwt_token(jwt_token, public_key, algorithm)
        if decoded_payload:
            print(f"{algorithm} JWT 验证成功:", decoded_payload)
        else:
            print(f"{algorithm} JWT 验证失败")

if __name__ == "__main__":
    __main__()

执行输出

基于椭圆曲线算法的 ES256 生成的签名长度,相较于其它算法可谓之遥遥领先,在 JWT 长度敏感的场景下可以优先选择

另外 PS256 和 RS256 相比较,查阅的资料如下:

PS256 使用 RSASSA-PSS,这是一种概率签名方案,引入了随机性,使得每次签名都是唯一的,因为引入了随机性,PS256 提供了比 RS256 更高的安全性,同样资源消耗也会更高一些

另外从 JWT 的是用场景来看,签发与核验 JWT 都由服务端自己完成,所以不存在跟客户端的兼容性问题,选择算法会更加的灵活

综上:推荐优先使用 ES256 算法,安全性高,同时签名、密钥对都是最小的

线上校验工具 JWT.io

这个调试工具支持的算法广泛,本文中提及的算法生成的 JWT 内容都通过 https://jwt.io/ 进行了二次核对

另外可以在 https://jwt.io/libraries 找到相应语言库支持的算法列表