1. PyJWT #
这一部分介绍 PyJWT 的定位与用途。PyJWT 是用于在 Python 中生成与验证 JSON Web Token 的库。JWT 遵循 RFC 7519 标准,可在不同服务之间安全、紧凑地传递声明信息,常用于认证与授权、分布式服务间信任传递等场景。
1.1. 安装 PyJWT #
下面演示在 Windows 环境中使用 py -m pip 安装 PyJWT 以及可选的加密支持。若需要 RSA/EC 等非对称算法,请一并安装带加密依赖的可选组件。
REM 升级 pip,避免旧版本导致安装报错
py -m pip install --upgrade pip
REM 安装 PyJWT(基础版本,包含 HS256 等对称算法)
py -m pip install pyjwt
REM 安装带加密依赖的 PyJWT(支持 RSA/EC 等非对称算法)
py -m pip install "pyjwt[crypto]"2. 核心功能 #
本节演示最常用的编码(生成 Token)与解码(验证 Token)能力。示例均为可独立运行的完整脚本形式,并提供逐行中文注释以便理解。
2.1. 编码(生成) JWT #
本示例演示使用 HS256(对称加密)生成一个带有过期时间的 Token。
# 导入 PyJWT 库
import jwt
# 从 datetime 导入时间工具以便设置过期时间
from datetime import datetime, timedelta
# 定义主函数,便于脚本独立运行
def main():
# 定义载荷字典开始
payload = {
# 用户唯一标识
'user_id': 123,
# 用户名
'username': 'john_doe',
# 过期时间(当前 UTC 时间 + 1 小时)
'exp': datetime.utcnow() + timedelta(hours=1)
}
# 定义对称密钥,仅服务端保存
secret_key = 'your-secret-key'
# 使用 HS256 算法对载荷进行签名,生成 JWT 字符串(PyJWT>=2 返回 str)
token = jwt.encode(payload, secret_key, algorithm='HS256')
# 打印生成的 Token,供后续验证或传输
print(token)
# 作为脚本入口执行主函数
if __name__ == '__main__':
main()2.2. 解码(验证) JWT #
本示例先生成一个 Token,然后演示如何在验证签名、校验过期时间的前提下进行解码,并完整处理常见异常。
# 导入 PyJWT 库
import jwt
# 导入时间工具以生成带过期时间的 Token
from datetime import datetime, timedelta
# 定义主函数,演示生成与解码流程
def main():
# 构造载荷并设置 1 小时后过期
payload = {
# 用户唯一标识
'user_id': 123,
# 用户名
'username': 'john_doe',
# 过期时间(当前 UTC 时间 + 1 小时)
'exp': datetime.utcnow() + timedelta(hours=1)
}
# 对称密钥,与生成时保持一致
secret_key = 'your-secret-key'
# 先生成一个 HS256 Token 以供演示
token = jwt.encode(payload, secret_key, algorithm='HS256')
# 尝试解码 Token,验证签名与过期时间(需显式提供 algorithms)
try:
# 解码并返回载荷(若已过期或签名不匹配会抛异常)
decoded_payload = jwt.decode(token, secret_key, algorithms=['HS256'])
# 打印解码结果
print(decoded_payload)
# 捕获过期错误
except jwt.ExpiredSignatureError:
# 说明 Token 已超过 exp 设置的时间
print('Token已过期')
# 捕获通用无效 Token 错误
except jwt.InvalidTokenError:
# 说明签名不合法或结构错误等
print('无效Token')
# 脚本入口
if __name__ == '__main__':
main()3. 支持的算法 #
PyJWT 同时支持对称加密(如 HS256)与非对称加密(如 RS256、ES256)等多种算法。对称加密仅需共享密钥;非对称加密使用公私钥对,更适合多服务间的安全验证与密钥分离。生产环境通常优先考虑非对称以降低密钥泄露影响面。
| 算法类型 | 算法名称 | 说明 |
|---|---|---|
| 对称加密 | HS256, HS384, HS512 | 使用共享密钥 |
| 非对称加密 | RS256, RS384, RS512 | RSA 签名 |
| 非对称加密 | ES256, ES384, ES512 | ECDSA 签名 |
| 无加密 | none | 不推荐生产环境使用 |
4. 高级用法 #
本节展示 RSA 非对称签名、带标准与自定义声明的载荷、以及解码时的高级校验选项,便于满足更严格的安全与合规需求。
4.1. 使用 RSA 非对称加密 #
此示例动态生成 RSA 公私钥对,用私钥签名、用公钥验证,适合服务端签发、客户端或其他服务端验证的场景。
# 导入 PyJWT 库
import jwt
# 导入生成与管理密钥所需的组件(本示例不直接序列化密钥,保留以示范)
from cryptography.hazmat.primitives import serialization
# 导入 RSA 算法支持
from cryptography.hazmat.primitives.asymmetric import rsa
# 导入默认后端
from cryptography.hazmat.backends import default_backend
# 定义主函数
def main():
# 生成 RSA 私钥(实际生产中请妥善保存,不要硬编码或外泄)
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
# 从私钥导出公钥,用于验证签名
public_key = private_key.public_key()
# 定义待签名的载荷(可根据业务扩展)
payload = {'user': 'admin'}
# 使用 RS256 算法与私钥签名,得到 Token
token = jwt.encode(payload, private_key, algorithm='RS256')
# 使用公钥验证签名并解码 Token
decoded = jwt.decode(token, public_key, algorithms=['RS256'])
# 打印解码结果
print(decoded)
# 脚本入口
if __name__ == '__main__':
main()4.2. 自定义 Claims (声明) #
示例演示如何在载荷中包含标准声明(iss、sub、aud、iat、exp、nbf、jti)与自定义数据,并在解码时对签发者与接收方进行严格校验,有助于提升安全性与契约清晰度。
# 导入 PyJWT 及时间工具
import jwt
# 导入 datetime 以便构造时间相关声明
from datetime import datetime, timedelta
# 定义主函数
def main():
# 开始定义载荷字典
payload = {
# 签发者(Issuer)
'iss': 'my-app',
# 主题(Subject)
'sub': 'user-auth',
# 接收方(Audience)
'aud': 'client-app',
# 签发时间(Issued At)
'iat': datetime.utcnow(),
# 过期时间(Expiration Time)
'exp': datetime.utcnow() + timedelta(days=1),
# 生效时间(Not Before)
'nbf': datetime.utcnow(),
# Token 唯一标识(JWT ID)
'jti': 'unique-token-id',
# 自定义数据(业务相关)
'custom_data': {
# 用户角色
'user_role': 'admin',
# 偏好设置
'preferences': {'theme': 'dark'}
}
}
# 对称密钥
secret_key = 'your-secret-key'
# 编码生成 Token
token = jwt.encode(payload, secret_key, algorithm='HS256')
# 解码并验证签发者与接收方
decoded = jwt.decode(
# 待解码的 Token
token,
# 解码所需密钥
secret_key,
# 指定可接受的算法列表
algorithms=['HS256'],
# 期望的接收方
audience='client-app',
# 期望的签发者
issuer='my-app'
)
# 打印解码结果
print(decoded)
# 脚本入口
if __name__ == '__main__':
main()4.3. 验证选项 #
通过 options 可以细化解码时的校验行为,例如是否校验签名、是否要求存在某些标准字段等。示例中我们开启常用校验并提供 aud 与 iss 以通过验证。
# 导入 PyJWT 与时间工具
import jwt
# 导入 datetime 用于构造时间声明
from datetime import datetime, timedelta
# 定义主函数
def main():
# 准备载荷,包含必要字段以通过各项校验
payload = {
# 签发者(Issuer)
'iss': 'my-app',
# 接收方(Audience)
'aud': 'client-app',
# 签发时间(Issued At)
'iat': datetime.utcnow(),
# 过期时间(Expiration Time),设置为 5 分钟
'exp': datetime.utcnow() + timedelta(minutes=5)
}
# 对称密钥
secret_key = 'your-secret-key'
# 生成 Token
token = jwt.encode(payload, secret_key, algorithm='HS256')
# 使用严格的解码与校验选项
decoded = jwt.decode(
# 待解码的 Token
token,
# 用于验证签名的密钥
secret_key,
# 指定允许的算法
algorithms=['HS256'],
# 细化验证行为的选项
options={
# 验证签名
'verify_signature': True,
# 验证过期时间
'verify_exp': True,
# 验证签发时间
'verify_iat': True,
# 验证接收方
'verify_aud': True,
# 要求必须包含 exp
'require_exp': True,
# 可选是否要求包含 iat(此处不强制)
'require_iat': False
},
# 期望的接收方
audience='client-app',
# 期望的签发者
issuer='my-app'
)
# 打印结果
print(decoded)
# 脚本入口
if __name__ == '__main__':
main()5. 实际应用示例 #
这里给出两个最常见的 Web 场景:Flask 与 Django REST Framework(DRF)。两个示例都是可独立运行的最小化应用,便于本地快速体验;生产环境请补充完整的认证流程与错误处理。
5.1. Flask JWT 认证 #
示例提供一个 /login 接口用于签发 Token,以及一个受保护的 /protected 接口用于验证 Token。
# 导入 Flask Web 框架中需要的对象
from flask import Flask, request, jsonify
# 导入 PyJWT 库
import jwt
# 导入 datetime 以便设置过期时间
import datetime
# 创建 Flask 应用实例
app = Flask(__name__)
# 设置应用级密钥(演示用)
app.config['SECRET_KEY'] = 'your-secret-key'
# 定义登录接口,使用 Basic Auth 进行最简演示
@app.route('/login', methods=['POST'])
# 登录视图函数
def login():
# 从请求头解析基础认证信息
auth = request.authorization
# 极简校验:密码为 'password' 则签发 Token(实际请使用数据库与加密校验)
if auth and auth.password == 'password':
# 生成 30 分钟有效期的 Token:开始构造载荷字典
payload = {
# 用户名来源于认证信息
'user': auth.username,
# 过期时间设置为 30 分钟
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
}
# 使用应用密钥进行编码
token = jwt.encode(payload, app.config['SECRET_KEY'])
# 返回 JSON,其中包含签发的 Token
return jsonify({'token': token})
# 认证失败时返回 401
return jsonify({'message': 'Invalid credentials'}), 401
# 定义受保护接口,需要提供 token 参数
@app.route('/protected')
# 受保护视图函数
def protected():
# 从查询字符串中获取 token(生产环境推荐使用 Authorization 头)
token = request.args.get('token')
# 尝试解码并返回载荷
try:
# 解码并验证签名
data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
# 返回业务数据
return jsonify({'data': data})
# 捕获异常并返回 403
except Exception:
# Token 缺失、格式错误或签名/过期校验失败
return jsonify({'message': 'Invalid token'}), 403
# 以调试模式启动应用
if __name__ == '__main__':
# 指定端口为 8000,便于与后续示例区分
app.run(debug=True, port=8000)5.2. Django REST Framework JWT(最小可运行示例,单文件) #
此示例以单文件方式动态配置 Django 与 DRF,提供 /token(签发 Token)与 /secure(验证 Token)两个接口,便于无需完整项目结构即可快速运行与测试。
# 导入标准库 os(此处未直接使用,保留结构完整性)
import os
# 导入 sys 以便复用命令行入口
import sys
# 导入 PyJWT
import jwt
# 导入时间工具
from datetime import datetime, timedelta
# 导入 Django 设置与 URL 构造工具
from django.conf import settings
from django.urls import path
# 导入 Django 响应对象(示例未直接使用)
from django.http import JsonResponse
# 导入运行开发服务器所需函数
from django.core.management import execute_from_command_line
# 导入 WSGI 应用构造(示例未直接使用)
from django.core.wsgi import get_wsgi_application
# 导入 DRF 视图与响应
from rest_framework.views import APIView
from rest_framework.response import Response
# 若未配置 Django 设置,则进行最小化配置
if not settings.configured:
# 调用 settings.configure 进行基本配置
settings.configure(
# 打开调试模式
DEBUG=True,
# 设置开发密钥(示例用)
SECRET_KEY='dev-secret',
# 将当前模块作为 URL 路由入口
ROOT_URLCONF=__name__,
# 允许所有主机访问(开发环境)
ALLOWED_HOSTS=['*'],
# 关闭中间件以简化示例
MIDDLEWARE=[],
# 注册必要的应用(contenttypes/auth 为 DRF 依赖)
INSTALLED_APPS=[
'django.contrib.contenttypes',
'django.contrib.auth',
'rest_framework',
],
)
# 初始化 Django(必须在定义视图前调用)
import django
# 调用 setup 完成应用注册与配置加载
django.setup()
# 定义一个视图用于签发 Token
class TokenView(APIView):
# 提供 GET 接口以简化测试(实际可用 POST 并验证用户)
def get(self, request):
# 读取查询参数中的用户名(默认 guest)
username = request.GET.get('user', 'guest')
# 构造载荷字典,设置 10 分钟过期
payload = {
# 将用户名放入载荷,便于下游识别
'user': username,
# 设置过期时间为 10 分钟
'exp': datetime.utcnow() + timedelta(minutes=10)
}
# 使用与验证一致的对称密钥
secret = 'your-secret-key'
# 生成 Token
token = jwt.encode(payload, secret, algorithm='HS256')
# 返回 JSON
return Response({'token': token})
# 定义受保护视图,验证 Authorization 头中的 Bearer Token
class SecureDataView(APIView):
# 简单示例不强制身份认证类,直接验证 Token
def get(self, request):
# 从 Authorization 头获取内容
auth = request.headers.get('Authorization', '')
# 按空格拆分,期待格式为 Bearer <token>
parts = auth.split()
# 判断是否满足 Bearer 格式
if len(parts) == 2 and parts[0].lower() == 'bearer':
# 取出 token 部分
token = parts[1]
# 尝试解码并验证签名
try:
# 解码 Token,校验签名
payload = jwt.decode(token, 'your-secret-key', algorithms=['HS256'])
# 成功则返回用户信息
return Response({'ok': True, 'user': payload.get('user')})
# 捕获过期错误
except jwt.ExpiredSignatureError:
# 返回 401 未授权,提示过期
return Response({'ok': False, 'error': 'expired'}, status=401)
# 捕获通用无效 Token 错误
except jwt.InvalidTokenError:
# 返回 401 未授权,提示无效
return Response({'ok': False, 'error': 'invalid'}, status=401)
# 若未提供或格式不正确
return Response({'ok': False, 'error': 'missing bearer token'}, status=400)
# 绑定 URL 到视图
urlpatterns = [
# 获取 Token 的接口
path('token', TokenView.as_view()),
# 需要携带 Bearer Token 才能访问的接口
path('secure', SecureDataView.as_view()),
]
# 运行开发服务器(默认端口 8001,避免与 Flask 冲突)
if __name__ == '__main__':
# 启动 Django 开发服务器
execute_from_command_line([sys.argv[0], 'runserver', '8001'])6. 安全最佳实践 #
以下建议有助于更安全地使用 JWT:
- 永远不要在 JWT 载荷中放置明文敏感信息(如密码、密钥、私密个人信息)。
- 使用强随机密钥,并定期轮换;生产环境建议使用 KMS/密钥管理服务。
- 合理设置过期时间(短生命周期),并结合刷新令牌机制。
- 解码时严格验证必要声明(iss、aud、exp 等),并处理所有异常分支。
- 全链路使用 HTTPS 传输,避免 Token 被窃听。
- 服务端应具备 Token 吊销策略(黑名单/版本号/会话表)。
7. 性能考虑 #
对称算法(HS)通常更快;非对称算法(RS/ES*)安全边界更清晰但计算更重。对解码路径进行缓存或集中验证可降低系统开销。
- HS256 相比 HS512 有更好的性能,但安全性略低;结合威胁模型选择合适算法。
- 解码操作通常比编码更耗时,热点路径需关注。
- 在多实例/多服务场景中,可集中化验证或引入网关以复用验证结果。
PyJWT 是 Python 生态中成熟可靠的 JWT 实现。配合正确的密钥管理、声明校验与异常处理策略,可构建健壮的认证与授权体系。