# 安全解决方案(P0问题修复) > 版本:v1.0 > 日期:2026-03-18 > 目的:系统性解决评审发现的安全P0问题 --- ## 1. 计费数据防篡改机制 ### 1.1 当前问题 - 只有 usage_records 表,缺乏完整性校验 - 无防篡改审计日志 - 无法追溯数据变更 ### 1.2 解决方案 #### 1.2.1 双重记账设计 ```python # 双重记账:借方和贷方必须平衡 class DoubleEntryBilling: def record_billing(self, transaction: Transaction): # 1. 借方:用户账户余额 self.debit( account_type='user_balance', account_id=transaction.user_id, amount=transaction.amount, currency=transaction.currency ) # 2. 贷方:收入账户 self.credit( account_type='revenue', account_id='platform_revenue', amount=transaction.amount, currency=transaction.currency ) # 3. 验证平衡 assert self.get_balance('user', transaction.user_id) + \ self.get_balance('revenue', 'platform_revenue') == 0 ``` #### 1.2.2 审计日志表 ```sql -- PostgreSQL 版本:计费审计日志表 CREATE TABLE IF NOT EXISTS billing_audit_log ( id BIGSERIAL PRIMARY KEY, record_id BIGINT NOT NULL, table_name VARCHAR(50) NOT NULL, operation VARCHAR(20) NOT NULL, old_value JSONB, new_value JSONB, operator_id BIGINT NOT NULL, operator_ip INET, operator_role VARCHAR(50), request_id VARCHAR(64), record_hash CHAR(64) NOT NULL, previous_hash CHAR(64), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_billing_audit_log_record_id ON billing_audit_log (record_id); CREATE INDEX IF NOT EXISTS idx_billing_audit_log_operator_id ON billing_audit_log (operator_id); CREATE INDEX IF NOT EXISTS idx_billing_audit_log_created_at ON billing_audit_log (created_at DESC); CREATE INDEX IF NOT EXISTS idx_billing_audit_log_request_id ON billing_audit_log (request_id); -- PostgreSQL 触发器:自动记录变更(示例) CREATE OR REPLACE FUNCTION fn_audit_supply_usage_update() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE v_prev_hash CHAR(64); BEGIN SELECT record_hash INTO v_prev_hash FROM billing_audit_log WHERE record_id = OLD.id ORDER BY id DESC LIMIT 1; INSERT INTO billing_audit_log ( record_id, table_name, operation, old_value, new_value, operator_id, operator_ip, operator_role, request_id, record_hash, previous_hash ) VALUES ( OLD.id, 'supply_usage_records', 'UPDATE', to_jsonb(OLD), to_jsonb(NEW), COALESCE(NULLIF(current_setting('app.operator_id', true), ''), '0')::BIGINT, NULLIF(current_setting('app.operator_ip', true), '')::INET, NULLIF(current_setting('app.operator_role', true), ''), NULLIF(current_setting('app.request_id', true), ''), encode(digest(to_jsonb(NEW)::text, 'sha256'), 'hex'), v_prev_hash ); RETURN NEW; END; $$; DROP TRIGGER IF EXISTS trg_usage_before_update ON supply_usage_records; CREATE TRIGGER trg_usage_before_update BEFORE UPDATE ON supply_usage_records FOR EACH ROW EXECUTE FUNCTION fn_audit_supply_usage_update(); ``` #### 1.2.3 实时对账机制 ```python class BillingReconciliation: def hourly_reconciliation(self): """小时级对账""" # 1. 获取计费记录 billing_records = self.get_billing_records( start_time=self.hour_ago, end_time=datetime.now() ) # 2. 获取用户消费记录 usage_records = self.get_usage_records( start_time=self.hour_ago, end_time=datetime.now() ) # 3. 比对 discrepancies = [] for billing, usage in zip(billing_records, usage_records): if not self.is_match(billing, usage): discrepancies.append({ 'billing_id': billing.id, 'usage_id': usage.id, 'difference': billing.amount - usage.amount }) # 4. 告警 if discrepancies: self.send_alert('billing_discrepancy', discrepancies) def real_time_verification(self): """实时验证(请求级别)""" # 每个请求完成后立即验证 request = self.get_current_request() expected_cost = self.calculate_cost(request.usage) actual_cost = self.get_billing_record(request.id).amount # 允许0.1%误差 if abs(expected_cost - actual_cost) > expected_cost * 0.001: raise BillingAccuracyError(f"计费差异: {expected_cost} vs {actual_cost}") ``` --- ## 2. 跨租户隔离强化 ### 2.1 当前问题 - team_id 和 organization_id 字段存在 - 但缺乏强制验证和行级安全 ### 2.2 解决方案 #### 2.2.1 强制租户上下文验证 ```python class TenantContextMiddleware: def process_request(self, request): # 1. 从Token提取租户ID tenant_id = self.extract_tenant_id(request.token) # 2. 从URL/Header强制验证 if request.tenant_id and request.tenant_id != tenant_id: raise TenantMismatchError() # 3. 强制设置租户上下文 request.tenant_id = tenant_id # 4. 存储到请求上下文 self.set_context('tenant_id', tenant_id) ``` #### 2.2.2 数据库行级安全(RLS) ```sql -- 启用行级安全 ALTER TABLE api_keys ENABLE ROW LEVEL SECURITY; -- 创建策略:用户只能访问自己的Key CREATE POLICY api_keys_tenant_isolation ON api_keys FOR ALL USING (tenant_id = current_setting('app.tenant_id')::BIGINT); -- 对所有敏感表启用RLS ALTER TABLE billing_records ENABLE ROW LEVEL SECURITY; ALTER TABLE usage_records ENABLE ROW LEVEL SECURITY; ALTER TABLE team_members ENABLE ROW LEVEL SECURITY; ``` #### 2.2.3 敏感操作二次验证 ```python class SensitiveOperationGuard: # 需要二次验证的操作 SENSITIVE_ACTIONS = [ 'billing.write', # 写账单 'admin.tenant_write', # 租户管理 'provider.withdraw', # 供应方提现 ] def verify(self, user_id, action, context): if action not in self.SENSITIVE_ACTIONS: return True # 1. 检查用户权限级别 user = self.get_user(user_id) if user.role == 'admin': return True # 2. 检查是否需要二次验证 if self.requires_mfa(action, context): # 发送验证码 self.send_verification_code(user) return False # 3. 记录审计日志 self.audit_log(user_id, action, context) return True ``` --- ## 3. 密钥轮换机制 ### 3.1 当前问题 - API Key 无失效机制 - 无法强制轮换 - 无生命周期管理 ### 3.2 解决方案 #### 3.2.1 密钥生命周期管理 ```python class APIKeyLifecycle: # 配置 KEY_EXPIRY_DAYS = 90 # 有效期90天 WARNING_DAYS = 14 # 提前14天提醒 GRACE_PERIOD_DAYS = 7 # 宽限期7天 MAX_KEYS_PER_USER = 10 # 每个用户最多10个Key def generate_key(self, user_id, description) -> APIKey: # 1. 检查Key数量限制 current_keys = self.count_user_keys(user_id) if current_keys >= self.MAX_KEYS_PER_USER: raise MaxKeysExceededError() # 2. 生成Key key = self._generate_key_string() # 3. 存储Key信息 api_key = APIKey( key_hash=self.hash(key), key_prefix=key[:12], # 显示前缀 user_id=user_id, description=description, expires_at=datetime.now() + timedelta(days=self.KEY_EXPIRY_DAYS), created_at=datetime.now(), status='active', version=1 ) # 4. 保存到数据库 self.save(api_key) return api_key def is_key_valid(self, key: APIKey) -> ValidationResult: # 1. 检查状态 if key.status == 'disabled': return ValidationResult(False, 'Key is disabled') if key.status == 'expired': return ValidationResult(False, 'Key is expired') # 2. 检查是否过期 if key.expires_at and key.expires_at < datetime.now(): # 检查是否在宽限期 if key.expires_at > datetime.now() - timedelta(days=self.GRACE_PERIOD_DAYS): # 在宽限期,提醒但不拒绝 return ValidationResult(True, 'Key expiring soon', warning=True) return ValidationResult(False, 'Key expired') # 3. 检查是否需要轮换提醒 days_until_expiry = (key.expires_at - datetime.now()).days if days_until_expiry <= self.WARNING_DAYS: # 异步通知用户 self.notify_key_expiring(key, days_until_expiry) return ValidationResult(True, 'Valid') ``` #### 3.2.2 密钥泄露应急处理 ```python class KeyCompromiseHandler: def report_compromised(self, key_id, reporter_id): """报告Key泄露""" # 1. 立即禁用Key key = self.get_key(key_id) key.status = 'compromised' key.disabled_at = datetime.now() key.disabled_by = reporter_id self.save(key) # 2. 通知用户 user = self.get_user(key.user_id) self.send_notification(user, 'key_compromised', { 'key_id': key_id, 'reported_at': datetime.now() }) # 3. 记录审计日志 self.audit_log('key_compromised', { 'key_id': key_id, 'reported_by': reporter_id, 'action': 'disabled' }) # 4. 自动创建新Key(可选) new_key = self.generate_key(key.user_id, 'Auto-generated replacement') return new_key def rotate_key(self, key_id): """主动轮换Key""" old_key = self.get_key(key_id) # 1. 创建新Key new_key = self.generate_key( old_key.user_id, f"Rotation of {old_key.description}" ) # 2. 标记旧Key为轮换 old_key.status = 'rotated' old_key.rotated_at = datetime.now() old_key.replaced_by = new_key.id self.save(old_key) return new_key ``` --- ## 4. 激活码安全强化 ### 4.1 当前问题 - 6位随机数entropy不足 - MD5校验和可碰撞 ### 4.2 解决方案 ```python import secrets import hashlib import hmac class SecureActivationCode: def generate(self, user_id: int, expiry_days: int) -> str: # 1. 使用 crypto.random 替代 random # 16字节 = 128位 entropy random_bytes = secrets.token_bytes(16) random_hex = random_bytes.hex() # 2. 使用 HMAC-SHA256 替代 MD5 expiry = datetime.now() + timedelta(days=expiry_days) expiry_str = expiry.strftime("%Y%m%d") # 3. 构建原始字符串 raw = f"lgw-act-{user_id}-{expiry_str}-{random_hex}" # 4. HMAC 签名(使用应用密钥) signature = hmac.new( self.secret_key.encode(), raw.encode(), hashlib.sha256 ).hexdigest()[:16] return f"{raw}-{signature}" def verify(self, code: str) -> VerificationResult: parts = code.split('-') if len(parts) != 6: return VerificationResult(False, 'Invalid format') # 1. 解析各部分 _, _, user_id, expiry_str, random_hex, signature = parts # 2. 验证签名 raw = f"lgw-act-{user_id}-{expiry_str}-{random_hex}" expected_signature = hmac.new( self.secret_key.encode(), raw.encode(), hashlib.sha256 ).hexdigest()[:16] if not hmac.compare_digest(signature, expected_signature): return VerificationResult(False, 'Invalid signature') # 3. 验证过期 expiry = datetime.strptime(expiry_str, "%Y%m%d") if expiry < datetime.now(): return VerificationResult(False, 'Expired') return VerificationResult(True, 'Valid', user_id=int(user_id)) ``` --- ## 4. DDoS防护机制 ### 4.1 防护层级 ```python class DDoSProtection: """DDoS防护 - 修复S-D-01""" # 三层防护 TIERS = [ {'name': 'L4', 'layer': 'tcp', 'method': 'syn_cookie'}, {'name': 'L7', 'layer': 'http', 'method': 'rate_limit'}, {'name': 'APP', 'layer': 'application', 'method': 'challenge'} ] # 限流配置 RATE_LIMITS = { 'global': {'requests': 100000, 'window': 60}, 'per_ip': {'requests': 1000, 'window': 60}, 'per_token': {'requests': 100, 'window': 60}, 'burst': {'requests': 50, 'window': 1} } # IP黑名单 def check_ip_blacklist(self, ip: str) -> bool: """检查IP是否在黑名单""" return self.redis.sismember('ddos:blacklist', ip) def add_to_blacklist(self, ip: str, reason: str, duration: int = 3600): """加入黑名单""" self.redis.sadd('ddos:blacklist', ip) self.redis.expire('ddos:blacklist', duration) # 记录原因 self.redis.hset('ddos:blacklist:reasons', ip, json.dumps({ 'reason': reason, 'added_at': datetime.now().isoformat() })) ``` ### 4.2 攻击检测 ```python class AttackDetector: """攻击检测""" # 检测规则 RULES = { 'syn_flood': {'threshold': 1000, 'window': 10, 'action': 'block'}, 'http_flood': {'threshold': 500, 'window': 60, 'action': 'rate_limit'}, 'slowloris': {'threshold': 50, 'window': 60, 'action': 'block'}, 'credential_stuffing': {'threshold': 100, 'window': 60, 'action': 'challenge'} } async def detect(self, metrics: AttackMetrics) -> DetectionResult: """检测攻击""" for rule_name, rule in self.RULES.items(): if metrics.exceeds_threshold(rule): return DetectionResult( attack=True, rule=rule_name, action=rule['action'], severity='HIGH' if rule['action'] == 'block' else 'MEDIUM' ) return DetectionResult(attack=False) ``` --- ## 5. 日志脱敏规则 ### 5.1 脱敏字段定义 ```python class LogDesensitization: """日志脱敏 - 修复S-D-02""" # 脱敏规则 RULES = { 'api_key': { 'pattern': r'(sk-[a-zA-Z0-9]{20,})', 'replacement': r'sk-***', 'level': 'SENSITIVE' }, 'password': { 'pattern': r'(password["\']?\s*[:=]\s*["\']?)([^"\']+)', 'replacement': r'\1***', 'level': 'SENSITIVE' }, 'email': { 'pattern': r'([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})', 'replacement': r'\1***@\2', 'level': 'PII' }, 'phone': { 'pattern': r'(1[3-9]\d)(\d{4})(\d{4})', 'replacement': r'\1****\3', 'level': 'PII' }, 'ip_address': { 'pattern': r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', 'replacement': r'\1 (masked)', 'level': 'NETWORK' }, 'credit_card': { 'pattern': r'(\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4})', 'replacement': r'****-****-****-\4', 'level': 'SENSITIVE' } } def desensitize(self, log: dict) -> dict: """脱敏处理""" import re result = {} for key, value in log.items(): if isinstance(value, str): result[key] = self._desensitize_value(value) else: result[key] = value return result ``` ### 5.2 日志级别 ```python class LogLevel: """日志级别""" LEVELS = { 'DEBUG': {'mask': False, 'retention_days': 7}, 'INFO': {'mask': False, 'retention_days': 30}, 'WARNING': {'mask': False, 'retention_days': 90}, 'ERROR': {'mask': False, 'retention_days': 365}, 'SENSITIVE': {'mask': True, 'retention_days': 365} # 敏感日志必须脱敏 } def should_mask(self, level: str) -> bool: """是否需要脱敏""" return self.LEVELS.get(level, {}).get('mask', False) ``` --- ## 6. 密钥定期轮换 ### 6.1 定期轮换策略 ```python class KeyRotationScheduler: """密钥定期轮换 - 修复S-D-03""" # 轮换配置 ROTATION_CONFIG = { 'api_key': {'days': 90, 'warning_days': 14}, 'internal_key': {'days': 30, 'warning_days': 7}, 'provider_key': {'days': 60, 'warning_days': 10} } async def schedule_rotation(self): """调度轮换""" while True: # 1. 查找需要轮换的Key keys_due = await self.find_keys_due_for_rotation() # 2. 发送提醒 for key in keys_due: await self.send_rotation_warning(key) # 3. 自动轮换(超过宽限期) keys_expired = await self.find_expired_keys() for key in keys_expired: await self.auto_rotate(key) await asyncio.sleep(3600) # 每小时检查 async def auto_rotate(self, key: APIKey): """自动轮换""" # 1. 创建新Key new_key = await self.generate_key(key.user_id, key.description) # 2. 标记旧Key key.status = 'rotating' key.rotated_at = datetime.now() key.replaced_by = new_key.id # 3. 通知用户 await self.notify_user(key.user_id, { 'type': 'key_rotated', 'old_key_id': key.id, 'new_key': new_key.key_prefix + '***' }) ``` --- ## 7. 实施计划 ### 7.1 优先级 | 任务 | 负责人 | 截止 | 依赖 | |------|--------|------|------| | 计费防篡改机制 | 后端 | S1前 | - | | 跨租户隔离强化 | 架构 | S1前 | - | | 密钥轮换机制 | 后端 | S0-M1 | - | | 激活码安全强化 | 后端 | S0-M1 | - | | DDoS防护机制 | 安全 | S0-M2 | - | | 日志脱敏规则 | 后端 | S0-M1 | - | | 密钥定期轮换 | 后端 | S0-M2 | - | ### 7.2 验证标准 - 所有计费操作都有审计日志 - 跨租户访问被强制拦截 - Key可以正常轮换和失效 - 激活码无法伪造 - DDoS攻击可被检测和阻断 - 敏感日志自动脱敏 --- **文档状态**:安全解决方案(修复版) **关联文档**: - `security_api_key_vulnerability_analysis_v1_2026-03-18.md` - `supply_detailed_design_v1_2026-03-18.md`