You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Django中使用PyJWT实现登出并销毁Token?

如何在PyJWT中实现Token销毁与用户登出功能?

Hey there! 首先得明确一个关键事实:JWT本身是无状态且自包含的——一旦签发出去,除非到达预设的过期时间(也就是你代码里的exp字段),否则它本身没法被主动"销毁"。所以我们得通过额外的服务器端机制来实现Token失效和用户登出的效果,下面是几个实用的方案,结合你的现有代码来讲解:

方案1:维护Token黑名单(最常用的即时失效方案)

核心思路是在服务器端维护一个存储(推荐用Redis,也可以用内存缓存或数据库),把需要立即失效的Token存进黑名单。每次验证Token时,先检查它是否在黑名单里,再进行正常解析。

具体实现步骤

  1. 用户发起登出请求时,将当前Token加入黑名单,并设置黑名单的过期时间和Token本身的exp一致,避免无效数据堆积。
  2. 修改你的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

火山引擎 最新活动