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

Flask应用密码重置提交后跳转404问题求助

Flask密码重置提交后跳转404问题排查

我在Flask Web应用中实现了邮件重置密码功能,目前大部分流程正常:邮件可发送带token的链接,点击能进入新密码输入页面,但提交新密码后会跳转到自定义的404错误页面。怀疑问题出在reset_token函数中,该函数本应将更新后的密码提交至数据库并跳转至登录页,但未正常执行。

相关代码

邮件发送与路由逻辑

def send_reset_email(user):
    token = user.get_reset_token().decode('utf-8')  # Decode bytes to string
    print("Reset Token:", token)
    msg = Message('Password Reset Request', sender='noreply@demo.com', recipients=[user.email])
    msg.body = f'''
    To reset your password, visit the following link:
    {url_for('reset_token', token=token, _external=True)}

    If you did not make this request, please ignore this email and consider changing your email and password.
    '''
    mail.send(msg)

@app.route("/reset_password", methods=['GET', 'POST'])
def reset_request():
    if current_user.is_authenticated:
        return redirect(url_for('index'))
    form = RequestResetForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        send_reset_email(user)
        flash('Reset password email has been send', 'info')
        return redirect(url_for('signin'))
    return render_template('reset_request.html', title='Reset Password', form=form)

@app.route("/reset_password/<token>", methods=['GET', 'POST'])
def reset_token(token):
    print( url_for('signin') )
    print("Reset Token Route Triggered")
    if current_user.is_authenticated:
        print('redirecting you to index')
        return redirect(url_for('index'))
    user = User.verify_reset_token(token)
    if user is None:
        print('The user value was none')
        flash('That is an invalid or expired token', 'warning')
        return redirect(url_for('reset_request'))
    form = ResetPasswordForm()
    if form.validate_on_submit():
        hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
        user.password = hashed_password
        print(f"new password is {user.password}")
        db.session.commit()
        print("Password has been committed")
        flash(f'Your password has been updated', 'success')
        print("Redirecting you now to sign in")
        return redirect(url_for('signin'))
    return render_template('reset_token.html', title='Reset Password', form=form)

User模型

class User(db.Model, UserMixin):
    # Manual Table name choice
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    firstname = db.Column(db.String(100), nullable=False)
    lastname = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(100), unique=True, nullable=False, index=True)
    password = db.Column(db.String(130), nullable=False)

    def get_reset_token(self, expires_sec=1800):
        s = Serializer(app.config['SECRET_KEY'])
        return s.dumps({'user_id': self.id}).encode('utf-8')

    @staticmethod
    def verify_reset_token(token):
        s = Serializer(app.config['SECRET_KEY'])
        try:
            user_id = s.loads(token)['user_id']
        except:
            return None
        return User.query.get(user_id)

    def __init__(self,firstname,lastname,email,password):
        self.firstname = firstname
        self.lastname = lastname
        self.email = email
        self.password = password

表单定义

class RequestResetForm(FlaskForm):
    email = StringField('Email', validators=[InputRequired(), Email()])
    submit = SubmitField('Request Password Reset')

    def validate_email(self, email):
        user = User.query.filter_by(email=email.data).first()
        if user is None:
            raise ValidationError("There is no account with this email.")


class ResetPasswordForm(FlaskForm):
    password = PasswordField('Password', validators=[InputRequired(), Length(min=4, max=200)])
    confirm_password = PasswordField('Confirm Password', validators=[InputRequired(), Length(min=2, max=200), EqualTo('password')])
    submit = SubmitField('Reset Password')

排查方向与解决方案

  1. 检查模板表单配置

    • 确认reset_token.html模板中包含CSRF令牌:添加{{ form.hidden_tag() }},否则表单验证会失败,导致提交后重新渲染页面,若路由匹配异常则触发404。
    • 确保表单action属性为空(或指向当前路由),避免提交到错误路径。
  2. 验证目标路由存在性

    • 确认应用中存在名为signin的路由函数,检查@app.route装饰器的路径和函数名是否匹配url_for('signin')的调用。
  3. 调试表单验证失败原因

    • 在reset_token函数的form.validate_on_submit()判断前添加print(form.errors),查看具体验证错误(如密码长度不匹配、CSRF失效等)。注意表单中password的最小长度是4,而confirm_password是2,两者规则不一致,可能导致验证失败。
  4. 检查Token编码与传输

    • 替换Serializer为URLSafeSerializer,避免token中的特殊字符(如+、/)在URL传输中被转义,导致验证失败:
      from itsdangerous import URLSafeSerializer
      
      def get_reset_token(self, expires_sec=1800):
          s = URLSafeSerializer(app.config['SECRET_KEY'])
          return s.dumps({'user_id': self.id})
      
  5. 添加数据库异常处理

    • 在db.session.commit()处添加异常捕获,避免数据库错误导致流程中断:
      try:
          db.session.commit()
          print("Password has been committed")
      except Exception as e:
          db.session.rollback()
          print(f"Database error: {e}")
          flash('Failed to update password, please try again', 'danger')
      

内容的提问来源于stack exchange,提问作者Naeim Salib

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 10:20:03