from datetime import datetime, timedelta from typing import Optional from jose import JWTError, jwt from passlib.context import CryptContext from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from sqlalchemy.orm import Session from app.core.config import settings from app.core.database import get_db from app.models.auth import User, UserStatus # 密码加密上下文 pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") # OAuth2 认证方案 oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/auth/login") class SecurityUtils: """安全工具类""" @staticmethod def hash_password(password: str) -> str: """密码哈希加密""" return pwd_context.hash(password) @staticmethod def verify_password(plain_password: str, hashed_password: str) -> bool: """验证密码""" return pwd_context.verify(plain_password, hashed_password) @staticmethod def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: """创建访问令牌""" to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({"exp": expire, "type": "access"}) encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) return encoded_jwt @staticmethod def create_refresh_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: """创建刷新令牌""" to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta(days=7) # 刷新令牌7天过期 to_encode.update({"exp": expire, "type": "refresh"}) encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) return encoded_jwt @staticmethod def verify_token(token: str, credentials_exception=None) -> Optional[dict]: """验证令牌""" try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) username: str = payload.get("sub") token_type: str = payload.get("type") if username is None: raise credentials_exception or HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="无效的认证凭据", headers={"WWW-Authenticate": "Bearer"}, ) return payload except JWTError: raise credentials_exception or HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="无效的认证凭据", headers={"WWW-Authenticate": "Bearer"}, ) # 权限校验依赖 def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)) -> User: """获取当前登录用户""" credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="无法验证凭据", headers={"WWW-Authenticate": "Bearer"}, ) payload = SecurityUtils.verify_token(token, credentials_exception) username: str = payload.get("sub") user = db.query(User).filter(User.username == username).first() if user is None: raise credentials_exception # 检查用户状态 if user.status != UserStatus.ACTIVE: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="用户已被禁用或锁定" ) return user def get_current_active_user(current_user: User = Depends(get_current_user)) -> User: """获取当前活跃用户(已废弃,保留兼容)""" return current_user class PermissionChecker: """权限检查器""" def __init__(self, required_permission: str): self.required_permission = required_permission def __call__(self, current_user: User = Depends(get_current_user)) -> User: """检查用户是否拥有指定权限""" # 超级管理员拥有所有权限 if current_user.role == "super_admin": return current_user # 基于角色的权限检查 role_permissions = { "admin": { "network", "ip", "scan", "snmp", "alert", "report" }, "operator": { "network:view", "ip:view", "ip:edit", "scan:run", "alert:view" }, "viewer": { "network:view", "ip:view", "alert:view" } } user_permissions = role_permissions.get(current_user.role, set()) # 检查是否有通配符权限(如 "network" 包含 "network:create") permission_category = self.required_permission.split(":")[0] if ( self.required_permission not in user_permissions and permission_category not in user_permissions and "*" not in user_permissions ): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=f"缺少权限: {self.required_permission}" ) return current_user # 快捷权限检查依赖 def require_permission(permission: str): """权限检查依赖函数""" return PermissionChecker(permission)