| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- import jwt
- import time
- from functools import wraps
- from flask import request, jsonify, redirect, url_for, session
- from http import HTTPStatus
- from config import config
- def is_api_request() -> bool:
- """判断是否为API请求"""
- return (request.path.startswith('/api/') or
- request.headers.get('X-Requested-With') == 'XMLHttpRequest' or
- 'application/json' in (request.headers.get('Accept') or ''))
- class AuthMiddleware:
- """认证中间件"""
-
- def __init__(self, app=None):
- self.app = app
- if app is not None:
- self.init_app(app)
-
- def init_app(self, app):
- """初始化应用"""
- self.app = app
- app.config['JWT_SECRET_KEY'] = config.secret_key
- app.config['JWT_ACCESS_TOKEN_EXPIRES'] = 7 * 24 * 60 * 60 # 7天
-
- def generate_token(self, user_id: str, user_info: dict) -> str:
- """生成JWT令牌"""
- payload = {
- 'user_id': user_id,
- 'user_info': user_info,
- 'exp': time.time() + self.app.config['JWT_ACCESS_TOKEN_EXPIRES'],
- 'iat': time.time()
- }
- token = jwt.encode(payload, self.app.config['JWT_SECRET_KEY'], algorithm='HS256')
- return token.decode('utf-8') if isinstance(token, bytes) else token
-
- def verify_token(self, token: str) -> dict:
- """验证JWT令牌"""
- import logging
- logger = logging.getLogger(__name__)
-
- try:
- payload = jwt.decode(token, self.app.config['JWT_SECRET_KEY'], algorithms=['HS256'])
- return payload
- except jwt.ExpiredSignatureError:
- logger.warning('JWT token已过期')
- raise ValueError('令牌已过期')
- except jwt.InvalidTokenError as e:
- logger.warning(f'JWT token无效: {str(e)}')
- raise ValueError('无效的令牌')
-
- def login_required(self, f):
- """登录要求装饰器"""
- @wraps(f)
- def decorated_function(*args, **kwargs):
- token = self._get_token()
-
- if not token:
- return self._handle_no_token()
-
- try:
- payload = self.verify_token(token)
- request.user_id = payload['user_id']
- request.user_info = payload['user_info']
- return f(*args, **kwargs)
- except ValueError as e:
- return self._handle_invalid_token(e)
-
- return decorated_function
-
- def _get_token(self) -> str:
- """获取token"""
- # 从请求头获取
- if 'Authorization' in request.headers:
- auth_header = request.headers['Authorization']
- if auth_header.startswith('Bearer '):
- return auth_header.split(' ')[1]
-
- # 从session获取
- return session.get('auth_token')
-
- def _handle_no_token(self):
- """处理无token情况"""
- if is_api_request():
- return jsonify({
- 'success': False,
- 'error': '需要登录',
- 'code': 'AUTH_REQUIRED'
- }), HTTPStatus.UNAUTHORIZED
- return redirect('/login')
-
- def _handle_invalid_token(self, error):
- """处理无效token情况"""
- if is_api_request():
- error_str = str(error)
- error_code = 'INVALID_TOKEN'
-
- if '令牌已过期' in error_str or 'expired' in error_str.lower():
- error_code = 'TOKEN_EXPIRED'
- elif '无效的令牌' in error_str or 'invalid' in error_str.lower():
- error_code = 'TOKEN_INVALID'
-
- return jsonify({
- 'success': False,
- 'error': str(error),
- 'code': error_code,
- 'can_refresh': 'TOKEN_EXPIRED' in error_code
- }), HTTPStatus.UNAUTHORIZED
- # 根据错误类型重定向到不同页面
- error_str = str(error)
- if '令牌已过期' in error_str or 'expired' in error_str.lower():
- return redirect('/login_error?error=token_expired&message=登录已过期,请重新登录&code=TOKEN_EXPIRED')
- elif '无效的令牌' in error_str or 'invalid' in error_str.lower():
- return redirect('/login_error?error=token_invalid&message=登录凭证无效,请重新登录&code=TOKEN_INVALID')
- else:
- return redirect('/login')
- # 创建全局实例
- auth_middleware = AuthMiddleware()
- def init_auth(app):
- """初始化认证中间件"""
- auth_middleware.init_app(app)
- return auth_middleware
|