Python账户登出KeyError问题排查及自动登录实现咨询
问题1:登出时出现KeyError: 'firstname'的原因及修复
这个错误的核心是登出逻辑没有彻底清理登录相关的Session数据,同时Profile页面没有对Session键的存在做容错处理。
错误原因分析:
你当前的logout函数只移除了email这个Session键,但loggedin、firstname等键仍然留在Session中。当用户登出后,如果Session没有过期,再次访问网站时,根路由会因为loggedin仍在Session中而直接跳转到Profile页面。此时如果因为某种原因(比如Session部分失效、手动修改了Cookie)firstname键不存在,就会触发KeyError。
另外,你的Profile路由直接通过session['firstname']取值,没有做任何存在性检查,这也放大了出错的概率。
修复步骤:
完善登出函数,彻底清理Session:
修改logout函数,移除所有登录相关的Session键,同时为后续自动登录预留Cookie清除逻辑:@app.route('/logout') def logout(): # 移除所有登录状态相关的Session键,用pop(key, None)避免键不存在时报错 session.pop('loggedin', None) session.pop('id', None) session.pop('firstname', None) session.pop('email', None) # 清除自动登录的Cookie(后续实现自动登录时会用到) response = redirect(url_for('login')) response.set_cookie('remember_token', '', expires=0) print('Logout successfully!') return response给Profile页面加容错处理:
在Profile路由中,用session.get(key, 默认值)替代直接取值,避免键不存在时抛出错误:@app.route('/profile', methods=['GET']) def profile(): if request.method == 'GET' and 'loggedin' in session: cur = mysql.connection.cursor() cur.execute("SELECT firstname, lastname, email FROM users.data WHERE description = 'description'") account = cur.fetchall() description = account # 用get方法取值,设置默认值避免KeyError return render_template('profile.html', id=session.get('id'), firstname=session.get('firstname', 'Guest'), email=session.get('email'), description=description) # 如果未登录,直接重定向到登录页 return redirect(url_for('login'))
问题2:实现打开网站自动登录(记住登录状态)
要实现自动登录,我们需要通过长期Cookie存储加密的用户身份信息,在用户每次访问网站时,检查Cookie的有效性并自动恢复Session登录状态。以下是具体实现方案:
步骤1:准备依赖和配置
首先安装用于生成安全Token的库:
pip install itsdangerous
确保你的Flask App已经配置了安全的SECRET_KEY(用于加密Token,一定要用随机的强字符串,不要硬编码在代码里,建议用环境变量注入):
import os from flask import Flask app = Flask(__name__) # 上线时替换为环境变量,不要硬编码密钥 app.secret_key = os.environ.get('FLASK_SECRET_KEY') or 'your-temporary-strong-secret-key'
步骤2:在登录页面添加"记住我"复选框
修改login.html,增加一个复选框让用户选择是否记住登录状态:
<form method="POST"> <div> <label>邮箱:</label> <input type="email" name="email" required> </div> <div> <label>密码:</label> <input type="password" name="password" required> </div> <div> <label><input type="checkbox" name="remember_me"> 记住我(7天内自动登录)</label> </div> <button type="submit">登录</button> </form>
步骤3:修改登录逻辑,生成自动登录Token
在登录成功时,如果用户勾选了"记住我",生成一个加密的Token并存入Cookie:
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer @app.route('/', methods=['GET', 'POST']) def login(): if request.method == 'GET' and 'loggedin' in session: return redirect(url_for('profile')) elif request.method == 'POST': email = request.form['email'] password = request.form['password'] # 获取"记住我"复选框的状态 remember_me = request.form.get('remember_me') == 'on' cur = mysql.connection.cursor() cur.execute('SELECT * FROM users.data WHERE email = %s AND password = %s', (email, password)) account = cur.fetchone() if account: session['loggedin'] = True session['id'] = account[0] session['firstname'] = account[1] session['email'] = account[3] # 处理自动登录逻辑 if remember_me: # 创建序列化器,设置Token有效期为7天(604800秒) s = Serializer(app.secret_key, expires_in=604800) # 生成包含用户ID的加密Token token = s.dumps({'user_id': account[0]}).decode('utf-8') # 设置Cookie:httponly防止XSS攻击,secure在HTTPS环境下开启 response = redirect(url_for('profile')) response.set_cookie('remember_token', token, max_age=604800, httponly=True) return response return redirect(url_for('profile')) else: return render_template('error.html') return render_template('login.html')
步骤4:添加请求钩子,实现自动登录
使用Flask的before_request钩子,在每次请求前检查Cookie中的Token,自动恢复登录状态:
@app.before_request def auto_login(): # 如果已经登录,跳过检查 if 'loggedin' in session: return # 获取Cookie中的自动登录Token token = request.cookies.get('remember_token') if token: try: # 验证并解析Token s = Serializer(app.secret_key) data = s.loads(token) user_id = data['user_id'] # 从数据库获取用户信息 cur = mysql.connection.cursor() cur.execute('SELECT * FROM users.data WHERE id = %s', (user_id,)) account = cur.fetchone() if account: # 恢复Session登录状态 session['loggedin'] = True session['id'] = account[0] session['firstname'] = account[1] session['email'] = account[3] except: # Token无效或过期,清除Cookie并跳转到登录页 response = redirect(url_for('login')) response.set_cookie('remember_token', '', expires=0) return response
额外安全提示:
- 不要明文存储密码:当前代码中直接存储和比对明文密码,非常不安全。建议使用
werkzeug.security对密码进行哈希存储,示例:from werkzeug.security import generate_password_hash, check_password_hash # 注册时存储哈希密码 hashed_pw = generate_password_hash(password) # 登录时验证密码 if account and check_password_hash(account[2], password): # 假设account[2]是哈希密码字段 # 执行登录逻辑 - SECRET_KEY要保密:上线时一定要用随机生成的强字符串,通过环境变量注入,不要硬编码在代码中。
内容的提问来源于stack exchange,提问作者abby

