2. 领域驱动设计(DDD)核心概念:实体、值对象、聚合、领域事件、限界上下文
领域驱动设计(Domain-Driven Design,DDD)是一套应对复杂业务建模的方法论。在构建后端合规边界与JWT令牌安全策略时,DDD提供了一种将安全规则、令牌生命周期与业务逻辑深度融合的建模语言。本章聚焦DDD的五大核心构造块,并阐明它们如何映射到合规与安全设计。
2.1 实体(Entity)
实体是具有唯一标识且会随时间改变状态的对象。在合规场景中,用户(User)、令牌(Token)、审计日志(AuditLog)都是典型的实体。
- 标识稳定性:实体的ID(如
userId、jti)一旦创建不可变更。 - 状态变化:令牌实体从“已签发”变为“已撤销”,用户实体从“活跃”变为“锁定”。
- 合规映射:每个实体必须记录创建时间、最后修改时间、操作者,以满足审计追溯要求。
设计提示:JWT的
jti(JWT ID)是令牌实体的天然唯一标识。在数据库中建立 token_entity 表,将 jti 设为主键,并附加 user_id、issued_at、expires_at、revoked 字段,即可实现令牌的实体化管理。
2.2 值对象(Value Object)
值对象通过属性值来定义,没有唯一标识,且不可变。在安全策略中,令牌载荷(TokenPayload)、权限集合(PermissionSet)、IP地址(IpAddress)都是值对象。
| 特征 | 实体 | 值对象 |
|---|---|---|
| 有无标识 | 有(ID) | 无 |
| 可变性 | 可变 | 不可变 |
| 相等性判断 | ID相等 | 所有属性相等 |
| 合规示例 | Token(jti) | TokenClaims(sub, roles, exp) |
// 值对象示例:不可变的令牌声明
public final class TokenClaims {
private final String subject;
private final Set<String> roles;
private final Instant issuedAt;
private final Instant expiresAt;
// 构造时校验所有字段,确保合规
public TokenClaims(String subject, Set<String> roles,
Instant issuedAt, Instant expiresAt) {
if (subject == null || subject.isBlank()) {
throw new IllegalArgumentException("subject不能为空");
}
if (expiresAt.isBefore(issuedAt)) {
throw new IllegalArgumentException("过期时间不能早于签发时间");
}
this.subject = subject;
this.roles = Collections.unmodifiableSet(new HashSet<>(roles));
this.issuedAt = issuedAt;
this.expiresAt = expiresAt;
}
// getters 省略
}
2.3 聚合(Aggregate)
聚合是一组相关对象的集群,外部只能通过聚合根(Aggregate Root)访问内部对象。聚合是数据一致性边界和事务边界。
- 聚合根:唯一能被外部引用的实体。例如
User作为聚合根,管理其下的RefreshToken实体和LoginHistory值对象。 - 一致性规则:一个用户最多只能有5个有效的刷新令牌;撤销用户时,必须同时撤销其所有令牌。
- 合规边界:聚合内的所有变更必须在一次数据库事务中完成,防止部分更新导致安全漏洞。
常见错误:将JWT的访问令牌(Access Token)和刷新令牌(Refresh Token)放在不同的聚合中管理,导致撤销操作无法原子化。正确做法是将它们放在同一个
User 聚合内,以用户ID为聚合根。
2.4 领域事件(Domain Event)
领域事件表示领域中发生的、业务人员关心的有意义的事件。在安全合规中,领域事件是审计日志和事件驱动安全策略的核心。
- 典型事件:
UserLoggedIn、TokenRefreshed、TokenRevoked、PasswordChanged。 - 事件内容:事件ID、发生时间、事件类型、聚合ID、事件数据(如IP地址、设备指纹)。
- 合规价值:领域事件天然形成不可篡改的审计轨迹,满足GDPR、SOX等法规的日志留存要求。
// 领域事件示例:令牌撤销事件
public class TokenRevokedEvent implements DomainEvent {
private final String eventId;
private final Instant occurredOn;
private final String userId;
private final String jti;
private final RevocationReason reason;
public TokenRevokedEvent(String userId, String jti, RevocationReason reason) {
this.eventId = UUID.randomUUID().toString();
this.occurredOn = Instant.now();
this.userId = userId;
this.jti = jti;
this.reason = reason;
}
// getters 省略
}
2.5 限界上下文(Bounded Context)
限界上下文是DDD中最关键的战术模式,它定义了模型边界和通用语言。在JWT安全策略中,至少需要划分三个限界上下文:
| 限界上下文 | 核心职责 | JWT相关模型 | 合规关注点 |
|---|---|---|---|
| 身份认证上下文 | 用户登录、凭证验证 | User、Password、MfaToken | 密码强度、MFA强制、登录失败锁定 |
| 令牌管理上下文 | 令牌签发、刷新、撤销 | AccessToken、RefreshToken、TokenClaims | 令牌有效期、撤销列表、刷新次数限制 |
| 审计合规上下文 | 事件记录、审计查询、报表 | AuditLog、DomainEvent、ComplianceReport | 不可篡改、保留期限、数据脱敏 |
关键原则:每个限界上下文拥有独立的数据库模式或至少独立的表空间。令牌管理上下文不应直接读取用户密码哈希,身份认证上下文不应关心令牌的撤销逻辑。通过上下文映射(Context Map)定义集成方式,如事件发布/订阅或防腐层(Anti-Corruption Layer)。
2.6 五大概念在合规边界中的协同
将上述五个概念组合使用,可以构建出既满足业务需求又符合安全合规的后端架构:
- 限界上下文划定安全边界,防止模型污染。
- 聚合保证令牌生命周期内的一致性。
- 实体提供可追溯的标识和状态。
- 值对象封装不可变的令牌声明和权限。
- 领域事件驱动审计日志和异步安全策略(如检测异常登录后自动撤销令牌)。
实战建议:在项目初期就绘制上下文映射图,明确每个限界上下文对JWT的依赖关系。例如,订单上下文不应直接解析JWT,而应通过身份认证上下文提供的
UserContext 值对象获取用户信息。这能有效降低安全漏洞的传播范围。