诸神缄默不语-个人技术博文与视频目录
文章目录
- 什么是JWT?一个简单的比喻
- 为什么需要JWT?
- JWT长什么样?
- 1. 头部(Header)
- 2. 载荷(Payload)
- 3. 签名(Signature)
- 用Python玩转JWT
- 场景1:用户登录后生成JWT
- 场景2:验证收到的JWT
- 场景3:完整的登录验证流程
- JWT的实际应用场景
- 1. **单点登录(SSO)**
- 2. **API身份验证**
- 3. **信息交换**
- 重要安全注意事项
- ✅ **应该做的:**
- ❌ **不要做的:**
- 常见问题解答
- 总结
什么是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的使用。首先安装必要的库:
pipinstallPyJWT场景1:用户登录后生成JWT
importjwtimportdatetime# 密钥(重要!实际项目中要从安全的地方获取)SECRET_KEY="my_secret_key_12345"defcreate_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)# 签发时间}# 生成JWTtoken=jwt.encode(payload,SECRET_KEY,algorithm="HS256")returntoken# 示例:用户登录成功后生成令牌token=create_jwt("user123","张三")print("生成的JWT令牌:")print(token)print("-"*50)场景2:验证收到的JWT
importjwtfromtypingimportDict# 密钥(重要!实际项目中要从安全的地方获取)SECRET_KEY="my_secret_key_12345"defverify_jwt(token:str)->Dict:""" 验证JWT令牌 返回解码后的数据或抛出异常 """try:# 验证并解码令牌payload=jwt.decode(token,SECRET_KEY,algorithms=["HS256"])return{"valid":True,"data":payload,"message":"令牌有效"}exceptjwt.ExpiredSignatureError:return{"valid":False,"data":None,"message":"令牌已过期"}exceptjwt.InvalidTokenError:return{"valid":False,"data":None,"message":"无效的令牌"}# 示例:验证令牌print("验证令牌结果:")result=verify_jwt(token)# 在这里输入上一节返回的JWT tokenifresult["valid"]:print("✓ 令牌有效")print(f"用户信息:{result['data']}")else:print(f"✗{result['message']}")print("-"*50)输出:
验证令牌结果: ✓ 令牌有效 用户信息:{'user_id': 'user123', 'username': '张三', 'exp': 1766732992, 'iat': 1766646592}场景3:完整的登录验证流程
importjwtimportdatetimefromtypingimportDict# 密钥(重要!实际项目中要从安全的地方获取)SECRET_KEY="my_secret_key_12345"defcreate_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)# 签发时间}# 生成JWTtoken=jwt.encode(payload,SECRET_KEY,algorithm="HS256")returntokendefverify_jwt(token:str)->Dict:""" 验证JWT令牌 返回解码后的数据或抛出异常 """try:# 验证并解码令牌payload=jwt.decode(token,SECRET_KEY,algorithms=["HS256"])return{"valid":True,"data":payload,"message":"令牌有效"}exceptjwt.ExpiredSignatureError:return{"valid":False,"data":None,"message":"令牌已过期"}exceptjwt.InvalidTokenError:return{"valid":False,"data":None,"message":"无效的令牌"}# 模拟用户数据库users_db={"user123":{"password":"password123",# 实际中应该存储哈希值,而不是明文!"username":"张三","role":"user"},"admin001":{"password":"admin_pass","username":"管理员","role":"admin"}}deflogin_and_get_token(user_id:str,password:str):""" 模拟登录过程 """# 1. 检查用户是否存在ifuser_idnotinusers_db:returnNone,"用户不存在"# 2. 验证密码ifusers_db[user_id]["password"]!=password:returnNone,"密码错误"# 3. 生成JWT令牌user_info=users_db[user_id]token=create_jwt(user_id,user_info["username"])returntoken,"登录成功"defaccess_protected_resource(token:str):""" 访问需要权限的资源 """result=verify_jwt(token)ifnotresult["valid"]:returnf"访问被拒绝:{result['message']}"user_data=result["data"]returnf"欢迎{user_data['username']}!您已成功访问受保护资源。"# 模拟完整流程print("=== 完整登录访问流程 ===")# 1. 用户登录print("1. 用户登录...")token,message=login_and_get_token("user123","password123")print(f"登录结果:{message}")iftoken:print(f"获取到的令牌:{token[:50]}...")print("-"*30)# 2. 访问受保护资源print("2. 访问受保护资源...")iftoken: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等)来处理复杂的认证场景。