如何在Flask的JWT认证中添加Refresh Token(刷新令牌)
给JWT授权添加Refresh Token功能
核心思路
- Access Token(访问令牌)保持短时效(比如15分钟),用于接口授权校验
- Refresh Token(刷新令牌)设置更长时效(比如7天),专门用于获取新的Access Token
- 必须存储Refresh Token,确保只有合法有效的令牌能换取新的Access Token
第一步:调整登录接口,生成双令牌
先修正原代码中JWT过期时间的不规范写法(原代码将过期时间转为字符串不符合JWT标准),同时生成Access Token和Refresh Token,并存储Refresh Token:
# 生产环境建议用Redis或数据库存储,这里用字典做临时示例 refresh_tokens = {} @app.route("/login", methods=['POST']) def login(): request_json = request.get_json() username = request_json.get("username") if not username: return jsonify({"message": "用户名必填"}), 400 # 生成Access Token(15分钟有效期) access_token_payload = { "user": username, "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=15) } access_token = jwt.encode(access_token_payload, str(app.config['SECRET_KEY']), algorithm='HS256') # 生成Refresh Token(7天有效期) refresh_token_payload = { "user": username, "exp": datetime.datetime.utcnow() + datetime.timedelta(days=7) } refresh_token = jwt.encode(refresh_token_payload, str(app.config['SECRET_KEY']), algorithm='HS256') # 存储Refresh Token,绑定用户名 refresh_tokens[refresh_token] = username return jsonify({ "access_token": access_token, "refresh_token": refresh_token, "token_type": "Bearer" })
第二步:实现Refresh Token刷新接口
创建接口接收Refresh Token,验证通过后返回新的Access Token:
@app.route("/refresh", methods=['POST']) def refresh(): request_json = request.get_json() refresh_token = request_json.get("refresh_token") if not refresh_token: return jsonify({"message": "refresh token 必填"}), 400 # 先校验Refresh Token是否在存储中 if refresh_token not in refresh_tokens: return jsonify({"message": "无效或已过期的refresh token"}), 401 try: # 解码Refresh Token payload = jwt.decode(refresh_token, str(app.config['SECRET_KEY']), algorithms=['HS256']) username = payload.get("user") # 生成新的Access Token new_access_token_payload = { "user": username, "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=15) } new_access_token = jwt.encode(new_access_token_payload, str(app.config['SECRET_KEY']), algorithm='HS256') return jsonify({ "access_token": new_access_token, "token_type": "Bearer" }) except jwt.ExpiredSignatureError: # 过期则移除存储的Refresh Token del refresh_tokens[refresh_token] return jsonify({"message": "refresh token 已过期"}), 401 except jwt.InvalidTokenError: return jsonify({"message": "无效的refresh token"}), 401
第三步:保留原有的Access Token验证装饰器
原token_required装饰器无需修改,依然用于保护需要授权的接口:
from functools import wraps from flask import request, jsonify import jwt def token_required(func): @wraps(func) def decorated(*args, **kwargs): token = None if "x-access-token" in request.headers: token = request.headers['x-access-token'] if not token: return jsonify({ "message": "token is required" }), 401 try: payload = jwt.decode(token, str(app.config['SECRET_KEY']), algorithms=['HS256']) except Exception as ee: print(ee) return jsonify({ "message": "invalid token" }), 401 return func(*args, **kwargs) return decorated # 示例保护接口 @app.route("/protected", methods=['GET']) @token_required def protected(): return jsonify({"message": "这是受保护的内容"})
生产环境注意事项
- 不要用全局字典存储Refresh Token,改用Redis(支持自动过期)或数据库,避免服务重启后令牌丢失
- 可以给Refresh Token添加
jti(JWT唯一ID)字段,方便精准注销令牌 - 用户修改密码或主动注销时,要及时从存储中删除对应的Refresh Token
内容的提问来源于stack exchange,提问作者Andrew L.
相关产品推荐
相关产品推荐

