Skip to main content

多因子认证组件 (MFA)

API文档​

MFA 管理内置控制器 API 文档(Apifox) - 绑定、激活、解绑、可信设备管理、设为主管理设备、备份码验证等接口定义与示例

组件作用​

项目开发中,多因子认证(MFA)是一个重要的安全增强功能,而不同的客户会有不同的安全要求和部署环境。通常情况下,我们需要自行实现TOTP/HOTP算法、密钥管理、设备信任、审计日志等功能,这些功能的实现复杂度高,且容易出错。

为了解决该问题,我们设计了MFA组件,该组件提供了统一的多因子认证接口,屏蔽了底层实现细节,使得业务代码可以不用关心密钥管理、验证算法、设备信任等底层实现,将所有差异屏蔽到配置文件中,代码中提供统一的管理和验证接口进行调用,通过调整配置文件的内容来达到对不同部署环境的支持,从而提高了代码的可读性和可维护性。

重要架构说明:MFA组件采用分离式架构设计,拆分为两个独立的模块:

  • rydeen-component-mfa-validation:验证模块,部署在 rydeen-gateway-service 中

    • 职责:MFA验证逻辑,只读GlobalCache(rydeen-component-cache),零数据库依赖
    • 特点:轻量级、高性能、毫秒级响应(<10ms)
  • rydeen-component-mfa-management:管理模块,部署在 rydeen-general-service 中

    • 职责:MFA管理功能(绑定、解绑、状态管理等),操作数据库
    • 特点:完整的CRUD操作,使用Liquibase管理DDL

目前我们支持的认证方式有:TOTP(基于时间的一次性密码)、HOTP(基于事件的一次性密码)、SMS验证码、Email验证码、备份码。基本已经覆盖了常见的企业级MFA需求。当需要更换认证方式或部署环境的时候,我们仅仅需要引入对应的组件包并调整配置文件即可,MFA组件包含的包清单如下:

核心包:

  • rydeen-component-mfa-core
  • rydeen-component-mfa-validation(网关验证模块)
  • rydeen-component-mfa-management(管理服务模块)

组件用法​

引入依赖​

MFA组件配置方式如下:

首先在项目所属的Maven模块中引入组件坐标:

网关服务(验证模块)​

在 rydeen-gateway-service 中引入验证模块:

<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-mfa-validation</artifactId>
</dependency>

通用服务(管理模块)​

在 rydeen-general-service 中引入管理模块:

<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-mfa-management</artifactId>
</dependency>

<!-- 依赖组件 -->
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-dao</artifactId>
</dependency>
<dependency>
<groupId>cn.com.rydeen.component</groupId>
<artifactId>rydeen-component-cache</artifactId>
</dependency>

注意:两个模块需要同时引入,验证模块部署在网关服务,管理模块部署在通用服务。

配置文件​

MFA组件的配置统一在Nacos配置中心管理,网关服务和通用服务共享同一份配置。配置包含两部分:验证模块配置(网关服务使用)和管理模块配置(通用服务使用)。

想要理解MFA组件的配置文件,首先需要了解MFA组件的几个基本概念:

  • 验证模块(validation):部署在网关服务,负责MFA验证逻辑,只读GlobalCache,零数据库依赖
  • 管理模块(management):部署在通用服务,负责MFA管理功能(绑定、解绑、状态管理等),操作数据库
  • 分离式架构:验证与管理完全分离,降低耦合度,符合网关轻量化原则
  • 缓存优先:热点数据优先使用缓存,减少数据库访问,提升性能
  • 统一配置:配置统一在Nacos配置中心管理,两个服务共享同一份配置

配置示例​

MFA组件的配置统一在Nacos配置中心管理,网关服务和通用服务共享同一份配置:

platform:
component:
mfa:
# 启用MFA(验证模块和管理模块共用)
enabled: true

