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
相关产品推荐
相关产品推荐

