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

Flask验证JSON Token时verify_token函数触发异常无法获取id_user值

问题排查与修复方案

核心错误点

  1. verify_token查询参数错误:函数最后调用Users.query.get('id_user')时传入的是固定字符串'id_user',而非前面解析得到的变量user_id,即使token解析成功也会查询失败。
  2. itsdangerous序列化/反序列化逻辑不匹配:生成token时使用的是带过期时间的Serializer实例,验证token时未指定过期相关配置,会直接触发解析异常。另外如果使用的是itsdangerous 2.0+版本,原Serializer已迁移到itsdangerous.url_safe模块,且API有调整,也会触发异常。
  3. 裸except吞掉所有报错:现有代码没有捕获具体异常类型,无法定位是签名错误、过期、还是格式错误,直接返回None。
  4. 路由逻辑隐含错误:password_reset路由中判断表单验证时写的是if form.validate_on_submit:,validate_on_submit是方法需要加括号调用,否则判断恒为True;同时如果邮箱不存在返回的user为None,直接调用send_mail(user)会触发属性报错,最终也会走到verify_token的异常分支。
  5. reset_token路由逻辑错误:密码修改成功后跳转signup页不符合常规逻辑,且表单验证同样漏了方法括号。

修正后的代码

依赖适配说明

如果使用itsdangerous 2.x版本,首先调整导入语句:

# 旧版本导入可能是 from itsdangerous import Serializer,2.x版本替换为:
from itsdangerous import URLSafeTimedSerializer as Serializer

函数定义修正

def get_reset_token(user):
    serial = Serializer(app.config['SECRET_KEY'])
    # 过期时间放在dumps参数中,适配不同版本的API规则
    return serial.dumps({'id_user':user.id}, salt='password-reset-salt').decode('utf-8')

def verify_token(token, expires_in=900):
    serial = Serializer(app.config['SECRET_KEY'])
    try:
        # 加载时指定过期时间和和生成时一致的salt,提高安全性
        data = serial.loads(token, salt='password-reset-salt', max_age=expires_in)
        user_id = data['id_user']
    except Exception as e:
        # 调试阶段可以打印e定位问题,上线后移除
        print(f"Token验证失败: {str(e)}")
        return None
    return Users.query.get(user_id)

def send_mail(user):
    # 加非空判断避免传入None报错
    if not user:
        return
    token = get_reset_token(user)
    message = Message('Password Reset Request', recipients = [user.email], sender='noreply@gmail.com')
    message.body= f'''
To Reset your password, click the following link:

{url_for('reset_token', token = token, _external = True)}

If you did not send this email, please ignore this message.
'''
    mail.send(message)

路由定义修正

@app.route('/password_reset', methods = ['GET', 'POST'])
def password_reset():
    form = Password_request()
    if request.method == "POST":
        # 补充方法调用括号
        if form.validate_on_submit():
            user = Users.query.filter_by(email = form.email.data).first()
            send_mail(user)
            # 不管用户是否存在都返回相同提示,避免泄露邮箱注册状态
            flash('Check your email. Password change request has been sent')
            return redirect(url_for('login'))
    return render_template('password_reset.html', form = form)


@app.route('/password_reset/<token>', methods = ['GET', 'POST'])
def reset_token(token):
    user = verify_token(token)
    if user == None:
        flash('The token is invalid or expired')
        return redirect(url_for('password_reset'))

    form = Password_success()
    # 补充方法调用括号
    if form.validate_on_submit():
        hashed_password=generate_password_hash(form.password.data, method = 'sha256')
        user.password = hashed_password
        db.session.commit()
        flash('Your password has been updated!')
        # 调整为跳转到登录页
        return redirect(url_for('login'))
    return render_template('reset_password.html', form=form, token=token)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 19:54:04