Files
ipam/backend/app/core/security.py
T
Your Name 1e1e5957e8 fix(ipam): 修复IP管理页面网段筛选不生效及下拉框过窄
- 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 记录
2026-07-18 09:45:30 +08:00

159 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)