gunicorn多线程部署django导致的登陆错误
使用django写后端,认证系统使用了内存中的令牌存储(authentication.py中的user_tokens字典)。
from secrets import token_hex
from .models import User# Create a custom token generation function
def generate_token():return token_hex(20) # Generate a 40-character hex token# Create an in-memory token storage (for demonstration)
# In production, you would use a database model for this
user_tokens = {} # user_id: token# Custom authentication for DRF views
class CustomTokenAuthentication:def authenticate(self, request):auth_header = request.headers.get('Authorization')if not auth_header or not auth_header.startswith('Token '):return Nonetoken = auth_header.split(' ')[1]# Find user with this tokenfor user_id, user_token in user_tokens.items():if user_token == token:try:user = User.objects.get(id=user_id)return (user, token)except User.DoesNotExist:return Nonereturn Nonedef authenticate_header(self, request):return 'Token'
当使用Gunicorn并配置多个工作进程时(workers = multiprocessing.cpu_count() * 2 + 1),每个工作进程都拥有独立的内存空间和自己的user_tokens字典副本。
这就导致:
用户登录时,其令牌仅存储在某个工作进程的内存中。
而后续请求可能会被路由到另一个没有该令牌内存记录的工作进程。
这将导致认证失败,从而触发前端api.js中的拦截器清除localStorage中的令牌。
import axios from 'axios'// 创建一个axios实例
const apiClient = axios.create({baseURL: '/api',headers: {'Content-Type': 'application/json','Accept': 'application/json'},// 添加超时设置,避免请求挂起过长时间timeout: 10000
})// 请求拦截器 - 添加认证token
apiClient.interceptors.request.use(config => {// 每次请求时重新从localStorage获取token,确保使用最新tokenconst token = localStorage.getItem('upload_token')if (token) {config.headers.Authorization = `Token ${token}`}return config},error => {return Promise.reject(error)}
)// 响应拦截器 - 全局错误处理
apiClient.interceptors.response.use(response => {return response},error => {console.log('API请求错误:', error.response)// 处理401未授权错误if (error.response && error.response.status === 401) {// 可以在这里处理自动登出逻辑console.log('认证失败,清除令牌')localStorage.removeItem('upload_token')localStorage.removeItem('upload_user')window.location.href = '/login'}return Promise.reject(error)}
)// 简化的API请求方法
const optimizedApiClient = {get: (url, config = {}) => apiClient.get(url, config),post: (url, data, config = {}) => apiClient.post(url, data, config),put: (url, data, config = {}) => apiClient.put(url, data, config),delete: (url, config = {}) => apiClient.delete(url, config)
}export default optimizedApiClient
导致用户登录-》进入其他页面-》重导到登录页反复横跳
处理:
临时处理:
将gunicorn改成单线程
长期处理:
建立token存储表单,采用持久化令牌存储方案:将令牌存入数据库表中,而非内存字典
确保所有工作进程都能访问同一份令牌数据