第23章 MCP系统的安全性
前言
在第22章解决了性能问题后,我们现在面对的是同等重要甚至更加重要的安全问题。MCP系统需要处理敏感数据,进行关键业务操作,必须建立多层次的安全防护。本章将从身份认证、授权、加密、审计等多个维度,构建企业级的MCP安全架构。
23.1 身份认证与授权
23.1.1 认证体系架构
graph TB
A["客户端请求"] --> B["身份认证"]
B --> B1["API密钥"]
B --> B2["OAuth 2.0"]
B --> B3["JWT Token"]
B --> B4["mTLS证书"]
B1 --> C["授权检查"]
B2 --> C
B3 --> C
B4 --> C
C --> C1["角色验证"]
C --> C2["权限检查"]
C --> C3["资源访问"]
C1 --> D["执行操作"]
C2 --> D
C3 --> D
D --> E["审计日志"]
23.1.2 多层身份认证
from typing import Dict, Optional, Any, Tuple, List
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
import hashlib
import secrets
import jwt
import hmac
import base64
class AuthenticationMethod(Enum):
"""认证方法"""
API_KEY = "api_key"
OAUTH2 = "oauth2"
JWT = "jwt"
MTLS = "mtls"
LDAP = "ldap"
@dataclass
class APIKey:
"""API密钥"""
key_id: str
key_secret: str
created_at: datetime
expires_at: Optional[datetime]
last_used_at: Optional[datetime] = None
is_active: bool = True
permissions: List[str] = field(default_factory=list)
class APIKeyManager:
"""API密钥管理"""
def __init__(self, secret_key: str):
"""
初始化密钥管理器
Args:
secret_key: 加密密钥
"""
self.secret_key = secret_key
self.keys: Dict[str, APIKey] = {}
self.key_index: Dict[str, str] = {} # key_id -> 加密后的key
def generate_api_key(self, user_id: str,
expires_in_days: int = 365,
permissions: Optional[List[str]] = None) -> Tuple[str, str]:
"""
生成API密钥
Args:
user_id: 用户ID
expires_in_days: 过期天数
permissions: 权限列表
Returns:
(key_id, key_secret)
"""
key_id = f"{user_id}_{secrets.token_hex(8)}"
key_secret = secrets.token_urlsafe(32)
# 存储加密后的密钥
hashed_secret = hashlib.sha256(key_secret.encode()).hexdigest()
self.key_index[key_id] = hashed_secret
# 创建密钥对象
self.keys[key_id] = APIKey(
key_id=key_id,
key_secret=key_secret,
created_at=datetime.now(),
expires_at=datetime.now() + timedelta(days=expires_in_days),
permissions=permissions or ["read", "write"]
)
return key_id, key_secret
def validate_api_key(self, key_id: str, key_secret: str) -> Dict[str, Any]:
"""
验证API密钥
Args:
key_id: 密钥ID
key_secret: 密钥
Returns:
验证结果
"""
if key_id not in self.keys:
return {
"valid": False,
"error": "Key not found",
"error_code": "INVALID_KEY_ID"
}
api_key = self.keys[key_id]
# 检查是否激活
if not api_key.is_active:
return {
"valid": False,
"error": "Key is inactive",
"error_code": "KEY_INACTIVE"
}
# 检查是否过期
if api_key.expires_at and datetime.now() > api_key.expires_at:
return {
"valid": False,
"error": "Key has expired",
"error_code": "KEY_EXPIRED"
}
# 验证密钥(恒定时间比较防止时序攻击)
hashed_input = hashlib.sha256(key_secret.encode()).hexdigest()
if not hmac.compare_digest(hashed_input, self.key_index[key_id]):
return {
"valid": False,
"error": "Invalid secret",
"error_code": "INVALID_SECRET"
}
# 更新最后使用时间
api_key.last_used_at = datetime.now()
return {
"valid": True,
"key_id": key_id,
"permissions": api_key.permissions,
"created_at": api_key.created_at.isoformat(),
"expires_at": api_key.expires_at.isoformat() if api_key.expires_at else None
}
class JWTTokenManager:
"""JWT令牌管理"""
def __init__(self, secret_key: str, algorithm: str = "HS256"):
"""
初始化JWT管理器
Args:
secret_key: 签名密钥
algorithm: 签名算法
"""
self.secret_key = secret_key
self.algorithm = algorithm
self.blacklist = set() # 撤销的令牌
def create_token(self, user_id: str, permissions: List[str],
expires_in_hours: int = 24) -> str:
"""
创建JWT令牌
Args:
user_id: 用户ID
permissions: 权限列表
expires_in_hours: 过期时间(小时)
Returns:
JWT令牌
"""
payload = {
"user_id": user_id,
"permissions": permissions,
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(hours=expires_in_hours),
"jti": secrets.token_urlsafe(16) # JWT ID用于撤销
}
token = jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
return token
def verify_token(self, token: str) -> Dict[str, Any]:
"""
验证JWT令牌
Args:
token: JWT令牌
Returns:
验证结果
"""
try:
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
# 检查是否在黑名单中
if payload.get("jti") in self.blacklist:
return {
"valid": False,
"error": "Token has been revoked",
"error_code": "TOKEN_REVOKED"
}
return {
"valid": True,
"user_id": payload.get("user_id"),
"permissions": payload.get("permissions", []),
"expires_at": payload.get("exp")
}
except jwt.ExpiredSignatureError:
return {
"valid": False,
"error": "Token has expired",
"error_code": "TOKEN_EXPIRED"
}
except jwt.InvalidTokenError as e:
return {
"valid": False,
"error": f"Invalid token: {str(e)}",
"error_code": "INVALID_TOKEN"
}
def revoke_token(self, token: str) -> bool:
"""撤销令牌"""
try:
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
self.blacklist.add(payload.get("jti"))
return True
except:
return False
class RoleBasedAccessControl:
"""基于角色的访问控制(RBAC)"""
def __init__(self):
self.roles: Dict[str, Dict[str, Any]] = {}
self.user_roles: Dict[str, List[str]] = {}
self._init_default_roles()
def _init_default_roles(self):
"""初始化默认角色"""
self.roles = {
"admin": {
"permissions": ["*"],
"description": "系统管理员",
"level": 5
},
"manager": {
"permissions": ["read", "write", "delete", "export"],
"description": "管理员",
"level": 4
},
"operator": {
"permissions": ["read", "write"],
"description": "操作员",
"level": 2
},
"viewer": {
"permissions": ["read"],
"description": "查看者",
"level": 1
},
"guest": {
"permissions": [],
"description": "访客",
"level": 0
}
}
def assign_role(self, user_id: str, role: str) -> bool:
"""分配角色"""
if role not in self.roles:
return False
if user_id not in self.user_roles:
self.user_roles[user_id] = []
if role not in self.user_roles[user_id]:
self.user_roles[user_id].append(role)
return True
def check_permission(self, user_id: str, required_permission: str) -> bool:
"""检查权限"""
user_roles = self.user_roles.get(user_id, [])
for role in user_roles:
if role not in self.roles:
continue
permissions = self.roles[role].get("permissions", [])
# 检查通配符权限
if "*" in permissions:
return True
# 检查具体权限
if required_permission in permissions:
return True
return False
def get_user_permissions(self, user_id: str) -> set:
"""获取用户所有权限"""
user_roles = self.user_roles.get(user_id, [])
all_permissions = set()
for role in user_roles:
if role in self.roles:
permissions = self.roles[role].get("permissions", [])
if "*" in permissions:
return {"*"} # 包含所有权限
all_permissions.update(permissions)
return all_permissions
23.2 访问控制与数据保护
23.2.1 细粒度访问控制
class AccessControlList:
"""访问控制列表(ACL)"""
def __init__(self):
self.resource_acls: Dict[str, Dict[str, List[str]]] = {}
def grant_access(self, resource_id: str, user_id: str,
permissions: List[str]) -> bool:
"""
授予访问权限
Args:
resource_id: 资源ID
user_id: 用户ID
permissions: 权限列表
Returns:
是否成功
"""
if resource_id not in self.resource_acls:
self.resource_acls[resource_id] = {}
self.resource_acls[resource_id][user_id] = permissions
return True
def revoke_access(self, resource_id: str, user_id: str) -> bool:
"""撤销访问权限"""
if resource_id in self.resource_acls and user_id in self.resource_acls[resource_id]:
del self.resource_acls[resource_id][user_id]
return True
return False
def check_access(self, resource_id: str, user_id: str,
required_permission: str) -> bool:
"""检查访问权限"""
if resource_id not in self.resource_acls:
return False
user_permissions = self.resource_acls[resource_id].get(user_id, [])
return required_permission in user_permissions or "*" in user_permissions
class EncryptionManager:
"""加密管理"""
@staticmethod
def encrypt_sensitive_data(data: str, encryption_key: str) -> str:
"""
加密敏感数据
Args:
data: 明文数据
encryption_key: 加密密钥
Returns:
加密数据
"""
from cryptography.fernet import Fernet
# 生成基于密钥的加密器
cipher_key = hashlib.sha256(encryption_key.encode()).digest()
cipher_key = Fernet(base64.urlsafe_b64encode(cipher_key[:32]))
encrypted = cipher_key.encrypt(data.encode())
return base64.b64encode(encrypted).decode()
@staticmethod
def decrypt_sensitive_data(encrypted_data: str, encryption_key: str) -> str:
"""解密敏感数据"""
from cryptography.fernet import Fernet
cipher_key = hashlib.sha256(encryption_key.encode()).digest()
cipher_key = Fernet(base64.urlsafe_b64encode(cipher_key[:32]))
encrypted = base64.b64decode(encrypted_data.encode())
decrypted = cipher_key.decrypt(encrypted)
return decrypted.decode()
@staticmethod
def hash_password(password: str, salt: Optional[str] = None) -> Tuple[str, str]:
"""
哈希密码(使用bcrypt)
Args:
password: 密码
salt: 盐值
Returns:
(哈希值, 盐值)
"""
import bcrypt
if salt is None:
salt = bcrypt.gensalt()
else:
salt = salt.encode()
hashed = bcrypt.hashpw(password.encode(), salt)
return hashed.decode(), salt.decode()
@staticmethod
def verify_password(password: str, hashed: str) -> bool:
"""验证密码"""
import bcrypt
return bcrypt.checkpw(password.encode(), hashed.encode())
23.3 审计与监控
23.3.1 审计日志系统
@dataclass
class AuditLog:
"""审计日志"""
log_id: str
timestamp: datetime
user_id: str
action: str
resource_id: Optional[str]
status: str # success, failure
details: Dict[str, Any]
ip_address: Optional[str]
user_agent: Optional[str]
class AuditLogger:
"""审计日志记录器"""
def __init__(self):
self.logs: List[AuditLog] = []
def log_action(self, user_id: str, action: str,
resource_id: Optional[str] = None,
status: str = "success",
details: Optional[Dict] = None,
ip_address: Optional[str] = None,
user_agent: Optional[str] = None) -> str:
"""
记录操作
Args:
user_id: 用户ID
action: 操作
resource_id: 资源ID
status: 状态
details: 详细信息
ip_address: IP地址
user_agent: 用户代理
Returns:
日志ID
"""
log_id = f"LOG_{secrets.token_hex(8)}"
log = AuditLog(
log_id=log_id,
timestamp=datetime.now(),
user_id=user_id,
action=action,
resource_id=resource_id,
status=status,
details=details or {},
ip_address=ip_address,
user_agent=user_agent
)
self.logs.append(log)
return log_id
def query_logs(self, user_id: Optional[str] = None,
action: Optional[str] = None,
days_back: int = 30) -> List[AuditLog]:
"""
查询审计日志
Args:
user_id: 用户ID过滤
action: 操作过滤
days_back: 回溯天数
Returns:
日志列表
"""
cutoff_time = datetime.now() - timedelta(days=days_back)
results = [
log for log in self.logs
if log.timestamp > cutoff_time
and (user_id is None or log.user_id == user_id)
and (action is None or log.action == action)
]
return results
def generate_audit_report(self, days: int = 30) -> Dict[str, Any]:
"""生成审计报告"""
cutoff = datetime.now() - timedelta(days=days)
recent_logs = [log for log in self.logs if log.timestamp > cutoff]
# 统计
successful = len([log for log in recent_logs if log.status == "success"])
failed = len([log for log in recent_logs if log.status == "failure"])
# 按用户分组
user_actions = {}
for log in recent_logs:
if log.user_id not in user_actions:
user_actions[log.user_id] = 0
user_actions[log.user_id] += 1
# 按操作分组
action_counts = {}
for log in recent_logs:
if log.action not in action_counts:
action_counts[log.action] = 0
action_counts[log.action] += 1
return {
"period_days": days,
"total_logs": len(recent_logs),
"successful_operations": successful,
"failed_operations": failed,
"success_rate": successful / len(recent_logs) if recent_logs else 0,
"top_users": sorted(user_actions.items(), key=lambda x: x[1], reverse=True)[:5],
"operation_summary": action_counts
}
23.4 威胁检测与防护
23.4.1 安全威胁防护
class ThreatDetection:
"""威胁检测"""
def __init__(self):
self.failed_attempts: Dict[str, List[datetime]] = {}
self.suspicious_ips: set = set()
self.rate_limit_buckets: Dict[str, List[datetime]] = {}
def detect_brute_force(self, user_id: str,
max_attempts: int = 5,
time_window_minutes: int = 15) -> Dict[str, Any]:
"""
检测暴力破解
Args:
user_id: 用户ID
max_attempts: 最大尝试次数
time_window_minutes: 时间窗口(分钟)
Returns:
检测结果
"""
now = datetime.now()
cutoff = now - timedelta(minutes=time_window_minutes)
# 清理过期尝试
if user_id in self.failed_attempts:
self.failed_attempts[user_id] = [
t for t in self.failed_attempts[user_id]
if t > cutoff
]
else:
self.failed_attempts[user_id] = []
# 记录失败尝试
self.failed_attempts[user_id].append(now)
# 检查是否超过阈值
attempts = len(self.failed_attempts[user_id])
return {
"is_brute_force": attempts >= max_attempts,
"attempts": attempts,
"max_attempts": max_attempts,
"time_window_minutes": time_window_minutes,
"blocked": attempts >= max_attempts,
"reset_at": (cutoff + timedelta(minutes=time_window_minutes)).isoformat()
}
def detect_anomaly(self, user_id: str, ip_address: str,
action: str) -> Dict[str, Any]:
"""
检测异常行为
Args:
user_id: 用户ID
ip_address: IP地址
action: 操作
Returns:
异常检测结果
"""
anomalies = []
risk_score = 0
# 检查IP是否可疑
if ip_address in self.suspicious_ips:
anomalies.append("IP地址在可疑名单中")
risk_score += 30
# 检查是否是高风险操作
high_risk_actions = ["delete", "export", "modify_permissions"]
if action in high_risk_actions:
anomalies.append(f"高风险操作: {action}")
risk_score += 20
# 检查时间异常(假设夜间访问为异常)
hour = datetime.now().hour
if hour < 6 or hour > 22:
anomalies.append("非工作时间访问")
risk_score += 15
return {
"user_id": user_id,
"ip_address": ip_address,
"action": action,
"anomalies": anomalies,
"risk_score": min(risk_score, 100),
"should_alert": risk_score >= 50,
"should_block": risk_score >= 80
}
class SecurityPolicies:
"""安全策略"""
@staticmethod
def get_default_policies() -> Dict[str, Any]:
"""获取默认安全策略"""
return {
"password_policy": {
"min_length": 12,
"require_uppercase": True,
"require_lowercase": True,
"require_numbers": True,
"require_special_chars": True,
"expiry_days": 90,
"history_count": 5
},
"session_policy": {
"max_session_duration_hours": 24,
"idle_timeout_minutes": 30,
"concurrent_sessions": 3,
"require_mfa": True
},
"api_key_policy": {
"max_age_days": 365,
"rotation_required": True,
"require_scope": True,
"ip_whitelist_required": False
},
"encryption_policy": {
"algorithm": "AES-256",
"tls_version": "1.3",
"key_rotation_days": 90,
"encrypt_at_rest": True,
"encrypt_in_transit": True
},
"audit_policy": {
"log_all_operations": True,
"log_retention_days": 365,
"alert_on_failures": True,
"alert_on_privilege_changes": True
}
}
本章总结
| 关键点 | 说明 |
|---|---|
| 身份认证 | API密钥、OAuth2、JWT、mTLS多层认证 |
| 授权机制 | RBAC角色管理、ACL细粒度控制 |
| 数据保护 | AES-256加密、密码哈希、敏感字段加密 |
| 审计日志 | 完整的操作追踪、审计报告 |
| 威胁检测 | 暴力破解、异常行为、风险评分 |
| 安全策略 | 密码、会话、密钥、加密、审计策略 |
常见问题
Q1: API密钥和JWT令牌应该如何选择? A: API密钥用于服务间通信和长期认证,JWT用于会话和短期认证。
Q2: 如何防止中间人攻击? A: 使用TLS 1.3、证书固定、HPKP头、mTLS相互认证。
Q3: 密码应该如何存储? A: 使用bcrypt/scrypt/argon2而不是MD5/SHA,永远不要明文存储。
Q4: 如何处理密钥泄露? A: 立即轮换密钥、撤销旧密钥、检查日志、通知用户。
Q5: 审计日志多久删除一次? A: 根据法规保留(通常1-7年),定期测试恢复流程。
下一章预告:第24章将讲述MCP与企业架构集成!