前面我们了解了🎋 Flask框架的背景与特征、组成等,并通过过*++一个简单的Flask API实例++* 了解到框架的工作逻辑 --- --- "接收、匹配、处理、返回"。
实例中的业务逻辑:任何人向我们的网站发送POST请求,都会给返回一个相应的sign签名
这类似于提供了一个公共服务,那如果我们只想给已授权用户提供服务,该怎么做呢?
1 基于文件授权
显然,我们需要做一个校验。我们将已授权用户信息存储在一个文件db.txt当中:

我们基于之前的案例进行修改:
在视图函数当中将文件数读取到内存,通过 user_dict存储信息,指明必须携带参数'token=......',之后验证token对应代码是否对应已授权用户,从而达到验证功能。
python
import hashlib
import json
from flask import Flask, request, jsonify
# 创建按一个Flask实例对象
app = Flask(__name__)
# 获取用户信息-字典
def get_user_dict():
info_dict = {}
with open('db.txt', mode='r', encoding='utf-8') as f:
for line in f:
line = line.strip()
token, name = line.split(',')
info_dict[token] = name
return info_dict
@app.route("/bili", methods=["POST"])
def bili():
'''
请求的URL需要携带: /bili?token=123456789
请求的数据格式要求: {"ordered_string": "......"}
:return:
:return:
'''
# 是否携带参数
token = request.args.get('token')
if not token:
return jsonify({"status": False, "error": "认证失败"})
# 是否对应授权
user_ditc = get_user_dict()
print(user_ditc)
if token not in user_ditc:
return jsonify({"status": False, "error": "认证失败"})
# 核心算法处理
ordered_string = request.json.get('ordered_string')
if not ordered_string:
return jsonify({"status":False, "error":"参数错误1"})
# 调用算法,处理接收到的数据
encrypt_string = ordered_string + "123456789"
obj = hashlib.md5(encrypt_string.encode('utf-8'))
sign = obj.hexdigest()
return jsonify({"status": True, 'data':sign})
if __name__ == '__main__':
app.run()
// 运行效果:
case1:没有携带token参数:

case2:携带正确token参数

这里是一个简单校验,也可以考虑绑定IP、手机验证码登录、人脸识别等更为复杂安全的校验。
2 基于数据库授权
相比于.txt文件,数据库操作往往更加方便、简洁、规范。我们将用户信息放在MySQL数据库中,让Flask程序去连接MySQL数据进行授权和校验。
管理员直接用图形化界面连接MySQL开账户和授权。
(主包使用的是数据库图形化工具是Navicat*,一套功能强大的**数据库管理和开发工具****)*

2.1 导入 pymysql 包
📫 PyMySQL 是一个纯 Python 编写的 MySQL 客户端库。它的作用是让 Python 程序能够遵循 MySQL 的协议,与数据库服务器进行通信。
python
pip install pymysql
PyMySQL 的核心工作流程
**建立连接:**通过pymsql.connect连接本地数据库(127.0.0.1),指定配置信息(用户名、密码等)
执行查询:
- 游标(Cursor):用于执行SQL并获取结果的"指针";
- 参数化查询:cursor.execute(sql,[params, ])将参数传入-注意:这里使用了一列表包装[params, ],这是为了防止SQL注入,是一种安全的写法。
- 获取结果:fetchone-只从查询结果取出第一行数据,如果没有匹配项,返回None
资源释放与返回
2.2 代码实现:访问API,连接MySQL数据库进行凭证的校验
python
import hashlib
import json
from flask import Flask, request, jsonify
import pymysql
# 创建按一个Flask实例对象
app = Flask(__name__)
#连接数据库读取数据
def fetch_one(sql, params):
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='123456', charset='utf8',
db='flask-learning')
cursor = conn.cursor()
# cursor.execute(sql, [params, ])
# result = cursor.fetchone()
# cursor.close()
# conn.close()
# return result
'''如果 execute 报错,conn.close() 可能不会被执行。建议使用上下文管理器,即使出错也能自动关闭连接。离开 with 块后游标会自动关闭'''
with conn.cursor() as cursor:
cursor.execute(sql, [params])
result = cursor.fetchone()
@app.route("/bili", methods=["POST"])
def bili():
'''
请求的URL需要携带: /bili?token=123456789
请求的数据格式要求: {"ordered_string": "......"}
:return:
:return:
'''
# 1.token是否为空
token = request.args.get('token')
if not token:
return jsonify({"status": False, "error": "认证失败"})
# 2.token是否合法,连接MySQL执行命令
result = fetch_one('select * from user where token=%s', [token, ])
if not result:
return jsonify({"status": False, "error": "认证失败"})
print(result)
# 核心算法处理
ordered_string = request.json.get('ordered_string')
if not ordered_string:
return jsonify({"status":False, "error":"参数错误1"})
# 调用算法,处理接收到的数据
encrypt_string = ordered_string + "123456789"
obj = hashlib.md5(encrypt_string.encode('utf-8'))
sign = obj.hexdigest()
return jsonify({"status": True, 'data':sign})
if __name__ == '__main__':
app.run()
// 运行效果:

讨论:SQL注入问题
SQL注入(SQL Injection)
这是一个非常经典且重要的问题,下面从"黑客视角"和"数据库视角"理解:
(1)正常场景
数据库执行这条指令,老老实实地返回了 ID 为 1 的用户信息。一切看起来很正常。
pythonuser_id = "1" # 假设这是用户从前端输入框传来的 sql = "SELECT * FROM users WHERE id = " + user_id # 最终生成的 SQL:SELECT * FROM users WHERE id = 1(2)攻击场景:SQL 注入
如果用户不是老老实实输入数字,而是在输入框里输入了:
1 OR 1=1
pythonuser_id = "1 OR 1=1" sql = "SELECT * FROM users WHERE id = " + user_id # 最终生成的 SQL:SELECT * FROM users WHERE id = 1 OR 1=1🔨🔨🔨 这很危险!
数据库在执行
WHERE id = 1 OR 1=1时,会对表中的每一行进行判断。虽然某一行的
id可能不是 1,但因为后面跟着OR 1=1,整个判断条件依然成立。结果 :数据库会毫无保留地把
users表里的所有用户信息全部返回。(3)更严重的后果
如果用户输入的不是
OR 1=1,而是更狠的指令呢?比如输入:1; DROP TABLE users;
pythonSELECT * FROM users WHERE id = 1; DROP TABLE users;第一条指令查出了数据,第二条指令直接把我们的用户表给删了。
方案: 参数化查询
使用%s作为占位符,将变量放在[]列表中传入:PyMySQL会自动处理转义字符,即使params包含恶意代码,也会被当作普通字符串处理,而不会当作SQL指令执行。
3 集成MySQL 数据库连接池
3.1 问题讨论
用户每次发送请求,后台与数据库会经历: ++建立连接-请求查询-返回-断开连接++ 这样一个过程。
------ 😥 这就好比你每去一次超市都要现买一辆新车,回来后再把车报废掉,效率低得离谱。
然而在实际开发当中,++相比于数据的发送与接收,连接的建立与关闭更为耗时++,这样效率很低。
😶 **如果我们让所有请求共用一个连接?****绝对不行!**可能存在并发的问题。
a) 并发冲突 :数据库游标(Cursor)不是线程安全的。如果 A 请求正在执行查询,B 请求突然进来了,它们会争抢同一个通道,导致报错(如Commands out of sync)。b) 事务错乱:若A请求开启了事务没提交,B请求紧接着执行操作,B 的操作可能会被错误地回滚或提交。
c) 连接失效:如果数据库因为超时主动断开了这个长连接,你的整个后端就挂了。
3.2 解决方案:数据库连接池(Connection Pooling)
逻辑:预先租好一排车(连接),谁用谁开,用完还回来。
核心原理:
a)初始化:程序启动时,先创建 N 个连接放在池子里。
b)借出 (Acquire):当请求进来时,从池子里取出一个"空闲"连接。
c)使用:执行 SQL 操作。
d)归还 (Release) :操作完成后,不关闭连接,而是放回池子,标记"空闲", 等待下一个请求。
在 Python/Flask 环境中,最常用的连接池工具是 DBUtils。
python
pip install DBUtils
编写连接池工具类:
(下面是修改部分,其余无改动)
python
import pymysql
from dbutils.pooled_db import PooledDB
# 1. 初始化连接池配置
POOL = PooledDB(
creator = pymysql, # 使用链接数据库的模块
maxconnections = 10, # 连接池允许的最大连接数,0和None表示不限制连接数
mincached = 2, # 初始化时,连接池中至少1创建的空闲的链接,0表示不创建
maxcached = 5, # 连接池中最多闲置的连接数,0和None表示不限制
blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True:等待;Flase:不等待,报错
setsession= [], # 开始会话前执行的命令列表。如:["set datastyle to ...", "set time zone ..."]
ping = 0,
# 数据库配置信息指定
host='127.0.0.1', port=3306, user='root', password='123456', charset='utf8',db='flask-learning'
)
# 2. 连接数据库读取数据
def fetch_one(sql, params):
# conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='123456', charset='utf8',db='flask-learning')
# 从池子里拿一个连接,而不是新建
conn = POOL.connection()
cursor = conn.cursor(pymysql.cursors.DictCursor) # 推荐用字典游标
try:
cursor.execute(sql, params)
result = cursor.fetchone()
finally:
# 这里的 close() 不会真的断开数据库,而是把连接还回池子
cursor.close()
conn.close()
return result
总结
在前面的练习中,我们针对 "生成签名" 案例进行了Flask API访问的简单实现,从开始的无校验版本,到基于文件授权来限制用户访问,为了更加规范并且数据操作简洁,我们实现基于数据库的授权------ 用户发送请求-后台与数据库:建立连接-请求查询-返回结果-断开连接。
然而,频繁的连接的建立与断开会极大降低程序的性能,在实际开发当中,可以通过集成 DBUtils 连接池来避免这个问题:
a) 省去了握手和认证时间,让响应速度大幅提升;b) 同时通过 maxconnections 限制,防止后端把数据库连接数撑爆;c) 资源复用,降低了 CPU 和内存的波动。