# 管理模块配置(通用服务使用)
management:
issuer: "Rydeen Inc." # 发行方名称(显示在Authenticator应用中)
controller-enabled: true # 启用Controller(默认)
api-prefix: "/api/mfa" # API路径前缀
ttl-hours: 24 # 缓存TTL(小时)

# TOTP配置(管理模块和验证模块共用)
totp:
algorithm: "SHA1" # 算法(SHA1/SHA256/SHA512),默认SHA1
digits: 6 # 验证码位数
period: 30 # 时间窗口周期(秒)
window-size: 1 # 窗口大小(容错窗口,±N表示允许前后N个时间窗口)
code-length: 6 # 验证码长度(与digits相同)
time-window: 30 # 时间窗口(秒,与period相同)

# 安全策略配置(管理模块和验证模块共用)
security:
# 密钥管理配置
key-management:
provider: vault # vault | kms | hsm | local
# vault: # Vault配置(当provider=vault时使用)
# endpoint: "http://vault:8200"
# token: "${VAULT_TOKEN}"
# transit-engine-path: "transit"
# kms: # 云KMS配置(当provider=kms时使用)
# provider: "aws" # aws | aliyun | tencent
# endpoint: "https://kms.example.com"

# 备份码配置
backup-code:
count: 10 # 备份码数量
length: 8 # 备份码长度
hash-algorithm: BCrypt # 哈希算法

# 可信设备配置
trusted-device:
enabled: true # 启用可信设备
max-devices: 10 # 最大设备数
default-trust-days: 30 # 默认信任天数

# 防重放攻击配置
replay-prevention-ttl-multiplier: 3 # TTL倍数(TTL = 时间窗口 * 倍数)

# 账户锁定配置
max-attempts: 5 # 最大失败次数
lock-duration-seconds: 300 # 锁定时长(秒)

gateway:
tenant:
enable: false # 如果MFA未显式配置enable-tenant,会自动读取此配置

注意:

  • 配置统一在Nacos配置中心管理,网关服务和通用服务共享同一份配置
  • 两个模块的 enable-tenant 配置必须保持一致
  • 如果未显式配置 enable-tenant,MFA组件会自动读取 platform.gateway.tenant.enable 配置
  • 缓存配置使用平台统一的 rydeen-component-cache 配置,无需在MFA组件中单独配置

管理服务API(rydeen-general-service)​

部署位置:rydeen-general-service

职责:处理所有MFA管理操作,包括绑定、解绑、状态查询、可信设备管理等

完整API文档:详细接口定义、请求/响应示例及数据模型可参考:MFA 管理接口 - platform

绑定MFA设备​

POST /api/mfa/bind
Content-Type: application/json

{
"userId": "user123",
"deviceType": "TOTP",
"tenantId": "tenant001"
}

Response 200:
{
"code": 0,
"msg": "success",
"data": {
"qrCodeUrl": "otpauth://totp/Rydeen:user123?secret=JBSWY3DPEHPK3PXP&issuer=Rydeen",
"secretKey": "JBSWY3DPEHPK3PXP",
"backupCodes": ["12345678", "87654321", ...],
"expiresIn": 600
}
}

处理流程:

  1. 生成密钥(使用SecretKeyManager,存储到KMS/Redis)
  2. 生成备份码(使用BackupCodeManager,BCrypt哈希)
  3. 生成二维码(使用QrCodeManager)
  4. 保存到数据库(事务,状态为NOT_ACTIVATED)
  5. 同步到GlobalCache(分布式锁)
  6. 发布审计事件(BIND操作)
  7. 返回结果

激活MFA设备​

POST /api/mfa/activate
Content-Type: application/json

{
"userId": "user123",
"code": "123456",
"deviceId": "device-fingerprint",
"deviceName": "iPhone 14 Pro",
"deviceFingerprint": "sha256:...",
"trustDevice": true,
"tenantId": "tenant001"
}

Response 200:
{
"code": 0,
"msg": "success"
}

