1e1e5957e8
- Ips.vue: el-select 添加 width: 280px,修复网段下拉框过窄 - Ips.vue: query 参数 networkId → network_id(与 FastAPI snake_case 一致) - Scan.vue: el-select 用 network.id 代替整个 network 对象作为 value,修复切换网段不生效 - enhanced_scan_service.py: 替换失效的 scapy ARP 扫描为 arp-scan 命令;扫描结果自动创建缺失 IP 记录
159 lines
5.5 KiB
Python
159 lines
5.5 KiB
Python
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)
|