如何在Django中使用PyJWT实现登出并销毁Token?
如何在PyJWT中实现Token销毁与用户登出功能?
Hey there! 首先得明确一个关键事实:JWT本身是无状态且自包含的——一旦签发出去,除非到达预设的过期时间(也就是你代码里的exp字段),否则它本身没法被主动"销毁"。所以我们得通过额外的服务器端机制来实现Token失效和用户登出的效果,下面是几个实用的方案,结合你的现有代码来讲解:
方案1:维护Token黑名单(最常用的即时失效方案)
核心思路是在服务器端维护一个存储(推荐用Redis,也可以用内存缓存或数据库),把需要立即失效的Token存进黑名单。每次验证Token时,先检查它是否在黑名单里,再进行正常解析。
具体实现步骤
- 用户发起登出请求时,将当前Token加入黑名单,并设置黑名单的过期时间和Token本身的
exp一致,避免无效数据堆积。 - 修改你的Token验证逻辑,先做黑名单校验,再解析Payload。
代码示例(以Redis为例)
首先安装Redis依赖:
pip install redis
登出接口代码(假设用Flask框架,可适配你自己的服务端框架):
import jwt import datetime import redis from flask import request # 初始化Redis连接 redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True) def logout(): # 从请求头获取Token(假设格式为 Bearer <token>) auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return {"message": "缺少有效Token"}, 400 token = auth_header.split(' ')[1] try: # 先不验证过期时间,获取Token的exp字段 payload = jwt.decode(token, 'secret', algorithms=['HS256'], options={"verify_exp": False}) exp_timestamp = payload['exp'] current_timestamp = datetime.datetime.utcnow().timestamp() # 计算剩余有效期,作为Redis的过期时间 ttl = int(exp_timestamp - current_timestamp) if ttl > 0: # 将Token存入黑名单 redis_client.setex(f"blacklist:{token}", ttl, "invalid") return {"message": "登出成功"} except jwt.InvalidTokenError: return {"message": "无效的Token"}, 400
修改你的Token验证逻辑:
def verify_token(token): # 第一步:检查Token是否在黑名单中 if redis_client.exists(f"blacklist:{token}"): raise Exception("Token已失效,请重新登录") # 第二步:正常解析Token payload = jwt.decode(token, 'secret', algorithms=['HS256']) return payload
方案2:缩短访问Token有效期,配合刷新Token
如果觉得维护黑名单麻烦,可以把访问Token(access token)的有效期设得很短(比如15分钟),同时签发一个刷新Token(refresh token),用于获取新的访问Token。用户登出时,只需将刷新Token加入黑名单即可——旧的访问Token很快会过期,无法再使用。
代码示例
签发双Token的逻辑:
def generate_tokens(user_id): # 访问Token:15分钟过期,用于接口请求 access_payload = { "id": user_id, "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=15), "iat": datetime.datetime.utcnow(), "type": "access" } access_token = jwt.encode(access_payload, 'secret', algorithm='HS256') # 刷新Token:7天过期,用于获取新的访问Token refresh_payload = { "id": user_id, "exp": datetime.datetime.utcnow() + datetime.timedelta(days=7), "iat": datetime.datetime.utcnow(), "type": "refresh" } refresh_token = jwt.encode(refresh_payload, 'secret', algorithm='HS256') # 将刷新Token存入Redis,方便登出时失效 redis_client.setex(f"refresh_token:{user_id}", 7*24*3600, refresh_token) return { "access_token": access_token, "refresh_token": refresh_token }
登出时失效刷新Token:
def logout(user_id): # 删除用户对应的刷新Token redis_client.delete(f"refresh_token:{user_id}") return {"message": "登出成功"}
方案3:维护用户的有效Token列表
这种思路适合需要控制用户登录设备数量的场景:每个用户登录时,将新生成的Token存入Redis的用户专属列表;登出时清空该列表,或每次登录时替换旧Token(实现单设备登录)。验证时检查Token是否在用户的有效列表中。
代码示例
登录时存储Token:
def login(user_id): payload = { "id": user_id, "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=1000), "iat": datetime.datetime.utcnow() } token = jwt.encode(payload, 'secret', algorithm='HS256') # 计算Token剩余有效期,作为Redis键的过期时间 exp_timestamp = payload['exp'].timestamp() current_timestamp = datetime.datetime.utcnow().timestamp() ttl = int(exp_timestamp - current_timestamp) # 将Token存入用户的有效Token列表 redis_client.setex(f"valid_tokens:{user_id}:{token}", ttl, "valid") return {"token": token}
登出时清空用户的有效Token:
def logout(user_id): # 匹配该用户的所有有效Token键,批量删除 keys = redis_client.keys(f"valid_tokens:{user_id}:*") if keys: redis_client.delete(*keys) return {"message": "登出成功"}
验证时检查有效性:
def verify_token(token): payload = jwt.decode(token, 'secret', algorithms=['HS256']) user_id = payload['id'] # 检查Token是否在用户的有效列表中 if not redis_client.exists(f"valid_tokens:{user_id}:{token}"): raise Exception("Token已失效,请重新登录") return payload
方案选择建议
- 若需要立即失效Token,优先选黑名单方案;
- 若对即时性要求不高,想减少服务器存储压力,选缩短有效期+刷新Token的方案;
- 若需要控制用户登录设备数量,选有效Token列表方案。
内容的提问来源于stack exchange,提问作者reza_khalafi