处理流程:

  1. 从数据库查询用户MFA信息
  2. 从KMS检索密钥(使用SecretKeyManager)
  3. 验证TOTP验证码(使用TotpValidationEngine)
  4. 更新状态为已启用(ENABLED)
  5. 同步到GlobalCache
  6. 如果用户选择信任设备,注册为可信设备(使用TrustedDeviceManager)
  7. 发布审计事件(ACTIVATE操作)

解绑MFA设备​

POST /api/mfa/unbind
Content-Type: application/json

{
"userId": "user123",
"tenantId": "tenant001"
}

Response 200:
{
"code": 0,
"msg": "success"
}

处理流程:

  1. 从数据库查询用户MFA信息
  2. 逻辑删除(使用MyBatis-Plus的deleteById)
  3. 从密钥管理器删除密钥(使用SecretKeyManager)
  4. 从GlobalCache删除
  5. 发布审计事件(UNBIND操作)

查询MFA状态​

GET /api/mfa/status?userId=user123&tenantId=tenant001

Response 200:
{
"code": 0,
"msg": "success",
"data": {
"userId": "user123",
"status": "ENABLED",
"deviceType": "TOTP",
"bindTime": "2024-01-15T10:30:00Z",
"lastUsedTime": "2024-01-15T14:20:00Z",
"trustedDevices": 2,
"backupCodesRemaining": 8
}
}

可信设备管理​

查询可信设备列表​
GET /api/mfa/trusted-devices?userId=user123&tenantId=tenant001&currentDeviceId=device-fingerprint

Response 200:
{
"code": 0,
"msg": "success",
"data": {
"devices": [
{
"deviceId": "abc123def456...",
"deviceName": "iPhone 14 Pro",
"trustedUntil": "2024-02-15T10:30:00Z",
"lastUsedTime": "2024-01-20T14:20:00Z",
"createdAt": "2024-01-15T10:30:00Z",
"isPrimary": true
}
],
"total": 1,
"maxDevices": 10,
"currentDeviceIsPrimary": true
}
}
撤销可信设备​
DELETE /api/mfa/trusted-devices/{deviceId}?userId=user123&tenantId=tenant001&currentDeviceId=device-fingerprint

Response 200:
{
"code": 0,
"msg": "success"
}
撤销所有可信设备​
DELETE /api/mfa/trusted-devices/all?userId=user123&tenantId=tenant001&currentDeviceId=device-fingerprint

Response 200:
{
"code": 0,
"msg": "success",
"data": 2 # 撤销的设备数量
}
设为主管理设备​
PUT /api/mfa/trusted-devices/{deviceId}/primary
Content-Type: application/json

{
"userId": "user123",
"currentDeviceId": "device-fingerprint",
"tenantId": "tenant001"
}

Response 200:
{
"code": 0,
"msg": "success"
}

功能说明:

  • 仅当前主设备可将另一台设备设为主设备
  • 主设备可移除其他可信设备,非主设备仅可查看
  • 主设备用于管理其他设备的信任状态

验证并消耗备份码​

POST /api/mfa/backup-codes/verify
Content-Type: application/json

{
"userId": "user123",
"code": "12345678",
"tenantId": "tenant001"
}

Response 200:
{
"code": 0,
"msg": "success"
}

功能说明:

  • 用户输入8位备份码时调用
  • 验证通过则从列表中移除该码(一次性使用)
  • 备份码验证失败时发布审计事件(VERIFY操作,authMethod=BACKUP_CODE)

网关验证API(rydeen-gateway-service)​

部署位置:rydeen-gateway-service

职责:MFA验证逻辑,只读GlobalCache,零数据库依赖

特点:

  • 网关验证层不提供RESTful API
  • 只通过过滤器拦截登录请求
  • 只读GlobalCache(rydeen-component-cache),不操作数据库

验证流程​

