@MFACheck 接入指南(其他微服务如何用)
任意依赖
hyper-util-server的微服务,在方法/类上加一个@MFACheck注解 即可获得 MFA 二次校验能力,无需写任何实现 。原理:
DefaultMfaStatusProvider已内置,一次查auth_user_basic_t的totp_enabled(已绑定)与need_totp(强制开关)两列(与 auth-server 完全一致)。
一、接入三步
1. 确认依赖(一般已有)
xml
<dependency>
<groupId>com.xxx.cloud.spring</groupId>
<artifactId>hyper-util-server</artifactId>
<version>2.1.4-SNAPSHOT</version>
</dependency>
2. 在敏感操作上加注解
java
import com.xxx.util.mfa.annotation.MFACheck;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/firmware")
public class FirmwareController {
/** 固件升级:每次都要 MFA(STRICT) */
@MFACheck(action = "FW_UPGRADE", level = MFACheck.Level.STRICT)
@PostMapping("/upgrade")
public Result<Void> upgrade(@RequestBody UpgradeDto dto) {
// ... 业务逻辑 ...
return ResultUtil.ok();
}
/** 批量配置:5 分钟窗口内验过一次即可(LIGHT) */
@MFACheck(action = "BATCH_CONFIG", level = MFACheck.Level.LIGHT, skipWindowSeconds = 300)
@PostMapping("/batchConfig")
public Result<Void> batchConfig(@RequestBody ConfigDto dto) {
// ... 业务逻辑 ...
return ResultUtil.ok();
}
}
也可标在类上,对该类所有方法生效。
3. 前端处理 -9000(二次验证闭环)
被拦截时,本服务自动返回(由 util 的 MfaExceptionHandler 生成,无需自己写异常处理):
json
{ "code": -9000, "message": "需要 MFA 验证", "data": { "mfaChallenge": true, "action": "FW_UPGRADE" } }
前端拿到后:
-
弹窗让用户输入 Authenticator 动态码;
-
调 auth-server 的统一二次校验接口(不是本服务):
POST {auth-server}/mfa/verifyAction Body: { "action": "FW_UPGRADE", "code": "123456" }通过后,auth-server 会把「该用户该 action 已验证」写入共享 Redis (
mfa:act:{uid}:{action})。 -
重放原请求 (再调一次本服务的
/firmware/upgrade)→ 切面读到共享 Redis 里已验证 → 放行。
-9001(强制 MFA 但用户未绑定:need_totp=1且totp_enabled!=1)→ 前端引导用户去 auth-server/mfa/init+/mfa/enable绑定。判定优先级:MFA 认证 > 管理员操作密码 。已绑定(
totp_enabled=1)走 MFA;未绑定且need_totp=1引导绑定;未绑定且need_totp=0放行,回退既有二次密码链。
二、你自动获得了什么(零代码)
引入 util-server 后,以下组件自动装配 (组件扫描 + MfaAutoConfiguration):
| 组件 | 作用 |
|---|---|
MFACheckAspect |
拦截 @MFACheck,做 MFA 判定 |
DefaultMfaStatusProvider |
默认查 auth_user_basic_t 的 totp_enabled + need_totp(@ConditionalOnMissingBean,可被覆盖) |
MfaExceptionHandler |
把 MFA 异常转成 code=-9000/-9001 + data.action |
MfaSessionManager |
共享 Redis 上的「窗口内已验证」状态(跨服务复用) |
MfaProperties |
mfa.* 配置绑定(多数用默认即可) |
三、前置条件(必须满足,否则默认实现会失效)
-
默认数据源能读到
auth_user_basic_t(和 auth-server 一样查这张表)。 -
@MapperScan覆盖com.xxx.util.*.mapper(否则AuthUserMfaMapper注入不到)。java@MapperScan(basePackages = {"com.xxx.*.mapper", "com.xxx.util.*.mapper"}) -
与 auth-server 共享同一个 Redis(二次验证的「已验证」状态要跨服务读到)。
四、可选配置(application.yml,不给就用默认)
yaml
mfa:
pending:
ttlSeconds: 300 # 两步登录凭证有效期(秒)
# 其余 totp/recovery/rateLimit 项主要给 auth-server 用,消费方一般不用配
五、特殊服务:查不到 auth_user_basic_t 时覆盖默认
若某服务(独立 DB / 不连 auth 库)无法直接查该表,自定义一个 MfaStatusProvider bean,MfaAutoConfiguration 的默认实现会自动让位:
java
import com.xxx.util.mfa.spi.MfaStatusProvider;
import com.xxx.util.mfa.spi.MfaUserStatus;
import org.springframework.stereotype.Component;
@Component
public class RemoteMfaStatusProvider implements MfaStatusProvider {
@Override
public MfaUserStatus getStatus(String uid) {
// 例如:Feign 回调 auth-server,或查本服务用户表
// 一次返回 totp_enabled(enabled) 与 need_totp(required),避免两次查库
boolean enabled = authFeignClient.isMfaEnabled(uid);
boolean required = authFeignClient.isMfaRequired(uid);
return new MfaUserStatus(enabled, required);
}
@Override
public boolean isMfaEnabled(String uid) {
return getStatus(uid).isEnabled();
}
}
六、依赖 auth-server 提供的端点(MFA 生命周期)
消费方不需要实现这些,由 auth-server 统一提供:
| 端点 | 用途 |
|---|---|
POST /mfa/init |
生成绑定二维码(首次绑定) |
| `POST /mfa/enable |