auth_middleware.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import jwt
  2. import time
  3. from functools import wraps
  4. from flask import request, jsonify, redirect, url_for, session
  5. from http import HTTPStatus
  6. from config import config
  7. def is_api_request() -> bool:
  8. """判断是否为API请求"""
  9. return (request.path.startswith('/api/') or
  10. request.headers.get('X-Requested-With') == 'XMLHttpRequest' or
  11. 'application/json' in (request.headers.get('Accept') or ''))
  12. class AuthMiddleware:
  13. """认证中间件"""
  14. def __init__(self, app=None):
  15. self.app = app
  16. if app is not None:
  17. self.init_app(app)
  18. def init_app(self, app):
  19. """初始化应用"""
  20. self.app = app
  21. app.config['JWT_SECRET_KEY'] = config.secret_key
  22. app.config['JWT_ACCESS_TOKEN_EXPIRES'] = 7 * 24 * 60 * 60 # 7天
  23. def generate_token(self, user_id: str, user_info: dict) -> str:
  24. """生成JWT令牌"""
  25. payload = {
  26. 'user_id': user_id,
  27. 'user_info': user_info,
  28. 'exp': time.time() + self.app.config['JWT_ACCESS_TOKEN_EXPIRES'],
  29. 'iat': time.time()
  30. }
  31. token = jwt.encode(payload, self.app.config['JWT_SECRET_KEY'], algorithm='HS256')
  32. return token.decode('utf-8') if isinstance(token, bytes) else token
  33. def verify_token(self, token: str) -> dict:
  34. """验证JWT令牌"""
  35. import logging
  36. logger = logging.getLogger(__name__)
  37. try:
  38. payload = jwt.decode(token, self.app.config['JWT_SECRET_KEY'], algorithms=['HS256'])
  39. return payload
  40. except jwt.ExpiredSignatureError:
  41. logger.warning('JWT token已过期')
  42. raise ValueError('令牌已过期')
  43. except jwt.InvalidTokenError as e:
  44. logger.warning(f'JWT token无效: {str(e)}')
  45. raise ValueError('无效的令牌')
  46. def login_required(self, f):
  47. """登录要求装饰器"""
  48. @wraps(f)
  49. def decorated_function(*args, **kwargs):
  50. token = self._get_token()
  51. if not token:
  52. return self._handle_no_token()
  53. try:
  54. payload = self.verify_token(token)
  55. request.user_id = payload['user_id']
  56. request.user_info = payload['user_info']
  57. return f(*args, **kwargs)
  58. except ValueError as e:
  59. return self._handle_invalid_token(e)
  60. return decorated_function
  61. def _get_token(self) -> str:
  62. """获取token"""
  63. # 从请求头获取
  64. if 'Authorization' in request.headers:
  65. auth_header = request.headers['Authorization']
  66. if auth_header.startswith('Bearer '):
  67. return auth_header.split(' ')[1]
  68. # 从session获取
  69. return session.get('auth_token')
  70. def _handle_no_token(self):
  71. """处理无token情况"""
  72. if is_api_request():
  73. return jsonify({
  74. 'success': False,
  75. 'error': '需要登录',
  76. 'code': 'AUTH_REQUIRED'
  77. }), HTTPStatus.UNAUTHORIZED
  78. return redirect('/login')
  79. def _handle_invalid_token(self, error):
  80. """处理无效token情况"""
  81. if is_api_request():
  82. error_str = str(error)
  83. error_code = 'INVALID_TOKEN'
  84. if '令牌已过期' in error_str or 'expired' in error_str.lower():
  85. error_code = 'TOKEN_EXPIRED'
  86. elif '无效的令牌' in error_str or 'invalid' in error_str.lower():
  87. error_code = 'TOKEN_INVALID'
  88. return jsonify({
  89. 'success': False,
  90. 'error': str(error),
  91. 'code': error_code,
  92. 'can_refresh': 'TOKEN_EXPIRED' in error_code
  93. }), HTTPStatus.UNAUTHORIZED
  94. # 根据错误类型重定向到不同页面
  95. error_str = str(error)
  96. if '令牌已过期' in error_str or 'expired' in error_str.lower():
  97. return redirect('/login_error?error=token_expired&message=登录已过期,请重新登录&code=TOKEN_EXPIRED')
  98. elif '无效的令牌' in error_str or 'invalid' in error_str.lower():
  99. return redirect('/login_error?error=token_invalid&message=登录凭证无效,请重新登录&code=TOKEN_INVALID')
  100. else:
  101. return redirect('/login')
  102. # 创建全局实例
  103. auth_middleware = AuthMiddleware()
  104. def init_auth(app):
  105. """初始化认证中间件"""
  106. auth_middleware.init_app(app)
  107. return auth_middleware