什么是JWT?一个简单的比喻
想象一下你去参加一个大型会议。第一次入场时,工作人员检查你的购票信息,确认无误后给你戴上一个手环。之后在会议期间,你进出各个分会场、领取茶歇、参加活动,只需要亮出手环就可以了,不需要反复出示购票凭证。
JWT(JSON Web Token)就是这个数字世界的“手环”。它是一种让Web应用安全传递信息的方式,解决了“如何证明你是你”的问题。
为什么需要JWT?
在传统网站中,服务器通过“会话”(Session)记录用户登录状态。但这有几个问题:
- 服务器需要存储大量会话数据,用户多了内存压力大
- 难以扩展,多台服务器之间要同步会话信息
- 不适合移动端和API服务
JWT的出现解决了这些问题:信息都存在令牌里,服务器不用存,只需要验证令牌是否有效即可。
JWT长什么样?
一个JWT看起来像这样(实际是一长串,这里折行显示):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
它由三部分组成,用点(.)分隔:
-
头部(Header)
-
载荷(Payload)
-
签名(Signature)
1. 头部(Header)
就像信封的“说明标签”,告诉别人这个令牌的基本信息:
{
"alg": "HS256", // 签名算法:HS256
"typ": "JWT" // 类型:JWT
}
2. 载荷(Payload)
这是令牌的“核心内容”,存放实际要传递的信息:
{
"sub": "1234567890", // 用户ID
"name": "John Doe", // 用户名
"iat": 1516239022, // 签发时间
"exp": 1516242622 // 过期时间
}
3. 签名(Signature)
这是最关键的部分!它像“防伪标识”,确保令牌没有被篡改。
签名的生成方式:
HMACSHA256(
base64UrlEncode(头部) + "." + base64UrlEncode(载荷),
密钥
)
用Python玩转JWT
让我们通过代码实际体验一下JWT的使用。首先安装必要的库:
pip install PyJWT
场景1:用户登录后生成JWT
import jwt
import datetime
# 密钥(重要!实际项目中要从安全的地方获取)
SECRET_KEY = "my_secret_key_12345"
def create_jwt(user_id: str, username: str) -> str:
"""
创建JWT令牌
"""
# 设置令牌的过期时间(例如:24小时后)
expiration = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=24)
# 构建载荷(Payload)
payload = {
"user_id": user_id,
"username": username,
"exp": expiration, # 过期时间
"iat": datetime.datetime.now(datetime.timezone.utc) # 签发时间
}
# 生成JWT
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
return token
# 示例:用户登录成功后生成令牌
token = create_jwt("user123", "张三")
print("生成的JWT令牌:")
print(token)
print("-" * 50)
场景2:验证收到的JWT
import jwt
from typing import Dict
# 密钥(重要!实际项目中要从安全的地方获取)
SECRET_KEY = "my_secret_key_12345"
def verify_jwt(token: str) -> Dict:
"""
验证JWT令牌
返回解码后的数据或抛出异常
"""
try:
# 验证并解码令牌
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
return {
"valid": True,
"data": payload,
"message": "令牌有效"
}
except jwt.ExpiredSignatureError:
return {
"valid": False,
"data": None,
"message": "令牌已过期"
}
except jwt.InvalidTokenError:
return {
"valid": False,
"data": None,
"message": "无效的令牌"
}
# 示例:验证令牌
print("验证令牌结果:")
result = verify_jwt(token) # 在这里输入上一节返回的JWT token
if result["valid"]:
print("✓ 令牌有效")
print(f"用户信息:{result['data']}")
else:
print(f"✗ {result['message']}")
print("-" * 50)
输出:
验证令牌结果:
✓ 令牌有效
用户信息:{'user_id': 'user123', 'username': '张三', 'exp': 1766732992, 'iat': 1766646592}
场景3:完整的登录验证流程
import jwt
import datetime
from typing import Dict
# 密钥(重要!实际项目中要从安全的地方获取)
SECRET_KEY = "my_secret_key_12345"
def create_jwt(user_id: str, username: str) -> str:
"""
创建JWT令牌
"""
# 设置令牌的过期时间(例如:24小时后)
expiration = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=24)
# 构建载荷(Payload)
payload = {
"user_id": user_id,
"username": username,
"exp": expiration, # 过期时间
"iat": datetime.datetime.now(datetime.timezone.utc) # 签发时间
}
# 生成JWT
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
return token
def verify_jwt(token: str) -> Dict:
"""
验证JWT令牌
返回解码后的数据或抛出异常
"""
try:
# 验证并解码令牌
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
return {
"valid": True,
"data": payload,
"message": "令牌有效"
}
except jwt.ExpiredSignatureError:
return {
"valid": False,
"data": None,
"message": "令牌已过期"
}
except jwt.InvalidTokenError:
return {
"valid": False,
"data": None,
"message": "无效的令牌"
}
# 模拟用户数据库
users_db = {
"user123": {
"password": "password123", # 实际中应该存储哈希值,而不是明文!
"username": "张三",
"role": "user"
},
"admin001": {
"password": "admin_pass",
"username": "管理员",
"role": "admin"
}
}
def login_and_get_token(user_id: str, password: str):
"""
模拟登录过程
"""
# 1. 检查用户是否存在
if user_id not in users_db:
return None, "用户不存在"
# 2. 验证密码
if users_db[user_id]["password"] != password:
return None, "密码错误"
# 3. 生成JWT令牌
user_info = users_db[user_id]
token = create_jwt(user_id, user_info["username"])
return token, "登录成功"
def access_protected_resource(token: str):
"""
访问需要权限的资源
"""
result = verify_jwt(token)
if not result["valid"]:
return f"访问被拒绝:{result['message']}"
user_data = result["data"]
return f"欢迎 {user_data['username']}!您已成功访问受保护资源。"
# 模拟完整流程
print("=== 完整登录访问流程 ===")
# 1. 用户登录
print("1. 用户登录...")
token, message = login_and_get_token("user123", "password123")
print(f"登录结果:{message}")
if token:
print(f"获取到的令牌:{token[:50]}...")
print("-" * 30)
# 2. 访问受保护资源
print("2. 访问受保护资源...")
if token:
response = access_protected_resource(token)
print(response)
print("-" * 30)
# 3. 演示过期令牌
print("3. 演示过期令牌...")
# 创建一个立即过期的令牌
expired_payload = {
"user_id": "user123",
"username": "张三",
"exp": datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(seconds=1), # 1秒前过期
"iat": datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(hours=1)
}
expired_token = jwt.encode(expired_payload, SECRET_KEY, algorithm="HS256")
response = access_protected_resource(expired_token)
print(response)
输出:
=== 完整登录访问流程 ===
1. 用户登录...
登录结果:登录成功
获取到的令牌:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkI...
------------------------------
2. 访问受保护资源...
欢迎 张三!您已成功访问受保护资源。
------------------------------
3. 演示过期令牌...
访问被拒绝:令牌已过期
JWT的实际应用场景
1. 单点登录(SSO)
用户在一个系统登录后,无需在其他关联系统重新登录。
2. API身份验证
移动App、前端应用调用后端API时携带JWT。
3. 信息交换
安全地在各方之间传递信息,因为签名可以验证内容是否被篡改。
重要安全注意事项
✅ 应该做的:
-
使用HTTPS:防止令牌在传输中被窃取
-
设置合理的过期时间:通常几小时到几天
-
存储敏感信息要加密:载荷默认只是编码,不是加密!
-
密钥要足够复杂:并且定期更换
❌ 不要做的:
-
不要在JWT中存储密码等敏感信息
-
不要将密钥硬编码在代码中
-
不要使用弱签名算法
-
前端存储要注意XSS攻击(考虑使用HttpOnly Cookie)
常见问题解答
Q:JWT和Session有什么区别?
A:Session把用户状态存在服务器,JWT把状态存在令牌里发给客户端。
Q:JWT被偷了怎么办?
A:就像手环被偷一样,小偷可以冒充你。因此过期时间要短,重要操作需二次验证。
Q:如何让JWT失效?
A:JWT一旦签发,在过期前无法主动失效。解决方案:使用短有效期+刷新令牌机制,或维护一个小的令牌黑名单。
总结
JWT就像数字世界的“身份证+防伪标识”:
-
头部说明类型和算法
-
载荷携带实际信息
-
签名确保不被篡改
它的优点是无状态、易扩展,适合现代分布式应用。但也要注意安全使用,特别是密钥管理和令牌存储。
希望这篇介绍能帮你理解JWT!在实际项目中,合理使用JWT能让你的应用更安全、更高效。
提示:本文示例代码用于学习演示,实际生产环境中需要考虑更多安全因素。建议使用成熟的认证库(如Authlib、Django REST Framework JWT等)来处理复杂的认证场景。