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为聚合根。
聚合边界与合规一致性 User(聚合根) AccessToken 实体 RefreshToken 实体 LoginHistory 值对象 聚合边界(事务一致性) 外部客户端 只能通过聚合根操作 合规约束(聚合内强制执行) • 一个用户最多5个有效RefreshToken • 撤销用户时原子撤销所有令牌

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)。
限界上下文划分与JWT安全策略 身份认证上下文 User 实体 Password 值对象 MfaToken 值对象 事件:UserLoggedIn 事件:PasswordChanged 令牌管理上下文 AccessToken 实体 RefreshToken 实体 TokenClaims 值对象 事件:TokenIssued 事件:TokenRevoked 审计合规上下文 AuditLog 实体 DomainEvent 值对象 ComplianceReport 订阅所有事件 生成审计轨迹 事件 事件 上下文映射规则 身份认证 → 令牌管理:通过事件发布/订阅解耦 | 审计合规:订阅所有上下文的事件

2.6 五大概念在合规边界中的协同

将上述五个概念组合使用,可以构建出既满足业务需求又符合安全合规的后端架构:

  1. 限界上下文划定安全边界,防止模型污染。
  2. 聚合保证令牌生命周期内的一致性。
  3. 实体提供可追溯的标识和状态。
  4. 值对象封装不可变的令牌声明和权限。
  5. 领域事件驱动审计日志和异步安全策略(如检测异常登录后自动撤销令牌)。
实战建议:在项目初期就绘制上下文映射图,明确每个限界上下文对JWT的依赖关系。例如,订单上下文不应直接解析JWT,而应通过身份认证上下文提供的 UserContext 值对象获取用户信息。这能有效降低安全漏洞的传播范围。