HMAC refresh token刷新代码示例及切换JWT解决过期问题的疑问
问题解答
切换到JWT是否能解决你的问题?
是的,切换到JWT完全可以解决你当前的问题。JWT的访问令牌(Access Token)设计为短期有效期(比如2分钟),配合长期有效的Refresh Token(比如30天),当Access Token过期时,客户端可以用Refresh Token向专门的接口请求新的Access Token,无需用户重新登录。
你当前用HMAC遇到的问题,核心是没有一套成熟的令牌刷新机制——每次Access Token过期都需要重新发起认证流程,而如果认证流程本身耗时或者令牌有效期过短,就容易出现请求前令牌已过期的情况。JWT的Refresh Token机制正好填补了这个空白,让令牌刷新流程自动化且无需用户参与。
HMAC实现Refresh Token的示例代码(Python)
以下是一个基于HMAC的Refresh Token实现示例,包含令牌生成、验证、刷新逻辑:
依赖安装
pip install cryptography python-dotenv
核心代码
import hmac import hashlib import time from datetime import datetime, timedelta from typing import Optional, Dict import os from dotenv import load_dotenv load_dotenv() SECRET_KEY = os.getenv("HMAC_SECRET_KEY").encode() ACCESS_TOKEN_EXPIRE_MINUTES = 2 REFRESH_TOKEN_EXPIRE_DAYS = 30 # 模拟数据库存储Refresh Token(实际项目用真实数据库) refresh_token_store: Dict[str, Dict] = {} def generate_hmac_token(payload: Dict) -> str: """生成带HMAC签名的令牌""" # 将载荷转为有序字符串(避免签名因键顺序变化失效) payload_str = "&".join([f"{k}={v}" for k, v in sorted(payload.items())]) # 生成HMAC签名 signature = hmac.new(SECRET_KEY, payload_str.encode(), hashlib.sha256).hexdigest() # 返回格式:载荷字符串|签名 return f"{payload_str}|{signature}" def verify_hmac_token(token: str) -> Optional[Dict]: """验证HMAC令牌并解析载荷""" try: payload_str, signature = token.split("|", 1) # 重新计算签名并校验(用compare_digest避免时序攻击) computed_signature = hmac.new(SECRET_KEY, payload_str.encode(), hashlib.sha256).hexdigest() if not hmac.compare_digest(signature, computed_signature): return None # 解析载荷 payload = {} for item in payload_str.split("&"): k, v = item.split("=", 1) payload[k] = int(v) if k == "exp" else v # 检查令牌是否过期 if payload.get("exp", 0) < time.time(): return None return payload except: return None def generate_access_and_refresh_tokens(user_id: str) -> Dict[str, str]: """生成Access Token和Refresh Token""" # 生成短期Access Token access_exp = int((datetime.now() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)).timestamp()) access_payload = {"user_id": user_id, "exp": access_exp, "type": "access"} access_token = generate_hmac_token(access_payload) # 生成长期Refresh Token refresh_exp = int((datetime.now() + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)).timestamp()) refresh_payload = {"user_id": user_id, "exp": refresh_exp, "type": "refresh"} refresh_token = generate_hmac_token(refresh_payload) # 存储Refresh Token(用于后续校验有效性) refresh_token_store[refresh_token] = {"user_id": user_id, "exp": refresh_exp} return { "access_token": access_token, "refresh_token": refresh_token, "access_expires_in": ACCESS_TOKEN_EXPIRE_MINUTES * 60, "refresh_expires_in": REFRESH_TOKEN_EXPIRE_DAYS * 86400 } def refresh_access_token(refresh_token: str) -> Optional[Dict[str, str]]: """用Refresh Token刷新Access Token""" # 验证Refresh Token的签名和格式 payload = verify_hmac_token(refresh_token) if not payload or payload.get("type") != "refresh": return None # 检查存储中的Refresh Token是否有效(防止已注销或过期) stored_token = refresh_token_store.get(refresh_token) if not stored_token or stored_token["exp"] < time.time() or stored_token["user_id"] != payload["user_id"]: return None # 生成新的Access Token(可选择同时生成新的Refresh Token) return generate_access_and_refresh_tokens(payload["user_id"]) # 示例用法 if __name__ == "__main__": # 生成初始令牌对 tokens = generate_access_and_refresh_tokens("user_123") print("初始令牌:", tokens) # 等待3分钟(模拟Access Token过期) time.sleep(180) # 刷新Access Token new_tokens = refresh_access_token(tokens["refresh_token"]) if new_tokens: print("刷新后的令牌:", new_tokens) else: print("Refresh Token无效或已过期")
关键说明
- 令牌格式采用
载荷字符串|HMAC签名,载荷必须包含exp(过期时间戳)和type(区分access/refresh令牌) - Refresh Token需要后端持久化存储(示例用字典模拟,实际推荐用Redis或关系型数据库),用于验证有效性和防止重复使用
- 签名校验必须使用
hmac.compare_digest,避免时序攻击导致的签名泄露
内容的提问来源于stack exchange,提问作者FSTL
相关产品推荐
相关产品推荐

