返回 筑基・数据元府藏真
数据库安全与合规
博主
大约 16 分钟
数据库安全与合规
一、问题引入:数据泄露危机
1.1 真实案例:用户信息泄露事件
事故时间:2024年3月
事故场景:某电商平台用户数据泄露,涉及500万用户信息
攻击路径分析:
┌─────────────────────────────────────────────────────────────┐
│ T1 攻击者通过SQL注入获取数据库访问权限 │
│ - 利用订单查询接口的注入漏洞 │
│ - 注入 payload:' UNION SELECT * FROM user-- │
│ │
│ T2 提升权限,获取敏感数据 │
│ - 发现应用账号具有DBA权限 │
│ - 直接导出user表所有数据 │
│ │
│ T3 数据在暗网出售 │
│ - 包含:手机号、地址、身份证号 │
│ - 部分用户密码明文存储 │
│ │
│ 后果: │
│ - 监管罚款:200万元(违反个人信息保护法) │
│ - 品牌损失:用户信任度下降,流失15% │
│ - 法律风险:面临集体诉讼 │
│ - 整改成本:安全体系重建,花费500万 │
└─────────────────────────────────────────────────────────────┘
安全漏洞复盘:
1. 应用存在SQL注入漏洞
2. 数据库账号权限过大
3. 敏感数据未加密存储
4. 缺少审计日志
5. 没有数据脱敏机制
6. 安全补丁未及时更新
整改措施:
1. 全面代码审计,修复注入漏洞
2. 实施最小权限原则
3. 敏感字段加密存储
4. 部署数据库审计系统
5. 数据脱敏展示
6. 建立安全更新流程
1.2 数据库安全威胁全景
数据库面临的威胁:
┌──────────────────────────────────────────────────────────────┐
│ │
│ 外部威胁 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ • SQL注入攻击 │ │
│ │ • 暴力破解密码 │ │
│ │ • 网络嗅探 │ │
│ │ • 勒索软件加密 │ │
│ │ • DDoS攻击 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 内部威胁 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ • 越权访问 │ │
│ │ • 数据窃取 │ │
│ │ • 误操作 │ │
│ │ • 账号共享 │ │
│ │ • 离职人员权限未回收 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 系统漏洞 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ • 数据库软件漏洞 │ │
│ │ • 操作系统漏洞 │ │
│ │ • 配置错误 │ │
│ │ • 默认密码 │ │
│ │ • 未加密传输 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
二、访问控制与权限管理
2.1 最小权限原则
-- 1. 创建应用专用账号(禁止DDL权限)
CREATE USER 'app_readonly'@'10.0.%.%' IDENTIFIED BY 'ComplexP@ssw0rd123!';
CREATE USER 'app_readwrite'@'10.0.%.%' IDENTIFIED BY 'ComplexP@ssw0rd456!';
CREATE USER 'app_admin'@'10.0.%.%' IDENTIFIED BY 'ComplexP@ssw0rd789!';
-- 2. 只读账号 - 报表查询、数据分析
GRANT SELECT ON mydb.orders TO 'app_readonly'@'10.0.%.%';
GRANT SELECT ON mydb.products TO 'app_readonly'@'10.0.%.%';
-- 3. 读写账号 - 业务应用
GRANT SELECT, INSERT, UPDATE ON mydb.orders TO 'app_readwrite'@'10.0.%.%';
GRANT SELECT, INSERT, UPDATE ON mydb.order_items TO 'app_readwrite'@'10.0.%.%';
GRANT SELECT ON mydb.products TO 'app_readwrite'@'10.0.%.%';
-- 明确禁止DELETE权限
REVOKE DELETE ON mydb.* FROM 'app_readwrite'@'10.0.%.%';
-- 4. 管理账号 - 运维使用(限制IP)
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX ON mydb.*
TO 'app_admin'@'10.0.1.10';
-- 5. 禁止危险权限
REVOKE ALL PRIVILEGES ON *.* FROM 'app_admin'@'10.0.1.10';
REVOKE GRANT OPTION ON *.* FROM 'app_admin'@'10.0.1.10';
REVOKE FILE ON *.* FROM 'app_admin'@'10.0.1.10';
REVOKE SUPER ON *.* FROM 'app_admin'@'10.0.1.10';
-- 6. 查看权限
SHOW GRANTS FOR 'app_readwrite'@'10.0.%.%';
-- 7. 定期审计权限
SELECT user, host, db, select_priv, insert_priv, update_priv, delete_priv
FROM mysql.db WHERE db = 'mydb';
-- 8. 回收离职人员权限
REVOKE ALL PRIVILEGES ON *.* FROM 'old_employee'@'%';
DROP USER 'old_employee'@'%';
2.2 账号生命周期管理
-- 1. 密码策略配置
SET GLOBAL validate_password.policy = STRONG;
SET GLOBAL validate_password.length = 12;
SET GLOBAL validate_password.mixed_case_count = 1;
SET GLOBAL validate_password.number_count = 1;
SET GLOBAL validate_password.special_char_count = 1;
-- 2. 密码过期策略
ALTER USER 'app_readwrite'@'10.0.%.%' PASSWORD EXPIRE INTERVAL 90 DAY;
-- 3. 登录失败锁定
INSTALL PLUGIN CONNECTION_CONTROL SONAME 'connection_control.so';
INSTALL PLUGIN CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS SONAME 'connection_control.so';
SET GLOBAL connection_control_failed_connections_threshold = 5;
SET GLOBAL connection_control_min_connection_delay = 1000;
SET GLOBAL connection_control_max_connection_delay = 20000;
-- 4. 空闲会话超时
SET GLOBAL wait_timeout = 3600; -- 1小时
SET GLOBAL interactive_timeout = 3600;
-- 5. 创建临时账号(带过期时间)
CREATE USER 'temp_developer'@'10.0.1.%' IDENTIFIED BY 'TempP@ss123!'
PASSWORD EXPIRE INTERVAL 7 DAY;
GRANT SELECT ON mydb.* TO 'temp_developer'@'10.0.1.%';
-- 6. 定期清理无效账号
SELECT user, host, password_last_changed, password_lifetime
FROM mysql.user
WHERE password_last_changed < DATE_SUB(NOW(), INTERVAL 1 YEAR);
三、数据加密
3.1 传输加密(SSL/TLS)
# 1. 生成SSL证书
mkdir -p /etc/mysql/ssl
cd /etc/mysql/ssl
# 生成CA证书
openssl genrsa 2048 > ca-key.pem
openssl req -new -x509 -nodes -days 3650 -key ca-key.pem -out ca-cert.pem \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=MySQL-CA"
# 生成服务器证书
openssl req -newkey rsa:2048 -days 3650 -nodes -keyout server-key.pem -out server-req.pem \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=mysql.server.com"
openssl rsa -in server-key.pem -out server-key.pem
openssl x509 -req -in server-req.pem -days 3650 -CA ca-cert.pem -CAkey ca-key.pem \
-set_serial 01 -out server-cert.pem
# 生成客户端证书
openssl req -newkey rsa:2048 -days 3650 -nodes -keyout client-key.pem -out client-req.pem \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=mysql.client.com"
openssl rsa -in client-key.pem -out client-key.pem
openssl x509 -req -in client-req.pem -days 3650 -CA ca-cert.pem -CAkey ca-key.pem \
-set_serial 01 -out client-cert.pem
# 设置权限
chown -R mysql:mysql /etc/mysql/ssl
chmod 600 /etc/mysql/ssl/*.pem
# my.cnf SSL配置
[mysqld]
# 强制SSL连接
require_secure_transport = ON
# SSL证书路径
ssl-ca=/etc/mysql/ssl/ca-cert.pem
ssl-cert=/etc/mysql/ssl/server-cert.pem
ssl-key=/etc/mysql/ssl/server-key.pem
# 加密连接参数
tls_version=TLSv1.2,TLSv1.3
ssl_cipher=ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
# 客户端配置
[client]
ssl-ca=/etc/mysql/ssl/ca-cert.pem
ssl-cert=/etc/mysql/ssl/client-cert.pem
ssl-key=/etc/mysql/ssl/client-key.pem
ssl-mode=VERIFY_CA
-- 验证SSL连接
SHOW VARIABLES LIKE '%ssl%';
SHOW STATUS LIKE 'Ssl_cipher';
-- 查看当前连接是否使用SSL
SELECT id, user, host, ssl_type, ssl_cipher
FROM information_schema.processlist
WHERE ssl_type IS NOT NULL;
-- 创建强制SSL的用户
CREATE USER 'secure_user'@'%' REQUIRE SSL;
CREATE USER 'secure_user'@'%' REQUIRE X509; -- 需要客户端证书
3.2 存储加密(TDE)
-- MySQL 8.0 TDE配置
-- 1. 安装keyring插件
INSTALL PLUGIN keyring_file SONAME 'keyring_file.so';
-- 2. 配置keyring
SET GLOBAL keyring_file_data = '/var/lib/mysql-keyring/keyring';
-- 3. 创建加密表空间
CREATE TABLESPACE encrypted_ts
ADD DATAFILE 'encrypted_ts.ibd'
ENCRYPTION='Y';
-- 4. 创建加密表
CREATE TABLE sensitive_data (
id INT PRIMARY KEY,
ssn VARCHAR(20),
credit_card VARCHAR(20)
) ENCRYPTION='Y';
-- 5. 对现有表启用加密
ALTER TABLE orders ENCRYPTION='Y';
-- 6. 查看加密状态
SELECT TABLE_NAME, TABLE_SCHEMA, CREATE_OPTIONS
FROM information_schema.TABLES
WHERE CREATE_OPTIONS LIKE '%ENCRYPTION%'
OR TABLESPACE_NAME IN (SELECT TABLESPACE_NAME
FROM information_schema.INNODB_TABLESPACES
WHERE ENCRYPTION='Y');
-- 7. 轮换加密密钥(定期执行)
ALTER INSTANCE ROTATE INNODB MASTER KEY;
3.3 应用层加密
/**
* 敏感数据加密服务
*/
@Service
public class EncryptionService {
@Value("${encryption.key}")
private String encryptionKey;
private static final String ALGORITHM = "AES/GCM/NoPadding";
private static final int GCM_IV_LENGTH = 12;
private static final int GCM_TAG_LENGTH = 16;
/**
* 加密敏感字段
*/
public String encrypt(String plaintext) {
if (plaintext == null || plaintext.isEmpty()) {
return plaintext;
}
try {
byte[] iv = new byte[GCM_IV_LENGTH];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
Cipher cipher = Cipher.getInstance(ALGORITHM);
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
SecretKeySpec keySpec = new SecretKeySpec(encryptionKey.getBytes(StandardCharsets.UTF_8), "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, parameterSpec);
byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// 组合IV和密文
byte[] combined = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length);
return Base64.getEncoder().encodeToString(combined);
} catch (Exception e) {
throw new RuntimeException("加密失败", e);
}
}
/**
* 解密敏感字段
*/
public String decrypt(String ciphertext) {
if (ciphertext == null || ciphertext.isEmpty()) {
return ciphertext;
}
try {
byte[] combined = Base64.getDecoder().decode(ciphertext);
byte[] iv = new byte[GCM_IV_LENGTH];
byte[] encrypted = new byte[combined.length - GCM_IV_LENGTH];
System.arraycopy(combined, 0, iv, 0, iv.length);
System.arraycopy(combined, iv.length, encrypted, 0, encrypted.length);
Cipher cipher = Cipher.getInstance(ALGORITHM);
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
SecretKeySpec keySpec = new SecretKeySpec(encryptionKey.getBytes(StandardCharsets.UTF_8), "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec, parameterSpec);
byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("解密失败", e);
}
}
/**
* 哈希敏感数据(用于查询)
*/
public String hash(String input) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("哈希失败", e);
}
}
}
/**
* 加密字段的JPA转换器
*/
@Converter
public class EncryptedAttributeConverter implements AttributeConverter<String, String> {
@Autowired
private EncryptionService encryptionService;
@Override
public String convertToDatabaseColumn(String attribute) {
return encryptionService.encrypt(attribute);
}
@Override
public String convertToEntityAttribute(String dbData) {
return encryptionService.decrypt(dbData);
}
}
/**
* 用户实体(使用加密)
*/
@Entity
@Table(name = "user")
public class User {
@Id
private Long id;
private String username;
// 手机号加密存储
@Convert(converter = EncryptedAttributeConverter.class)
@Column(name = "phone_encrypted")
private String phone;
// 手机号哈希(用于查询)
@Column(name = "phone_hash")
private String phoneHash;
// 身份证号加密
@Convert(converter = EncryptedAttributeConverter.class)
private String idCard;
// 设置手机号时同时设置哈希
public void setPhone(String phone) {
this.phone = phone;
this.phoneHash = encryptionService.hash(phone);
}
}
四、SQL注入防护
4.1 参数化查询
/**
* SQL注入防护示例
*/
@Repository
public class UserRepository {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* ❌ 错误:字符串拼接SQL
*/
public User findByUsernameUnsafe(String username) {
// 危险!存在SQL注入
String sql = "SELECT * FROM user WHERE username = '" + username + "'";
// 攻击者可以传入:' OR '1'='1
return jdbcTemplate.queryForObject(sql, User.class);
}
/**
* ✅ 正确:使用参数化查询
*/
public User findByUsernameSafe(String username) {
String sql = "SELECT * FROM user WHERE username = ?";
return jdbcTemplate.queryForObject(sql, new Object[]{username}, User.class);
}
/**
* ✅ 正确:使用NamedParameterJdbcTemplate
*/
public User findByUsernameNamed(String username) {
String sql = "SELECT * FROM user WHERE username = :username";
Map<String, Object> params = new HashMap<>();
params.put("username", username);
return namedParameterJdbcTemplate.queryForObject(sql, params, User.class);
}
}
/**
* MyBatis参数化
*/
@Mapper
public interface UserMapper {
// ✅ 正确:使用#{}占位符(预编译)
@Select("SELECT * FROM user WHERE username = #{username}")
User findByUsername(@Param("username") String username);
// ❌ 错误:使用${}字符串替换(有注入风险)
@Select("SELECT * FROM ${tableName} WHERE id = #{id}")
User findByIdUnsafe(@Param("tableName") String tableName, @Param("id") Long id);
// ✅ 正确:动态SQL使用安全方式
@Select("<script>" +
"SELECT * FROM user " +
"<where>" +
" <if test='username != null'> AND username = #{username} </if>" +
" <if test='email != null'> AND email = #{email} </if>" +
"</where>" +
"</script>")
List<User> findByCondition(@Param("username") String username,
@Param("email") String email);
}
4.2 输入验证与过滤
/**
* 输入验证服务
*/
@Component
public class InputValidationService {
// SQL关键字黑名单
private static final Pattern SQL_INJECTION_PATTERN = Pattern.compile(
"('|\"|;|--|/\\*|\\*/|union|select|insert|update|delete|drop|create|alter|exec|execute|xp_|sp_)",
Pattern.CASE_INSENSITIVE
);
/**
* 验证输入是否安全
*/
public boolean isSafe(String input) {
if (input == null || input.isEmpty()) {
return true;
}
return !SQL_INJECTION_PATTERN.matcher(input).find();
}
/**
* 清理危险字符
*/
public String sanitize(String input) {
if (input == null) {
return null;
}
return input.replaceAll("['\";\\-\\/]", "");
}
/**
* 验证排序字段(防止注入)
*/
public String validateOrderBy(String orderBy) {
Set<String> allowedColumns = Set.of("id", "username", "created_at", "updated_at");
Set<String> allowedDirections = Set.of("ASC", "DESC");
String[] parts = orderBy.split("\\s+");
String column = parts[0];
String direction = parts.length > 1 ? parts[1].toUpperCase() : "ASC";
if (!allowedColumns.contains(column) || !allowedDirections.contains(direction)) {
throw new IllegalArgumentException("Invalid order by clause");
}
return column + " " + direction;
}
}
五、审计与监控
5.1 数据库审计
-- MySQL审计插件配置
-- 1. 安装审计插件
INSTALL PLUGIN audit_log SONAME 'audit_log.so';
-- 2. 配置审计策略
SET GLOBAL audit_log_policy = 'ALL'; -- ALL, LOGINS, QUERIES, NONE
SET GLOBAL audit_log_format = 'JSON';
SET GLOBAL audit_log_file = '/var/lib/mysql/audit.log';
-- 3. 审计特定表
SET GLOBAL audit_log_include_accounts = 'app_user@%,admin@%';
SET GLOBAL audit_log_exclude_accounts = 'monitor@%,backup@%';
-- 4. 查看审计日志
SELECT * FROM mysql.audit_log ORDER BY timestamp DESC LIMIT 100;
-- 5. 查询敏感操作
SELECT * FROM mysql.audit_log
WHERE command_class IN ('drop_table', 'delete', 'update')
AND timestamp > DATE_SUB(NOW(), INTERVAL 1 DAY);
/**
* 数据库操作审计
*/
@Aspect
@Component
@Slf4j
public class DatabaseAuditAspect {
@Autowired
private AuditLogRepository auditLogRepository;
@Around("@annotation(auditable)")
public Object audit(ProceedingJoinPoint joinPoint, Auditable auditable) throws Throwable {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
String operation = auditable.operation();
String table = auditable.table();
AuditLog auditLog = new AuditLog();
auditLog.setUsername(username);
auditLog.setOperation(operation);
auditLog.setTableName(table);
auditLog.setRequestTime(LocalDateTime.now());
auditLog.setIpAddress(getClientIp());
try {
Object result = joinPoint.proceed();
auditLog.setStatus("SUCCESS");
return result;
} catch (Exception e) {
auditLog.setStatus("FAILED");
auditLog.setErrorMessage(e.getMessage());
throw e;
} finally {
auditLog.setResponseTime(LocalDateTime.now());
auditLogRepository.save(auditLog);
}
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Auditable {
String operation();
String table();
}
5.2 数据脱敏
/**
* 数据脱敏服务
*/
@Component
public class DataMaskingService {
/**
* 手机号脱敏
*/
public String maskPhone(String phone) {
if (phone == null || phone.length() != 11) {
return phone;
}
return phone.substring(0, 3) + "****" + phone.substring(7);
}
/**
* 身份证号脱敏
*/
public String maskIdCard(String idCard) {
if (idCard == null || idCard.length() != 18) {
return idCard;
}
return idCard.substring(0, 6) + "********" + idCard.substring(14);
}
/**
* 邮箱脱敏
*/
public String maskEmail(String email) {
if (email == null || !email.contains("@")) {
return email;
}
String[] parts = email.split("@");
String local = parts[0];
String domain = parts[1];
if (local.length() <= 2) {
return "*@" + domain;
}
return local.substring(0, 2) + "***@" + domain;
}
/**
* 银行卡号脱敏
*/
public String maskBankCard(String cardNo) {
if (cardNo == null || cardNo.length() < 8) {
return cardNo;
}
return cardNo.substring(0, 4) + " **** **** " + cardNo.substring(cardNo.length() - 4);
}
/**
* 姓名脱敏
*/
public String maskName(String name) {
if (name == null || name.isEmpty()) {
return name;
}
if (name.length() == 2) {
return "*" + name.substring(1);
}
return name.charAt(0) + "*" + name.substring(name.length() - 1);
}
}
/**
* 脱敏JSON序列化器
*/
public class MaskingSerializer extends JsonSerializer<String> {
private final MaskingType type;
public MaskingSerializer(MaskingType type) {
this.type = type;
}
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
DataMaskingService maskingService = SpringContextUtil.getBean(DataMaskingService.class);
String masked = switch (type) {
case PHONE -> maskingService.maskPhone(value);
case ID_CARD -> maskingService.maskIdCard(value);
case EMAIL -> maskingService.maskEmail(value);
case BANK_CARD -> maskingService.maskBankCard(value);
case NAME -> maskingService.maskName(value);
default -> value;
};
gen.writeString(masked);
}
}
// 使用示例
public class UserDTO {
private String username;
@JsonSerialize(using = MaskingSerializer.class)
@MaskingType(MaskingType.PHONE)
private String phone;
@JsonSerialize(using = MaskingSerializer.class)
@MaskingType(MaskingType.ID_CARD)
private String idCard;
}
六、合规要求
6.1 等保2.0要求
等保2.0(三级)数据库安全要求:
┌──────────────────────────────────────────────────────────────┐
│ │
│ 身份鉴别 │
│ - 使用双因素认证 │
│ - 密码复杂度要求 │
│ - 登录失败锁定 │
│ - 会话超时 │
│ │
│ 访问控制 │
│ - 最小权限原则 │
│ - 敏感数据分级 │
│ - 操作审批流程 │
│ │
│ 安全审计 │
│ - 启用审计日志 │
│ - 审计记录留存6个月以上 │
│ - 审计记录防篡改 │
│ │
│ 数据完整性 │
│ - 传输完整性校验 │
│ - 存储完整性保护 │
│ │
│ 数据保密性 │
│ - 传输加密(TLS) │
│ - 存储加密(TDE) │
│ - 敏感数据脱敏 │
│ │
│ 数据备份恢复 │
│ - 定期备份 │
│ - 异地备份 │
│ - 恢复演练 │
│ │
└──────────────────────────────────────────────────────────────┘
6.2 个人信息保护法合规
个人信息保护法要求:
┌──────────────────────────────────────────────────────────────┐
│ │
│ 数据收集 │
│ - 明确告知收集目的 │
│ - 获得用户同意 │
│ - 最小必要原则 │
│ │
│ 数据存储 │
│ - 敏感信息加密存储 │
│ - 去标识化处理 │
│ - 访问日志记录 │
│ │
│ 数据使用 │
│ - 不超范围使用 │
│ - 不非法提供给他人 │
│ - 自动化决策透明 │
│ │
│ 数据安全 │
│ - 制定内部管理制度 │
│ - 采取技术保护措施 │
│ - 定期安全培训 │
│ │
│ 数据删除 │
│ - 用户要求时及时删除 │
│ - 服务终止时删除 │
│ - 留存期满时删除 │
│ │
└──────────────────────────────────────────────────────────────┘
七、安全最佳实践清单
┌─────────────────────────────────────────────────────────────────────┐
│ 数据库安全最佳实践清单 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【账号安全】 │
│ □ 1. 实施最小权限原则 │
│ □ 2. 定期轮换密码(90天) │
│ □ 3. 启用登录失败锁定 │
│ □ 4. 删除默认账号(如root@%) │
│ □ 5. 定期审计账号权限 │
│ │
│ 【网络安全】 │
│ □ 1. 强制SSL/TLS加密连接 │
│ □ 2. 限制数据库访问IP │
│ □ 3. 使用防火墙隔离数据库 │
│ □ 4. 关闭不必要的端口 │
│ │
│ 【数据安全】 │
│ □ 1. 敏感数据加密存储 │
│ □ 2. 敏感数据脱敏展示 │
│ □ 3. 使用参数化查询防注入 │
│ □ 4. 启用数据库审计 │
│ │
│ 【运维安全】 │
│ □ 1. 及时更新安全补丁 │
│ □ 2. 定期安全扫描 │
│ □ 3. 制定灾难恢复预案 │
│ □ 4. 定期安全培训 │
│ │
│ 【合规要求】 │
│ □ 1. 满足等保要求 │
│ □ 2. 个人信息保护合规 │
│ □ 3. 审计日志留存6个月以上 │
│ □ 4. 定期合规检查 │
│ │
└─────────────────────────────────────────────────────────────────────┘
系列上一篇:备份恢复与灾难恢复策略
系列总结
本系列从数据库选型、设计、优化、架构、高级特性到运维安全,构建了完整的数据库知识体系。掌握这些知识,可以应对从单机到分布式、从开发到生产的各种数据库挑战。
知识点测试
读完文章了?来测试一下你对知识点的掌握程度吧!
评论区
使用 GitHub 账号登录后即可发表评论,支持 Markdown 格式。
如果评论系统无法加载,请确保:
- 您的网络可以访问 GitHub
- giscus GitHub App 已安装到仓库
- 仓库已启用 Discussions 功能