pyotp密钥复用问题:重置密码OTP验证过期异常及解决需求
问题解决方案
核心问题分析
你当前的问题根源在于:生成OTP时使用的随机secret没有持久化存储,重置密码时又重新生成了新的secret,导致TOTP验证时用了不同的密钥,自然会提示过期或无效。同时要实现每次请求接口都发新验证码,只需在每次请求时生成新的secret和验证码,覆盖用户旧的验证记录即可。
具体修改步骤
1. 更新VerificationCode模型
首先需要给VerificationCode模型添加一个secret字段,用于存储生成OTP时的密钥:
# 假设你的VerificationCode模型定义类似这样 class VerificationCode(Base): __tablename__ = "verification_codes" id = Column(Integer, primary_key=True, index=True) code = Column(String, nullable=False) secret = Column(String, nullable=False) # 新增该字段用于存储OTP密钥 user_id = Column(Integer, ForeignKey("users.id"), nullable=False) user = relationship("User", back_populates="code")
2. 修改生成OTP验证码的方法
每次请求时生成新的secret和验证码,直接覆盖用户原有的验证记录(不管旧验证码是否仍有效):
secret = pyotp.random_base32() totp = pyotp.TOTP(secret, interval=settings.verification_code_expire_time) verification_code = totp.now() if not user.code: # 用户无验证记录,创建新条目 user.code = VerificationCode(code=verification_code, secret=secret) else: # 覆盖旧的验证码和对应密钥 user.code.code = verification_code user.code.secret = secret await db.commit() return verification_code
3. 修改重置密码的方法
从数据库中取出对应验证码绑定的secret,用这个密钥来验证OTP的有效性:
query = select(VerificationCode).where(VerificationCode.code == reset_password_schema.verification_code) result = await db.execute(query) verification_code = result.scalar_one_or_none() if not verification_code: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="无效的验证码。") # 使用存储的密钥创建TOTP实例,而非生成新的随机密钥 totp = pyotp.TOTP(verification_code.secret, interval=settings.verification_code_expire_time) if not totp.verify(reset_password_schema.verification_code): raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="验证码已过期或无效。") # 执行密码更新操作 query = ( update(User).where(User.id == verification_code.user_id).values( password=reset_password_schema.new_password) ) await db.execute(query) await db.commit()
额外说明
- 每次请求发送验证码接口时,都会生成全新的
secret和验证码,直接覆盖用户旧的验证记录,满足“即使原验证码仍有效也发送新验证码”的需求。 - TOTP的验证逻辑依赖生成验证码时的
secret和当前时间窗口,使用同一密钥才能正确校验验证码是否在有效期内。
内容的提问来源于stack exchange,提问作者binbin
相关产品推荐
相关产品推荐

