基于Bottle与SQLite3的登录机制无法跳转问题排查及优化
问题排查与修复方案
首先,我仔细梳理了你的代码,发现几个核心问题导致登录成功后无法跳转到/index页面,同时还有不少安全和性能方面的隐患,下面逐一说明:
一、核心问题(导致跳转失败的原因)
1. checkAuth()函数逻辑完全混乱
这个函数是保护/index路由的关键,但它的代码顺序完全颠倒:
- 先执行SQL查询,却使用了还未定义的
loginName和password变量,直接触发NameError异常 - 查询逻辑错误:Cookie中只存储了用户名,你却试图用用户名+密码查询数据库(密码根本不在Cookie里)
- 冗余的数据库操作:查询操作不需要
conn.commit(),还重复调用了commit()和close()
当登录成功后,浏览器跳转到/index,但checkAuth()抛出异常,导致/index无法正常加载,浏览器可能停留在登录页或显示错误页面。
2. login()函数中的日志错误
log.info = ('not none')这行是致命的错误赋值:它把logging.Logger的info方法覆盖成了字符串,虽然这行本身不会直接阻止跳转,但会破坏后续的日志功能,而且代码逻辑冗余(多次调用commit()和close())。
3. SQL注入风险
使用字符串拼接构造SQL语句("%s" % (loginName,password)),如果用户输入的账号/密码包含引号,会导致SQL语法错误,甚至被注入攻击。
二、修复后的完整代码
下面是修复后的代码,我已经修正了所有核心问题:
#!/usr/bin/env python3 # -*- coding: UTF-8 -*- from bottle import Bottle, run, template, get, post, debug, static_file, request, redirect, response, HTTPResponse import time import random import string import logging import logging.handlers import sqlite3 # 日志配置 log = logging.getLogger('bottle') log.setLevel('INFO') h = logging.handlers.TimedRotatingFileHandler( 'logs/nlog', when='midnight', backupCount=9999) f = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s') h.setFormatter(f) log.addHandler(h) secretKey = "SDMDSIUDSFYODS&TTFS987f9ds7f8sd6DFOUFYWE&FY" app = Bottle() # 静态文件路由 @app.route('/static/:path#.+#', name='static') def static(path): return static_file(path, root='./static') # 认证检查函数(修复后) def checkAuth(): # 先从加密Cookie获取登录用户名 loginName = request.get_cookie("user", secret=secretKey) if not loginName: # 无有效Cookie,跳转登录页 return redirect('/login') # 记录访问日志(注意:不要记录敏感信息) log.info(f'{loginName} {request.method} {request.url} {request.environ.get("REMOTE_ADDR")}') # 验证用户是否存在于数据库 conn = sqlite3.connect('trainsdb2.db') c = conn.cursor() # 使用参数化查询避免SQL注入 c.execute('SELECT * FROM LoginData WHERE login = ?', (loginName,)) user = c.fetchone() conn.close() # 查询操作无需commit,直接关闭连接 if user is not None: return loginName else: # Cookie中的用户不存在,删除无效Cookie并跳转登录 response.delete_cookie("user") return redirect('/login') # 登录路由(修复后) @app.route('/login') @app.route('/login/') @app.route('/login', method='POST') def login(): loginName = request.forms.get('login_name', default='') password = request.forms.get('password', default='') # 记录登录尝试日志(不记录密码) log.info(f'Login attempt: {loginName} {request.method} {request.url} {request.environ.get("REMOTE_ADDR")}') if request.method == 'POST': conn = sqlite3.connect('trainsdb2.db') c = conn.cursor() # 参数化查询,彻底避免SQL注入 c.execute('SELECT * FROM LoginData WHERE login = ? AND password = ?', (loginName, password)) user = c.fetchone() conn.close() if user is not None: # 设置安全的加密Cookie,有效期1小时 response.set_cookie( "user", loginName, secret=secretKey, max_age=3600, httponly=True, # 防止XSS窃取Cookie samesite='Lax' # 防止CSRF攻击 ) log.info(f'User {loginName} logged in successfully') redirect('/index') return # GET请求或登录失败,返回登录模板 return template('login') # 首页路由(修复后) @app.route('/') @app.route('/index') @app.route('/index/') @app.route('/index/<message>') def index(message=''): auth_result = checkAuth() # 如果checkAuth返回的是重定向响应,直接返回 if isinstance(auth_result, HTTPResponse): return auth_result loginName = auth_result messDict = {'error': "Something went wrong", 'ok': "Everything is ok."} return template('index', message=messDict.get(message, ""), loginName=loginName) # 启动服务(开发环境可开启reloader) app.run(host='localhost', port=63700, reloader=False, debug=True)
三、关键优化建议
1. 密码必须加密存储
当前代码明文存储密码是严重的安全漏洞,建议使用bcrypt或passlib对密码进行哈希存储:
# 安装passlib:pip install passlib[bcrypt] from passlib.context import CryptContext pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") # 注册用户时存储哈希密码: hashed_password = pwd_context.hash(raw_password) # 登录验证时: if pwd_context.verify(input_password, stored_hashed_password): # 验证通过
2. 使用数据库连接池
每次请求创建新连接会影响性能,建议使用连接池(比如SQLAlchemy),或者对sqlite3设置check_same_thread=False(仅适用于单线程环境)。
3. 改用认证装饰器
将checkAuth改成装饰器,简化路由保护逻辑:
def require_auth(func): def wrapper(*args, **kwargs): loginName = request.get_cookie("user", secret=secretKey) if not loginName: return redirect('/login') conn = sqlite3.connect('trainsdb2.db') c = conn.cursor() c.execute('SELECT * FROM LoginData WHERE login = ?', (loginName,)) user = c.fetchone() conn.close() if not user: response.delete_cookie("user") return redirect('/login') kwargs['loginName'] = loginName return func(*args, **kwargs) return wrapper # 使用装饰器保护首页路由 @app.route('/index') @require_auth def index(loginName, message=''): messDict = {'error': "Something went wrong", 'ok': "Everything is ok."} return template('index', message=messDict.get(message, ""), loginName=loginName)
4. 生产环境配置
- 关闭
debug=True,避免暴露敏感错误信息 - 设置
server='gunicorn'或其他生产级服务器 - 开启HTTPS,并设置
secure=True在Cookie中
内容的提问来源于stack exchange,提问作者mkropkowski
相关产品推荐
相关产品推荐

