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

