第2章:领域建模安全(Domain Modeling Security)
一、导读
1.1 本章学习目标
第2章讲解如何通过领域建模来增强安全性——让类型系统成为安全的第一道防线。
本章的核心学习目标包括:
- 理解值对象在安全中的作用
- 掌握不变量的设计
- 学会用领域边界隔离安全关注点
- 理解类型驱动的安全设计
1.2 为什么本章如此重要
好的领域模型能防止非法状态被表示。如果你的类型系统能表达"一个有效的邮箱地址"而不仅仅是"一个字符串",那么很多安全漏洞在编译时就能被发现。
1.3 核心问题引导
值对象如何帮助防止注入攻击?
不变量在安全设计中扮演什么角色?
如何用领域边界隔离安全关注点?
类型系统能成为安全防线吗?
二、核心概念详解
2.1 值对象与安全
java
// 不好的设计:使用原始类型
public void sendEmail(String email, String message) {
// email 可能是恶意的
// message 可能包含注入代码
}
// 好的设计:使用值对象
public class EmailAddress {
private final String value;
public EmailAddress(String value) {
if (!isValid(value)) {
throw new IllegalArgumentException("Invalid email");
}
this.value = value;
}
private boolean isValid(String value) {
return value.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}
public String getValue() { return value; }
}
public void sendEmail(EmailAddress email, Message message) {
// email 一定是有效的
// message 是类型安全的
}2.2 不变量
java
// 不可变的金额类
public final class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
this.amount = amount;
this.currency = currency;
}
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch");
}
return new Money(this.amount.add(other.amount), this.currency);
}
}2.3 领域边界
java
// 安全边界:认证领域
public class AuthContext {
private final UserId userId;
private final Set<Permission> permissions;
public boolean hasPermission(Permission p) {
return permissions.contains(p);
}
}
// 业务领域:使用认证上下文
public class OrderService {
public Order createOrder(OrderRequest request, AuthContext auth) {
if (!auth.hasPermission(Permission.CREATE_ORDER)) {
throw new AccessDeniedException();
}
// 业务逻辑不需要关心认证细节
}
}三、实战案例
3.1 案例一:类型安全的 SQL 查询
java
// 值对象防止 SQL 注入
public class UserId {
private final UUID value;
public UserId(String value) {
try {
this.value = UUID.fromString(value);
} catch (IllegalArgumentException e) {
throw new SecurityException("Invalid user ID format");
}
}
public UUID getValue() { return value; }
}
// 查询构建器使用值对象
public class UserQuery {
public User findById(UserId id) {
// id 一定是有效的 UUID,不可能注入 SQL
return jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = ?",
new Object[]{id.getValue()},
UserRowMapper.INSTANCE
);
}
}四、本章小结
值对象:通过类型约束防止非法数据。
不变量:确保对象始终处于有效状态。
领域边界:隔离安全关注点,减少攻击面。
类型驱动安全:让编译器成为安全防线。
五、思考与练习
设计一组值对象来防止常见的 Web 安全漏洞。
如何用不变量保护一个银行账户系统?
分析一个你熟悉的系统,找出可以用领域建模改进的安全点。