在使用第三方服务的时候,我们总会看到其提供 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 找到相应语言库支持的算法列表