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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:10:54