网关验证层通过 IssueTokensFilter 过滤器拦截登录请求,流程如下:

  1. 业务服务登录:业务服务(如 rydeen-general-service)处理登录请求
  2. 检查MFA状态:业务服务调用 MfaBindManager.checkLoginMfa() 检查是否需要MFA
    • 如果设备已信任且未过期,返回 accessToken,跳过MFA验证
    • 如果设备未信任或已绑定MFA,返回 mfaRequired=true,不返回 accessToken
  3. 网关过滤器拦截:IssueTokensFilter 检查业务响应中是否包含 accessToken
    • 如果包含 accessToken:直接放行,不进行MFA验证
    • 如果不包含 accessToken:调用 MfaValidationService.checkMfaStatus() 检查MFA状态
  4. MFA状态检查:网关层从GlobalCache读取MFA元数据,返回前端展示用的信息
  5. MFA验证:用户输入验证码后,网关层调用 MfaValidationService.verifyMfaCode() 验证
    • 验证成功:发布审计事件(VERIFY操作,result=SUCCESS)
    • 验证失败:发布审计事件(VERIFY操作,result=FAILED)
    • 账户锁定:发布审计事件(VERIFY操作,result=BLOCKED)

关键方法:

  • MfaBindManager.checkLoginMfa():业务服务层方法,检查是否需要MFA(考虑可信设备)
  • MfaValidationService.checkMfaStatus():网关层方法,只读缓存返回MFA元数据
  • MfaValidationService.verifyMfaCode():网关层方法,验证MFA码(TOTP/备份码)

注意:

  • 网关验证层不进行设备信任检查,设备信任检查由业务登录层(MfaBindManager.checkLoginMfa())完成
  • 网关验证层不降级到数据库,缓存不可用时快速失败,保持网关轻量化

审计事件监听​

MFA组件通过 Spring ApplicationEventPublisher 发布审计事件,业务系统通过 @EventListener 监听并自行决定如何处理(持久化、签名、归档等)。

事件模型​

MFA组件定义 MfaAuditEvent 事件类,继承 ApplicationEvent:

位置:cn.com.rydeen.component.mfa.core.event.MfaAuditEvent

字段说明:

字段类型必填说明
tenantIdString否租户ID(如果未启用租户则为 null)
userIdString是用户ID(业务系统User表的主键ID)
operationTypeMfaOperationTypeEnum是操作类型枚举(BIND/UNBIND/VERIFY/ACTIVATE/DISABLE)
authMethodString是认证方式(TOTP/HOTP/SMS/EMAIL/BACKUP_CODE)
ipAddressString否IP地址(自动从HTTP请求提取)
userAgentString否用户代理(自动从HTTP请求提取)
deviceIdString否设备ID(设备指纹)
resultString是操作结果(SUCCESS/FAILED/BLOCKED)
errorCodeString否错误码(失败时)
errorMessageString否错误消息(失败时)
durationMsLong否操作耗时(毫秒)
timestampLocalDateTime是事件时间戳(自动生成)

操作类型枚举(MfaOperationTypeEnum):

枚举值编码描述使用场景
BIND"BIND"绑定设备用户绑定 MFA 设备时
UNBIND"UNBIND"解绑设备用户解绑 MFA 设备时
VERIFY"VERIFY"验证码验证用户验证 TOTP 码或备份码时
ACTIVATE"ACTIVATE"激活设备用户激活 MFA 设备时
DISABLE"DISABLE"禁用设备管理员禁用用户 MFA 时(暂未实现)

事件发布时机​

MFA组件在以下关键操作点自动发布审计事件:

1. 绑定设备(BIND)

  • 位置:MfaBindManager.bindDevice()
  • 时机:绑定成功后
  • 事件内容:operationType=BIND, result=SUCCESS

2. 激活设备(ACTIVATE)

  • 位置:MfaBindManager.activateDevice()
  • 时机:
    • 激活成功:result=SUCCESS
    • 激活失败(验证码错误):result=FAILED, errorCode=MFA_CODE_INVALID
    • 激活失败(用户不存在):result=FAILED, errorCode=MFA_NOT_BOUND

