You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中TOTP MFA登录验证失败问题求助

TOTP验证码验证失败的排查与修复

使用dev.samstevens.totp库实现MFA认证时,注册流程正常、二维码可扫码生成验证码,但登录阶段验证码始终验证不通过,以下是问题分析与修复方案:

核心问题分析

1. 验证器参数与生成端不一致

在DefaultMFATokenManager的verifyTotp方法中,手动创建的CodeGenerator和CodeVerifier未明确指定与二维码生成时完全匹配的算法、位数、周期参数;同时类中已注入的CodeVerifier未被复用,导致配置逻辑割裂。

2. 时间窗口容错性不足

TOTP基于时间戳计算验证码,若服务器与用户设备时间偏差超过30秒(一个周期),默认配置下会直接验证失败。

3. 密钥一致性未验证

未确认数据库中存储的密钥与二维码传递的密钥是否完全一致,存在密钥保存错误的可能。

修复方案

1. 统一验证器配置,复用注入实例

修改DefaultMFATokenManager的verifyTotp方法,直接使用注入的CodeVerifier,确保验证逻辑与生成端参数一致:

@Override
public boolean verifyTotp(String code, String secret) {
    return codeVerifier.isValidCode(secret, code);
}

同时在Spring配置类中明确配置CodeVerifier的参数,与二维码生成逻辑对齐:

@Configuration
public class MfaConfig {
    @Bean
    public SecretGenerator secretGenerator() {
        return new DefaultSecretGenerator();
    }

    @Bean
    public QrGenerator qrGenerator() {
        return new ZxingPngQrGenerator();
    }

    @Bean
    public CodeVerifier codeVerifier() {
        TimeProvider timeProvider = new SystemTimeProvider();
        // 与二维码生成时的算法、位数完全一致
        CodeGenerator codeGenerator = new DefaultCodeGenerator(HashingAlgorithm.SHA1, 6);
        DefaultCodeVerifier verifier = new DefaultCodeVerifier(codeGenerator, timeProvider);
        // 允许前后各1个周期(共90秒)的时间偏差,增强容错性
        verifier.setAllowedTimeWindow(1);
        return verifier;
    }
}

2. 验证服务器时间同步

执行以下命令检查服务器时间:

date

确保服务器时间与标准时间偏差不超过10秒,若偏差过大,配置NTP服务同步时间。

3. 确认密钥一致性

在UserAuthService的mfaAccountSetup方法中添加日志,打印用户密钥:

System.out.println("用户密钥:" + user.getSecret());

将该密钥手动输入Google Authenticator,测试验证码是否能通过验证,排除二维码生成时的密钥传递错误。

4. 检查验证码输入格式

确保前端传入的验证码为纯6位数字字符串,无空格、换行或其他特殊字符,后端接收时未被截断或修改。

修复后的完整DefaultMFATokenManager示例

@Service("mfaTokenManager")
public class DefaultMFATokenManager implements MFATokenManager {

    private final SecretGenerator secretGenerator;
    private final QrGenerator qrGenerator;
    private final CodeVerifier codeVerifier;

    public DefaultMFATokenManager(SecretGenerator secretGenerator, QrGenerator qrGenerator, CodeVerifier codeVerifier) {
        this.secretGenerator = secretGenerator;
        this.qrGenerator = qrGenerator;
        this.codeVerifier = codeVerifier;
    }

    @Override
    public String generateSecretKey() {
        return secretGenerator.generate();
    }

    @Override
    public String getQRCode(String secret) throws QrGenerationException {
        QrData data = new QrData.Builder()
                .label("MFA")
                .secret(secret)
                .issuer("Daniel token")
                .algorithm(HashingAlgorithm.SHA1)
                .digits(6)
                .period(30)
                .build();
        return Utils.getDataUriForImage(
                qrGenerator.generate(data),
                qrGenerator.getImageMimeType()
        );
    }

    @Override
    public boolean verifyTotp(String code, String secret) {
        return codeVerifier.isValidCode(secret, code);
    }
}

内容的提问来源于stack exchange,提问作者S H

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 09:10:35