Python 接口自动化必看:Token 过期的9种解决方案
为什么Token管理是接口自动化的痛点?
在进行Python接口自动化测试时,相信大家都遇到过这样的困扰:
❌ 每次跑用例前手动更新 Token
❌ 测试中途 Token 过期,用例批量失败
❌ 多环境(dev/test/prod)Token 管理混乱
❌ 登录接口变化,所有用例都要改
为什么Token需要自动刷新?
在Python接口自动化测试中,Token自动刷新不是一个可有可无的"锦上添花"功能,而是确保测试稳定性和可靠性的核心机制。
例如:大多数基于Token的认证机制(如JWT、OAuth2)都会为Token设置一个有效期(例如30分钟、2小时等)。一旦Token过期,接口就会返回401 Unauthorized错误,导致测试用例失败。这会导致测试的连续性和稳定性,影响测试效率,也会影响自动化测试模拟用户真实行为(如用户会在Token过期后通过刷新Token或重新登录来继续操作)。
Token 刷新的常见场景

Token过期的9种解决方案
解决方案1:简单粗暴型 - 每次请求前重新登录
核心思路:每次发送请求前都先调用登录接口获取新Token
def get_fresh_token():
"""每次请求前获取新Token"""
login_data = {"username": "testuser", "password": "testpass"}
resp = requests.post(f"{BASE_URL}/login", json=login_data)
return resp.json()["data"]["access_token"]
def test_api_with_fresh_token():
token = get_fresh_token()
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{BASE_URL}/api/data", headers=headers)
return response.json()
优缺点分析:
优点:实现简单,保证每次都是新Token;
缺点:效率极低,增加额外请求,频繁登录可能触发风控
解决方案2:前置获取型 - 用例执行前统一获取
核心思路:在测试套件执行前一次性获取Token,所有用例共享。
import pytest
class TestBase:
token = None
@classmethod
def setup_class(cls):
"""测试类执行前获取Token"""
cls.token = cls.get_token_once()
@classmethod
def get_token_once(cls):
login_data = {"username": "testuser", "password": "testpass"}
resp = requests.post(f"{BASE_URL}/login", json=login_data)
return resp.json()["data"]["access_token"]
def test_user_info(self):
headers = {"Authorization": f"Bearer {self.token}"}
response = requests.get(f"{BASE_URL}/user/info", headers=headers)
assert response.status_code == 200
优缺点分析:
优点:减少登录次数,提高效率;
缺点:长时间运行测试时Token仍会过期。
解决方案3:智能检测型 - 请求失败时重新登录
核心思路:捕获401错误,自动重新登录并重试请求
class SmartTokenClient:
def __init__(self, base_url, username, password):
self.base_url = base_url
self.username = username
self.password = password
self.token = None
def login(self):
"""登录获取Token"""
login_data = {"username": self.username, "password": self.password}
resp = requests.post(f"{self.base_url}/login", json=login_data)
self.token = resp.json()["data"]["access_token"]
return self.token
def request_with_retry(self, method, endpoint, **kwargs):
"""带重试机制的请求"""
if not self.token:
self.login()
headers = kwargs.get('headers', {})
headers['Authorization'] = f'Bearer {self.token}'
kwargs['headers'] = headers
response = requests.request(method, f"{self.base_url}{endpoint}", **kwargs)
# 检测到Token过期,重新登录并重试
if response.status_code == 401:
print("检测到Token过期,重新登录...")
self.login()
kwargs['headers']['Authorization'] = f'Bearer {self.token}'
response = requests.request(method, f"{self.base_url}{endpoint}", **kwargs)
return response
client = SmartTokenClient(BASE_URL, "testuser", "testpass")
response = client.request_with_retry("GET", "/api/users")
优缺点分析
优点:自动处理过期情况,用户体验好;
缺点:第一次401错误无法避免,需要重试。
解决方案4:主动刷新型 - 定期刷新Token
核心思路:记录Token获取时间,在过期前主动刷新
import time
class ProactiveTokenClient:
def __init__(self, base_url, username, password, token_ttl=1800):
self.base_url = base_url
self.username = username
self.password = password
self.token_ttl = token_ttl # Token有效期,默认30分钟
self.token = None
self.token_time = 0
def get_token(self):
"""获取Token,如果快过期则主动刷新"""
current_time = time.time()
# 没有Token或Token即将过期(提前5分钟刷新)
if not self.token or (current_time - self.token_time) > (self.token_ttl - 300):
self.refresh_token()
return self.token
def refresh_token(self):
"""刷新Token"""
login_data = {"username": self.username, "password": self.password}
resp = requests.post(f"{self.base_url}/login", json=login_data)
self.token = resp.json()["data"]["access_token"]
self.token_time = time.time()
print(f"Token已刷新: {self.token[:20]}...")
def get(self, endpoint, **kwargs):
"""GET请求"""
token = self.get_token()
headers = kwargs.get('headers', {})
headers['Authorization'] = f'Bearer {token}'
kwargs['headers'] = headers
return requests.get(f"{self.base_url}{endpoint}", **kwargs)
优缺点分析:
优点:避免401错误,用户体验最佳;
缺点:需要知道Token准确有效期,实现稍复杂。
解决方案5:双Token轮换型 - Access Token + Refresh Token
核心思路:使用Refresh Token静默刷新Access Token,用户无感知
class DualTokenClient:
def __init__(self, base_url, username, password):
self.base_url = base_url
self.username = username
self.password = password
self.access_token = None
self.refresh_token = None
self.setup_tokens()
def setup_tokens(self):
"""初始获取双Token"""
login_data = {"username": self.username, "password": self.password}
resp = requests.post(f"{self.base_url}/login", json=login_data)
token_data = resp.json()["data"]
self.access_token = token_data["access_token"]
self.refresh_token = token_data["refresh_token"]
def refresh_access_token(self):
"""使用Refresh Token刷新Access Token"""
refresh_data = {"refresh_token": self.refresh_token}
try:
resp = requests.post(f"{self.base_url}/refresh", json=refresh_data)
if resp.status_code == 200:
token_data = resp.json()["data"]
self.access_token = token_data["access_token"]
# 有些系统会返回新的refresh_token
if "refresh_token" in token_data:
self.refresh_token = token_data["refresh_token"]
return True
except Exception as e:
print(f"刷新Token失败: {e}")
# 刷新失败,重新登录
self.setup_tokens()
return False
def request(self, method, endpoint, max_retry=1, **kwargs):
"""支持Token自动刷新的请求"""
for attempt in range(max_retry + 1):
headers = kwargs.get('headers', {})
headers['Authorization'] = f'Bearer {self.access_token}'
kwargs['headers'] = headers
response = requests.request(method, f"{self.base_url}{endpoint}", **kwargs)
if response.status_code != 401:
return response
if attempt < max_retry:
print("Access Token过期,尝试刷新...")
if self.refresh_access_token():
continue
return response
优缺点分析:
优点:用户体验极佳,安全性高;
缺点:需要服务端支持双Token机制。
解决方案6:装饰器模式 - 无侵入式Token管理
核心思路:使用装饰器自动为请求方法添加Token管理功能。
def auto_token_refresh(func):
"""自动Token刷新装饰器"""
def wrapper(self, *args, **kwargs):
# 确保有有效Token
self.ensure_valid_token()
# 执行原函数
result = func(self, *args, **kwargs)
# 如果返回401,刷新Token后重试
if hasattr(result, 'status_code') and result.status_code == 401:
print("检测到401错误,自动刷新Token并重试...")
self.refresh_token()
result = func(self, *args, **kwargs)
return result
return wrapper
class DecoratorTokenClient:
def __init__(self, base_url, username, password):
self.base_url = base_url
self.username = username
self.password = password
self.token = None
def login(self):
"""登录获取Token"""
login_data = {"username": self.username, "password": self.password}
resp = requests.post(f"{self.base_url}/login", json=login_data)
self.token = resp.json()["data"]["access_token"]
return self.token
def refresh_token(self):
"""刷新Token"""
return self.login()
def ensure_valid_token(self):
"""确保有有效的Token"""
if not self.token:
self.login()
@auto_token_refresh
def get_users(self):
"""获取用户列表"""
headers = {"Authorization": f"Bearer {self.token}"}
return requests.get(f"{self.base_url}/users", headers=headers) @ auto_token_refresh
@auto_token_refresh
def create_order(self, order_data):
"""创建订单"""
headers = {"Authorization": f"Bearer {self.token}"}
return requests.post(f"{self.base_url}/orders", json=order_data,
headers=headers)
client = DecoratorTokenClient(BASE_URL, "testuser", "testpass")
response = client.get_users() # 自动处理Token
优缺点分析:
优点:代码简洁,无侵入性,易于维护;
缺点:需要为每个方法添加装饰器。
解决方案7:会话保持型 - 使用requests.Session。
核心思路:利用Session对象保持登录状态,自动处理Cookie。
class SessionTokenClient:
def __init__(self, base_url: str, username: str, password: str):
self.base_url = base_url.rstrip("/")
self.username = username
self.password = password
self.session = requests.Session()
self.is_logged_in = False
def login(self):
"""登录并缓存 Token 到 session.headers"""
login_data = {"username": self.username, "password": self.password}
response = self.session.post(f"{self.base_url}/login", json=login_data)
if response .status_code == 200:
self.is_logged_in = True
# 如果是Token认证,可能需要手动设置Header
token = response .json()["data"]["access_token"]
self.session.headers.update({"Authorization": f"Bearer {token}"})
return response
def ensure_login(self):
"""懒登录:没登陆就登一下"""
if not self.is_logged_in:
self.login()
def get(self, endpoint: str, **kwargs):
self.ensure_login()
response = self.session.get(f"{self.base_url}{endpoint}", **kwargs)
# 检查是否因会话过期而失败
if response.status_code == 401:
print("会话过期,重新登录...")
self.login()
response = self.session.get(f"{self.base_url}{endpoint}", **kwargs)
return response
def post(self, endpoint: str, **kwargs):
self.ensure_login()
response = self.session.post(f"{self.base_url}{endpoint}", **kwargs)
if response.status_code == 401:
print("会话过期,重新登录...")
self.login()
response = self.session.post(f"{self.base_url}{endpoint}", **kwargs)
return response
client = SessionTokenClient(BASE_URL, "testuser", "testpass")
users = client.get("/api/users").json()
优缺点分析:
优点:自动处理Cookie,适合基于会话的系统;
缺点:不适合纯Token认证的系统。
解决方案8:工厂模式 - 多环境Token管理
核心思路:使用工厂模式管理不同环境的Token,支持动态切换。
from abc import ABC, abstractmethod
import requests
class TokenManager(ABC):
"""Token 管理器抽象类"""
@abstractmethod
def get_token(self) -> str:
"""获取当前可用令牌"""
pass
@abstractmethod
def refresh_token(self) -> bool:
"""刷新令牌,成功返回 True"""
pass
class DevTokenManager(TokenManager):
"""开发环境 Token 管理器"""
def get_token(self) -> str:
# 开发环境可能返回固定Token或mock Token
return "dev_mock_token"
def refresh_token(self) -> bool:
return True
class TestTokenManager(TokenManager):
"""测试环境 Token 管理器(账号密码登录)"""
def __init__(self, base_url, username, password):
self.base_url = base_url
self.username = username
self.password = password
self.token = None
def get_token(self) -> str:
if not self.token:
self.refresh_token()
return self.token
def refresh_token(self) -> bool:
login_data = {"username": self.username, "password": self.password}
resp = requests.post(f"{self.base_url}/login", json=login_data)
self.token = resp.json()["data"]["access_token"]
return True
class ProdTokenManager(TokenManager):
"""生产环境Token管理器 - 更严格的安全控制"""
def get_token(self) -> str:
raise RuntimeError("生产环境请使用正式认证流程")
def refresh_token(self) -> bool:
raise RuntimeError("生产环境请使用正式认证流程")
class TokenManagerFactory:
"""Token管理器工厂"""
@staticmethod
def create_manager(env: str, **kwargs) -> TokenManager:
if env == "dev":
return DevTokenManager()
elif env == "test":
return TestTokenManager(kwargs["base_url"], kwargs["username"], kwargs["password"])
elif env == "prod":
return ProdTokenManager()
else:
raise ValueError(f"不支持的环境: {env}")
if __name__ == "__main__":
token_manager = TokenManagerFactory.create_manager(
env="test",
base_url="https://test.api.com",
username="testuser",
password="testpass",
)
token = token_manager.get_token()
headers = {"Authorization": f"Bearer {token}"}
response = requests.get("https://test.api.com/api/data", headers=headers)
优缺点分析:
优点:支持多环境,扩展性强,符合开闭原则;
缺点:实现相对复杂,过度设计对于简单项目。
解决方案9:终极方案 - 封装支持自动刷新的AuthClient。
核心思路:设计一个 AuthHttpClient 类,具备以下能力:
-
自动登录获取 Token;
-
请求时自动携带 Token;
-
检测 401 错误,自动刷新;
-
刷新失败则重新登录;
-
Token 持久化(文件或内存);
-
线程安全,支持并发。
import requests
import json
import os
import time
from typing import Optional, Dict, Any
# =======================
# ?? 认证客户端(支持自动刷新)
# =======================
class AuthHttpClient:
def __init__(
self,
base_url: str,
login_url: str = "/auth/login",
refresh_url: str = "/auth/refresh",
username: str = "",
password: str = "",
token_file: str = "cache/token.json"
):
self.base_url = base_url.rstrip("/")
self.login_url = login_url
self.refresh_url = refresh_url
self.username = username
self.password = password
self.token_file = token_file
self.session = requests.Session()
# 确保缓存目录存在
os.makedirs(os.path.dirname(token_file), exist_ok=True)
def _save_token(self, token: str, refresh_token: str, expires_in: int):
"""保存 Token 到本地文件"""
data = {
"access_token": token,
"refresh_token": refresh_token,
"expires_at": time.time() + expires_in - 60, # 提前60秒过期
"username": self.username
}
with open(self.token_file, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
def _load_token(self) -> Optional[Dict[str, Any]]:
"""从文件加载 Token"""
if not os.path.exists(self.token_file):
return None
try:
with open(self.token_file, "r", encoding="utf-8") as f:
return json.load(f)
except:
return None
def _is_token_expired(self) -> bool:
"""检查 Token 是否过期"""
token_data = self._load_token()
if not token_data:
return True
return time.time() >= token_data.get("expires_at", 0)
def login(self) -> bool:
"""登录并获取 Token"""
try:
resp = self.session.post(
f"{self.base_url}{self.login_url}",
json={"username": self.username, "password": self.password}
)
if resp.status_code == 200:
data = resp.json()
token = data["data"]["access_token"]
refresh_token = data["data"]["refresh_token"]
expires_in = data["data"].get("expires_in", 1800) # 默认30分钟
self._save_token(token, refresh_token, expires_in)
self.session.headers.update({"Authorization": f"Bearer {token}"})
print(f"? 登录成功,Token 已保存")
return True
else:
print(f"? 登录失败: {resp.text}")
return False
except Exception as e:
print(f"? 登录异常: {e}")
return False
def refresh_token(self) -> bool:
"""刷新 Token"""
token_data = self._load_token()
if not token_data:
return False
try:
resp = self.session.post(
f"{self.base_url}{self.refresh_url}",
json={"refresh_token": token_data["refresh_token"]}
)
if resp.status_code == 200:
data = resp.json()
new_token = data["data"]["access_token"]
new_refresh = data["data"]["refresh_token"]
expires_in = data["data"].get("expires_in", 1800)
self._save_token(new_token, new_refresh, expires_in)
self.session.headers.update({"Authorization": f"Bearer {new_token}"})
print(f"?? Token 刷新成功")
return True
else:
print(f"? Token 刷新失败: {resp.text}")
return False
except Exception as e:
print(f"? 刷新异常: {e}")
return False
def ensure_auth(self) -> bool:
"""确保当前有有效 Token(自动登录或刷新)"""
if self._is_token_expired():
print("?? Token 已过期,尝试刷新...")
if not self.refresh_token():
print("?? 刷新失败,正在重新登录...")
return self.login()
else:
# 加载有效 Token
token_data = self._load_token()
self.session.headers.update({"Authorization": f"Bearer {token_data['access_token']}"})
print("?? 使用现有 Token")
return True
def request(self, method: str, url: str, **kwargs) -> requests.Response:
"""重写 request,自动处理认证"""
full_url = self.base_url + ("" if url.startswith("/") else "/") + url
# 确保认证有效
if not self.ensure_auth():
raise Exception("认证失败,无法继续请求")
try:
resp = self.session.request(method, full_url, **kwargs)
# 如果返回 401,可能是刷新失败,尝试重新登录
if resp.status_code == 401:
print("?? 收到 401,尝试重新登录...")
self.login()
# 重新发送请求
resp = self.session.request(method, full_url, **kwargs)
return resp
except Exception as e:
print(f"? 请求异常: {e}")
raise
# 便捷方法
def get(self, url, **kwargs):
return self.request("GET", url, **kwargs)
def post(self, url, **kwargs):
return self.request("POST", url, **kwargs)
def put(self, url, **kwargs):
return self.request("PUT", url, **kwargs)
def delete(self, url, **kwargs):
return self.request("DELETE", url, **kwargs)
优缺点分析:
这个方案融合了前面所有方案的优点,提供了最完善、最健壮的Token自动刷新机制。

浙公网安备 33010602011771号