文件上传小接口:Python + Flask 本地存盘就能用
一、背景:文件上传接口,是后端逃不掉的基本功
几乎所有系统都有"传文件"的需求:头像、附件、截图、导入的 Excel。它看着简单——前端 POST 一个文件、后端存下来——但真写要考虑不少:存哪、叫什么名、多大、什么类型、并发会不会乱、怎么防覆盖。Flask 写这个小接口特别轻,几行就能跑起来,适合本地工具、内网服务或练手。
本文用 Flask 写一个文件上传接口:接收 multipart 文件、校验类型和大小、用安全文件名存到本地目录、返回可访问 URL。讲清 Flask 怎么收文件、为什么要用 secure_filename、以及几个安全坑。具体 API 以 Flask 官方文档为准。它和前面 Storage/对象存储是"本地版"对应,思路相通。
二、最小上传接口
from flask import Flask, request, jsonify
import os
app = Flask(__name__)
UPLOAD_DIR = "./uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)
@app.route("/upload", methods=["POST"])
def upload():
f = request.files.get("file")
if not f or f.filename == "":
return jsonify(error="no file"), 400
path = os.path.join(UPLOAD_DIR, f.filename)
f.save(path)
return jsonify(url=f"/files/{f.filename}")
request.files.get("file") 取上传的文件对象;没取到或文件名为空返回 400。f.save(path) 把文件写到 UPLOAD_DIR。os.makedirs(..., exist_ok=True) 确保目录在。返回存后的"访问 URL"(这里假设另有路由把 /files/<name> 映射出去)。整个接口不到 10 行,但已能跑——本地传个图、存下来了。
三、为什么必须用 secure_filename
上面的 f.filename 直接拼路径,是个安全隐患。攻击者可以把文件名写成 ../../../etc/cron.d/evil,拼出来的 path 就跳出 UPLOAD_DIR、写到系统目录去(路径穿越,path traversal)。Flask 提供 secure_filename 把文件名净化:
from werkzeug.utils import secure_filename
name = secure_filename(f.filename) # 只保留安全字符、去路径分隔
if not name:
return jsonify(error="bad filename"), 400
path = os.path.join(UPLOAD_DIR, name)
f.save(path)
secure_filename 会剥掉 ../、只留基本文件名(中文可能被转写,按版本行为),杜绝路径穿越。但要注意:它不保证文件名唯一——两个用户传同名 a.png 会互相覆盖。所以生产还要自己生成唯一名(如 uuid4().hex + 后缀),secure_filename 只负责"净化"、不负责"唯一",两者配合。
四、类型与大小校验
不能什么文件都收,至少校验扩展名和白名单、限制大小:
ALLOWED = {".png", ".jpg", ".jpeg", ".pdf", ".txt"}
MAX = 5 * 1024 * 1024 # 5MB
@app.route("/upload", methods=["POST"])
def upload():
f = request.files.get("file")
if not f or f.filename == "":
return jsonify(error="no file"), 400
ext = os.path.splitext(f.filename)[1].lower()
if ext not in ALLOWED:
return jsonify(error="type not allowed"), 400
name = secure_filename(f.filename) or (uuid4().hex + ext)
path = os.path.join(UPLOAD_DIR, name)
f.save(path)
return jsonify(url=f"/files/{name}")
ALLOWED 是扩展名白名单,非白名单类型直接拒(防有人传 .exe、.php 之类危险文件,尤其当上传目录还可被 Web 直接执行时,传可执行脚本是严重风险)。大小限制靠 Flask 配置的 MAX_CONTENT_LENGTH(请求体超了直接 413 拒绝),具体配置项以官方文档为准——别只在前端限制,前端限制一改就绕过,后端必须自己卡。
五、几个安全与工程注意点
第一,存盘目录别可被当作代码执行。UPLOAD_DIR 如果和 Web 可执行目录重叠、且允许传 .php/.jsp,攻击者传个 webshell 就能远程执行。白名单只收静态类型(图片/文档)、上传目录不配置为可执行、是基本防线。
第二,文件名唯一。再次强调,secure_filename 不保证唯一,同名覆盖会丢数据或串数据。用 uuid4().hex + 原扩展名 当存储名最稳,原始文件名只做展示、不做存储名。
第三,大小限制放后端。前端说"最大 5MB"只是体验,攻击者用 curl 直接发 500MB 照样打爆磁盘。Flask 的 MAX_CONTENT_LENGTH 在服务端截断,必须配。具体字段名以官方文档为准。
第四,并发写同一文件。多请求同时存同名文件可能互相覆盖或写坏。用唯一文件名(uuid)从根上避免;若一定要固定名,加锁或先写临时文件再原子 rename。
第五,返回 URL 要可访问。接口返回 /files/xxx,得有对应路由把 UPLOAD_DIR 暴露出去(Flask 用 send_from_directory 并同样 secure_filename 防穿越),否则返回了 URL 却打不开。暴露目录时也要注意不要整目录可列、不要越权读别人的文件。
第六,存本地 vs 存对象存储。本地磁盘适合单机小工具;多实例、要持久、要公网访问,还是前面 OBS 那套更合适。上传接口换成"写 OBS"即可,校验逻辑不变。按部署形态选存储。
六、小结
Flask 文件上传接口 = 收 request.files、校验、安全存盘、返回 URL。安全三件套:secure_filename 防路径穿越、扩展名白名单防可执行文件、后端 MAX_CONTENT_LENGTH 防超大文件(前端限制可被绕过)。文件名要用 uuid 保证唯一、避免覆盖;上传目录不可配置为可执行、暴露时防越权读。本地磁盘适合单机,多实例/公网换 OBS、校验逻辑复用。Flask 文件处理与配置项以官方文档为准。
- 点赞
- 收藏
- 关注作者
评论(0)