3. 解绑设备(UNBIND)

  • 位置:MfaBindManager.unbindDevice()
  • 时机:
    • 解绑成功:result=SUCCESS
    • 解绑失败(用户不存在):result=FAILED, errorCode=MFA_NOT_BOUND

4. 验证 MFA 码(VERIFY)

  • 位置:
    • MfaBindManager.verifyMfaCode()(备份码验证)
    • MfaValidationServiceImpl.verifyMfaCode()(TOTP 验证)
  • 时机:
    • 验证成功:result=SUCCESS, authMethod=TOTP 或 BACKUP_CODE
    • 验证失败(验证码错误):result=FAILED, errorCode=MFA_CODE_INVALID
    • 验证失败(用户未绑定):result=FAILED, errorCode=MFA_NOT_BOUND
    • 验证失败(MFA 未启用):result=FAILED, errorCode=MFA_NOT_ENABLED
    • 账户锁定:result=BLOCKED, errorCode=ACCOUNT_LOCKED
    • 重放攻击:result=FAILED, errorCode=MFA_CODE_USED
    • 验证异常:result=FAILED, errorCode=MFA_VERIFY_ERROR

事件发布实现​

MFA 组件内部使用 MfaAuditEventPublisher 工具类封装事件发布逻辑:

工具类位置:cn.com.rydeen.component.mfa.management.util.MfaAuditEventPublisher

核心功能:

  • 自动提取 IP 地址和 User-Agent(从 HTTP 请求上下文)
  • 提供简化方法:publishSuccess()、publishFailure()、publishBlocked()
  • 异常处理:事件发布失败不影响主流程

业务系统监听与处理​

业务系统通过 @EventListener 监听 MFA 审计事件,自行决定处理方式:

基础监听器示例:

package com.example.business.mfa.listener;

import cn.com.rydeen.component.mfa.core.event.MfaAuditEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
* MFA 审计事件监听器
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MfaAuditLogListener {

@EventListener
@Async("mfaAuditExecutor") // 使用自定义线程池(可选)
public void handleMfaAuditEvent(MfaAuditEvent event) {
try {
log.info("收到MFA审计事件: operationType={}, userId={}, result={}",
event.getOperationType() != null ? event.getOperationType().getCode() : null,
event.getUserId(),
event.getResult());

// 业务系统自行决定如何处理(见下方选项)
processAuditEvent(event);

} catch (Exception e) {
log.error("处理MFA审计事件失败", e);
}
}

private void processAuditEvent(MfaAuditEvent event) {
// 业务系统自行实现处理逻辑
// 选项1:持久化到数据库
// 选项2:数字签名后持久化
// 选项3:发送到消息队列
// 选项4:发送到归档服务
// 选项5:告警通知
// 选项6:统计分析
// 选项7:组合处理
}
}

配置异步线程池(可选):

方式1:虚拟线程(推荐,Java 21+)

虚拟线程适合IO密集型任务(如数据库写入、日志记录),可以创建数百万个线程,在IO阻塞时自动释放平台线程,提高资源利用率:

@Configuration
@EnableAsync
public class AsyncConfig {

@Bean("mfaAuditExecutor")
public Executor mfaAuditExecutor() {
// 使用虚拟线程执行器(Java 21+)
// 优势:轻量级、高并发、自动调度、适合IO密集型任务
return Executors.newVirtualThreadPerTaskExecutor();
}
}

方式2:传统线程池(Java 21以下版本)

如果使用 Java 21 以下版本,可以使用传统线程池:

