SpringBoot实现OTP校验始终提示无效OTP报错如何解决
我开发了一套用户注册登录系统:用户先完成注册,再使用注册时填写的邮箱和密码登录,登录成功后系统会生成随机字符串作为OTP(一次性验证码)发送至用户邮箱,同时将该OTP存储到数据库中。目前需要实现OTP校验逻辑,比对用户输入的OTP与数据库中存储的OTP是否一致,确认用户输入正确性,但编写完对应逻辑后,每次校验都会提示OTP无效。
相关代码
OTPMailContoller 类
@PostMapping("/validate-otp") public SessionHandling validateOtp(@RequestBody SessionHandling otp) throws Exception{ String tempOtp = otp.getOneTimePassword(); SessionHandling UserOtp = null; if(tempOtp != null) { UserOtp = emailService.fetchUserByOneTimePassword(tempOtp); } if(UserOtp == null){ throw new Exception("invalid otp !!!"); } return UserOtp; }
Service 类
public SessionHandling fetchUserByOneTimePassword(String tempOtp) { return repo.findByOneTimePassword(tempOtp); }
Repository 类
@Query(value = "select * from session_handling where one_time_password= :otp", nativeQuery =true) public SessionHandling findByOneTimePassword(@Param("otp")String tempOtp);
Model 类
@Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String name; private String email; private String password; private String cpassword; private static final long OTP_VALID_DURATION = 5 * 60 * 1000; // 5分钟有效期 @Column(name = "one_time_password") private String oneTimePassword;
报错信息
Invalid otp !!!
每次都触发OTP无效报错,核心是findByOneTimePassword查询返回了null,按优先级排查以下问题:
OTP生成后未持久化到数据库
这是最高发的原因:生成OTP、给对象setOneTimePassword、发送邮件后,没有调用repo.save()方法把更新后的对象存入数据库,导致数据库中对应记录的one_time_password字段为null,查询时自然匹配不到结果。
修复:生成OTP后必须调用持久化方法存库,发送邮件的逻辑要放在存库成功之后。前端传参字段不匹配,后端拿不到OTP值
Spring Boot默认按驼峰命名规则解析JSON参数,如果前端传的参数字段是下划线命名one_time_password,而实体类字段是驼峰oneTimePassword,且没有配置全局下划线转驼峰、也没有加@JsonProperty("one_time_password")注解,otp.getOneTimePassword()拿到的就是null,根本不会执行数据库查询。
排查方式:在controller里加一行日志打印tempOtp的值,看是不是null。如果是null就调整参数映射规则。OTP存储/传入时存在格式不一致问题
检查生成OTP后存库时,是否做了多余的处理:比如全大/小写转换、拼接了多余字符、前后带空格/换行符;另外检查用户输入的OTP是否做了多余的trim处理,导致和库中存储的值不一致。现有逻辑的设计缺陷(修复完上述问题后必须调整)
- 不能只靠OTP单字段查询:必须带上当前用户的唯一标识(邮箱/用户ID)作为查询条件,避免OTP碰撞导致的越权问题,也能避免多个用户生成相同OTP时匹配到错误记录。
- 你在实体类里定义了5分钟的OTP有效期,但现有逻辑完全没做过期校验,需要给表加OTP生成时间字段,查询时同时判断OTP是否在有效期内,过期的OTP直接判定无效。
调整后的Repository查询示例:
@Query(value = "select * from session_handling where email = :email and one_time_password= :otp and otp_generate_time > :expireTime", nativeQuery =true) public SessionHandling findValidOtp(@Param("email")String email, @Param("otp")String otp, @Param("expireTime")LocalDateTime expireTime);
内容的提问来源于stack exchange,提问作者Harshit Toxia

