跳到主要内容

审计与合规

问题

数据库操作审计如何实施?数据脱敏和合规要求有哪些?

答案

一、数据库审计

数据库审计是记录谁在什么时间对什么数据做了什么操作的过程。

MySQL 审计

my.cnf
# 开启通用查询日志(记录所有 SQL,生产环境不建议长期开启)
general_log = 1
general_log_file = /var/log/mysql/general.log

# 慢查询日志
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
-- MySQL Enterprise Audit Plugin(企业版)
INSTALL PLUGIN audit_log SONAME 'audit_log.so';

-- 开源替代:MariaDB Audit Plugin
INSTALL SONAME 'server_audit';
SET GLOBAL server_audit_logging = ON;
SET GLOBAL server_audit_events = 'CONNECT,QUERY,TABLE';

PostgreSQL 审计

postgresql.conf
# pgAudit 扩展(推荐)
shared_preload_libraries = 'pgaudit'
pgaudit.log = 'write, ddl' # 审计写操作和 DDL
pgaudit.log_catalog = off
pgaudit.log_relation = on
pgaudit.log_parameter = on # 记录参数值
-- 安装 pgAudit
CREATE EXTENSION pgaudit;

-- 对特定角色审计
ALTER ROLE app_user SET pgaudit.log = 'read, write';

应用层审计

// 审计中间件:记录所有数据变更
class AuditInterceptor {
async intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest();
const userId = request.user?.id;
const method = request.method;
const path = request.path;

const result = await lastValueFrom(next.handle());

// 记录审计日志
await this.auditService.log({
userId,
action: `${method} ${path}`,
timestamp: new Date(),
ip: request.ip,
requestBody: this.sanitize(request.body), // 脱敏后记录
responseStatus: context.switchToHttp().getResponse().statusCode,
});

return result;
}
}

二、数据脱敏

将敏感数据进行变换,使其无法还原,但保留数据格式和统计特性。

脱敏规则

数据类型原始数据脱敏后方法
手机号13800138000138****8000中间掩码
身份证110101199001011234110***********1234中间掩码
邮箱alice@example.coma****@example.com前缀掩码
银行卡6222021001234567************4567仅保留后4位
姓名张三丰张*丰中间掩码
地址北京市朝阳区xxx路北京市朝阳区***截断

实现方式

// 脱敏工具函数
const masks = {
phone: (v: string) => v.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'),
idCard: (v: string) => v.replace(/(\d{3})\d{11}(\d{4})/, '$1***********$2'),
email: (v: string) => v.replace(/(.{1}).+(@.+)/, '$1****$2'),
bankCard: (v: string) => v.replace(/.(?=.{4})/g, '*'),
name: (v: string) => {
if (v.length <= 1) return v;
if (v.length === 2) return v[0] + '*';
return v[0] + '*'.repeat(v.length - 2) + v[v.length - 1];
},
};

// 使用装饰器自动脱敏
function Mask(type: keyof typeof masks) {
return function (target: any, propertyKey: string) {
// 在序列化时自动脱敏
};
}

class UserResponse {
name: string;
@Mask('phone') phone: string;
@Mask('email') email: string;
}

数据库视图脱敏

-- 创建脱敏视图
CREATE VIEW users_masked AS
SELECT
id,
CONCAT(LEFT(name, 1), '**') AS name,
CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS phone,
CONCAT(LEFT(email, 1), '****', SUBSTRING(email, LOCATE('@', email))) AS email,
created_at
FROM users;

-- 分析师只能查询脱敏视图
GRANT SELECT ON users_masked TO 'analyst'@'%';

三、合规要求

GDPR(欧盟通用数据保护条例)

要求数据库层面的实现
数据最小化只收集必要字段
存储期限自动清理过期数据(TTL)
被遗忘权支持删除用户所有数据
数据可携带权支持导出用户数据
访问控制RBAC + 审计日志
加密保护TDE + 传输加密
泄露通知72 小时内通知
-- 被遗忘权:删除用户所有数据
-- 需要处理外键级联
DELETE FROM user_logs WHERE user_id = ?;
DELETE FROM user_orders WHERE user_id = ?;
DELETE FROM user_profiles WHERE user_id = ?;
DELETE FROM users WHERE id = ?;

-- 或使用软删除 + 数据清洗
UPDATE users SET
name = 'DELETED',
email = CONCAT('deleted_', id, '@deleted.com'),
phone = NULL,
deleted_at = NOW()
WHERE id = ?;

中国个人信息保护法

要求实现方式
敏感信息加密存储字段加密(身份证、银行卡)
跨境传输限制数据本地化存储
操作可追溯数据库审计日志
最小必要原则按需查询字段

四、审计日志最佳实践

// 审计日志表设计
interface AuditLog {
id: string;
timestamp: Date;
userId: string;
action: 'CREATE' | 'READ' | 'UPDATE' | 'DELETE';
tableName: string;
recordId: string;
oldValue: object | null; // 变更前的值
newValue: object | null; // 变更后的值
ip: string;
userAgent: string;
}
实践说明
不可篡改审计日志表不授予 UPDATE/DELETE 权限
独立存储审计日志与业务数据分库
异步写入审计日志异步写入,不影响业务性能
定期归档超过保留期的日志归档到冷存储
告警规则异常操作(大量 DELETE、非工作时间访问)触发告警

常见面试问题

Q1: 静态脱敏和动态脱敏的区别?

答案

对比静态脱敏动态脱敏
时机离线处理(ETL)查询时实时脱敏
数据生成脱敏副本原始数据不变
使用场景测试环境数据准备生产环境查询控制
实现脱敏脚本 / 工具数据库视图 / 中间件 / 应用层

Q2: 如何设计一个可审计的删除操作?

答案

  1. 软删除:不物理删除,设置 deleted_at 字段
  2. 审计日志:记录删除操作的操作人、时间、删除的数据内容
  3. 回收站:将删除的数据移到回收站表,保留 30 天
  4. 权限控制:物理删除需要更高权限审批

Q3: 如何实现"被遗忘权"(数据删除请求)?

答案

  1. 数据映射:先梳理用户数据分布在哪些表、哪些服务
  2. 级联删除:按依赖顺序删除所有关联数据
  3. 匿名化:对于需要保留的统计数据,将个人标识替换为匿名值
  4. 确认机制:删除完成后生成确认报告
  5. 日志清理:同时清理包含用户信息的日志

相关链接