@Configuration
@EnableAsync
public class AsyncConfig {

@Bean("mfaAuditExecutor")
public Executor mfaAuditExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(1000);
executor.setThreadNamePrefix("mfa-audit-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}

虚拟线程优势:

  • ✅ 轻量级:可以创建数百万个虚拟线程,不受平台线程数量限制
  • ✅ I/O 优化:在 I/O 阻塞时自动释放平台线程,提高资源利用率
  • ✅ 简化管理:无需手动管理线程池大小,由 JVM 自动调度
  • ✅ 适合审计场景:审计事件处理通常是IO密集型操作(数据库写入、日志记录等),非常适合使用虚拟线程

处理选项:

业务系统可根据需求选择以下处理方式:

  • ✅ 持久化到数据库:业务系统可自行创建 mfa_operation_log 表并持久化(MFA 组件不提供表结构)
  • ✅ 数字签名:使用 RSA 私钥签名,确保日志完整性
  • ✅ 归档处理:定期归档到对象存储(S3/OSS)
  • ✅ 告警通知:异常事件发送告警
  • ✅ 统计分析:聚合分析、报表生成
  • ✅ 不处理:如果业务系统不需要审计日志,可以不监听事件

数据库表结构建议:

CREATE TABLE mfa_operation_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_id VARCHAR(64),
user_id VARCHAR(64) NOT NULL,
operation_type VARCHAR(20) NOT NULL COMMENT '操作类型:BIND/UNBIND/VERIFY/ACTIVATE/DISABLE',
auth_method VARCHAR(20) COMMENT '认证方式:TOTP/HOTP/SMS/EMAIL/BACKUP_CODE',
ip_address VARCHAR(45) COMMENT 'IP地址',
user_agent VARCHAR(500) COMMENT '用户代理',
device_id VARCHAR(128) COMMENT '设备ID',
result VARCHAR(20) NOT NULL COMMENT '操作结果:SUCCESS/FAILED/BLOCKED',
error_code VARCHAR(50) COMMENT '错误码',
error_message VARCHAR(500) COMMENT '错误消息',
duration_ms BIGINT COMMENT '操作耗时(毫秒)',
signature VARCHAR(512) COMMENT '数字签名(可选)',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

INDEX idx_user_id (user_id),
INDEX idx_tenant_user (tenant_id, user_id),
INDEX idx_operation_type (operation_type),
INDEX idx_result (result),
INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MFA操作审计日志表';

完整集成示例:

步骤1:创建实体类

package com.example.business.mfa.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("mfa_operation_log")
public class MfaOperationLog {
@TableId(type = IdType.AUTO)
private Long id;
private String tenantId;
private String userId;
private String operationType;
private String authMethod;
private String ipAddress;
private String userAgent;
private String deviceId;
private String result;
private String errorCode;
private String errorMessage;
private Long durationMs;
private String signature;
private LocalDateTime createdAt;
}

步骤2:创建 Mapper

package com.example.business.mfa.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.business.mfa.entity.MfaOperationLog;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface MfaOperationLogMapper extends BaseMapper<MfaOperationLog> {
}

步骤3:创建监听器

package com.example.business.mfa.listener;

import cn.com.rydeen.component.mfa.core.event.MfaAuditEvent;
import com.example.business.mfa.entity.MfaOperationLog;
import com.example.business.mfa.mapper.MfaOperationLogMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class MfaAuditLogListener {
private final MfaOperationLogMapper logMapper;

@EventListener
@Async("mfaAuditExecutor")
public void handleMfaAuditEvent(MfaAuditEvent event) {
try {
// 转换为实体对象
MfaOperationLog entity = convertToEntity(event);

// 持久化到数据库
logMapper.insert(entity);

log.debug("MFA审计日志已保存: operationType={}, userId={}, result={}",
event.getOperationType() != null ? event.getOperationType().getCode() : null,
event.getUserId(),
event.getResult());

} catch (Exception e) {
log.error("保存MFA审计日志失败: operationType={}, userId={}",
event.getOperationType() != null ? event.getOperationType().getCode() : null,
event.getUserId(), e);
}
}

private MfaOperationLog convertToEntity(MfaAuditEvent event) {
MfaOperationLog entity = new MfaOperationLog();
entity.setTenantId(event.getTenantId());
entity.setUserId(event.getUserId());
entity.setOperationType(event.getOperationType() != null
? event.getOperationType().getCode() : null);
entity.setAuthMethod(event.getAuthMethod());
entity.setIpAddress(event.getIpAddress());
entity.setUserAgent(event.getUserAgent());
entity.setDeviceId(event.getDeviceId());
entity.setResult(event.getResult());
entity.setErrorCode(event.getErrorCode());
entity.setErrorMessage(event.getErrorMessage());
entity.setDurationMs(event.getDurationMs());
entity.setCreatedAt(event.getTimestamp());
return entity;
}
}

步骤4:配置异步线程池(已在上面配置,此处略)

步骤5:创建数据库表(已在上面提供,此处略)

更多处理选项示例:

选项1:持久化到数据库(见上方完整示例)

选项2:数字签名后持久化

@Component
@RequiredArgsConstructor
public class MfaAuditLogListener {
private final MfaOperationLogMapper logMapper;
private final SignatureService signatureService; // 业务系统自行实现

@EventListener
@Async
public void handleMfaAuditEvent(MfaAuditEvent event) {
MfaOperationLog entity = convertToEntity(event);

// 生成数字签名(使用 RSA 私钥)
String signature = signatureService.sign(event);
entity.setSignature(signature);

// 持久化
logMapper.insert(entity);
}
}

选项3:发送到消息队列

@Component
@RequiredArgsConstructor
public class MfaAuditLogListener {
private final MessageService messageService; // 业务系统自行实现

@EventListener
@Async
public void handleMfaAuditEvent(MfaAuditEvent event) {
// 发送到消息队列(异步处理、归档等)
messageService.send("mfa-audit-topic", event);
}
}

选项4:发送到归档服务

@Component
@RequiredArgsConstructor
public class MfaAuditLogListener {
private final ArchiveService archiveService; // 业务系统自行实现

@EventListener
@Async
public void handleMfaAuditEvent(MfaAuditEvent event) {
// 归档到对象存储(S3/OSS)
archiveService.archive("mfa-audit", event);
}
}

选项5:告警通知

@Component
@RequiredArgsConstructor
public class MfaAuditLogListener {
private final AlertService alertService; // 业务系统自行实现

@EventListener
@Async
public void handleMfaAuditEvent(MfaAuditEvent event) {
// 高风险事件发送告警
if ("BLOCKED".equals(event.getResult())
|| "MFA_CODE_USED".equals(event.getErrorCode())) {
alertService.sendAlert("MFA高风险事件", event);
}
}
}

选项6:统计分析

@Component
@RequiredArgsConstructor
public class MfaAuditLogListener {
private final StatisticsService statisticsService; // 业务系统自行实现

@EventListener
@Async
public void handleMfaAuditEvent(MfaAuditEvent event) {
// 实时统计(成功率、失败率等)
statisticsService.recordMfaEvent(event);
}
}

选项7:组合处理

@Component
@RequiredArgsConstructor
public class MfaAuditLogListener {
private final MfaOperationLogMapper logMapper;
private final SignatureService signatureService;
private final AlertService alertService;
private final StatisticsService statisticsService;

@EventListener
@Async
public void handleMfaAuditEvent(MfaAuditEvent event) {
// 1. 持久化到数据库
MfaOperationLog entity = convertToEntity(event);
String signature = signatureService.sign(event);
entity.setSignature(signature);
logMapper.insert(entity);

// 2. 高风险事件告警
if ("BLOCKED".equals(event.getResult())
|| "MFA_CODE_USED".equals(event.getErrorCode())) {
alertService.sendAlert("MFA高风险事件", event);
}

// 3. 实时统计
statisticsService.recordMfaEvent(event);
}
}

最佳实践:

1. 异步处理

  • ✅ 使用 @Async 异步处理事件,避免阻塞主流程
  • ✅ 配置专用线程池,避免影响其他异步任务

2. 异常处理

  • ✅ 事件处理失败不应影响主流程,只记录错误日志
  • ✅ 使用 try-catch 包裹处理逻辑

3. 性能优化

  • ✅ 批量处理:对于高并发场景,可批量收集事件后批量处理
  • ✅ 异步队列:使用消息队列缓冲,避免事件丢失

4. 数据完整性

  • ✅ 数字签名:对重要审计日志进行数字签名,防止篡改
  • ✅ 定期归档:定期归档历史日志到对象存储

5. 监控告警

  • ✅ 监控事件处理延迟和失败率
  • ✅ 高风险事件(账户锁定、重放攻击)及时告警

优势:

  • ✅ 组件轻量化:MFA 组件不依赖数据库,只发布事件,保持组件独立性
  • ✅ 业务灵活性:业务系统可自行决定是否启用审计、如何处理
  • ✅ 无中间件依赖:使用 Spring 内置事件机制,无需 Redis Stream、MQ 等外部中间件
  • ✅ 解耦设计:MFA 组件与审计实现完全解耦,业务系统可自由选择处理方式
  • ✅ 非阻塞发布:事件发布采用异步模式,不阻塞主业务流程

设计原则​

独立性与解耦​

MFA组件完全独立于业务系统的User表结构:

  • ✅ 只关联User表主键ID:MFA组件只需要业务系统User表的主键ID(userId),不维护任何用户信息
  • ✅ 不依赖User表结构:不同业务系统的User表结构可以完全不同,MFA组件都能适配
  • ✅ 零侵入集成:业务系统无需为MFA修改已有的User模块代码
  • ✅ 灵活适配:支持String或Long类型的User表主键(统一转换为String存储)

租户功能可选​

MFA组件支持可配置的租户功能,并自动读取网关配置:

  • ✅ 租户可选:通过 enableTenant 配置项控制是否启用租户功能(默认 false)
  • ✅ 自动读取网关配置:如果未显式配置,自动读取 platform.gateway.tenant.enable(推荐方式)
  • ✅ 无租户系统:如果系统没有租户概念,tenantId 可以为 null,数据库 tenant_id 字段为 NULL
  • ✅ 有租户系统:如果系统有租户概念,启用租户后 tenantId 必须提供
  • ⚠️ 配置一致性:两个模块(validation和management)的 enableTenant 配置必须保持一致

网关轻量化原则​

  • 零数据库依赖:网关验证层严格禁止操作数据库
  • 只读缓存:网关验证层只从GlobalCache(rydeen-component-cache)读取数据,不写入
  • 快速失败:缓存不可用时快速失败,不阻塞请求,不降级到数据库

核心特性​

🔐 多重认证方式​

  • TOTP认证:基于时间的一次性密码(Time-based One-Time Password),符合RFC 6238标准
  • HOTP认证:基于事件的一次性密码(HMAC-based One-Time Password),符合RFC 4226标准
  • 备份码:8位数字备份码,BCrypt哈希存储,一次性使用

🛡️ 安全保障​

  • 密钥加密存储:使用KMS/HSM管理主密钥,用户密钥AES-256-GCM加密,存储在KMS/Redis中,不存储在数据库
  • 防重放攻击:记录已使用验证码,TTL控制,防止重复使用
  • 审计日志完整性:通过Spring ApplicationEvent发布审计事件,业务系统可自行决定是否签名、归档
  • 防暴力破解:智能失败次数限制,达到阈值后账户锁定

⚡ 高性能设计​

  • GlobalCache缓存:热点数据高速访问(rydeen-component-cache),网关层毫秒级响应(<10ms)
  • 架构分离:验证与管理完全分离,网关零数据库依赖
  • 异步处理:审计事件发布采用异步模式,不阻塞主流程

🎯 易用性强​

  • Spring Boot Starter:一键集成,自动配置
  • RESTful API:标准化接口设计(管理模块)
  • 自动配置:开箱即用,Liquibase自动创建数据库表
  • 详细文档:完善的使用指南和API文档

相关标准​