📖 文章摘要
网盘功能支持密码分发 + 5 分钟 Token 下载。本文详解时间窗口算法、无状态 Token 验证、双重上传认证和前端页面的完整实现。
临时网盘功能设计
博客附带了一个临时网盘功能,适用于向读者分发文件。设计原则是安全足够用、代码足够轻量。
业务场景
假设你写了一个工具脚本,想分享给读者。传统做法是挂网盘链接或 GitHub Releases,但两者都有问题——网盘链接会过期,GitHub 需要账号。
本博客的网盘方案:上传时设置下载密码和次数限制,生成分享链接;访客输入密码即可下载。
业务流程
上传者:填写管理员密码 + 文件密码 + 文件 → 获取分享链接
↓
访客:访问链接 → 输入文件密码 → 获取下载 token(5 分钟有效)
↓
使用 token 下载文件
↓
后台可查看下载记录
核心 API 实现
Token 生成(时间窗口算法)
TOKEN_WINDOW = 300 # 5 分钟
def _make_token(share_id: int, password_hash: str) -> str:
slot = int(datetime.utcnow().timestamp() / TOKEN_WINDOW)
raw = f"{share_id}:{password_hash}:{slot}".encode()
return hashlib.sha256(raw).hexdigest()[:16]
Token 基于当前时间窗口计算,无状态——服务器不需要存储 token,验证时重新计算比较即可。
验证时同时检查当前窗口和上一个窗口,防止窗口切换时刻的临界过期:
expected = _make_token(share.id, share.password_hash)
if token != expected:
# 试上一个时间窗口
expected_prev = _make_token(share.id, share.password_hash)
if token != expected_prev:
raise HTTPException(403, "token 无效或已过期")
文件上传
@router.post("/upload")
async def upload_pan(
password: str, admin_password: str,
download_limit: int = 0, expire_days: int = 0,
file: UploadFile = File(...),
):
# 1. 验证管理员身份
admin = db.query(Admin).first()
if not admin or not verify_password(admin_password, admin.password_hash):
raise HTTPException(403, "管理员密码错误")
# 2. 保存文件
ext = Path(file.filename).suffix
filename = f"{uuid.uuid4().hex}{ext}"
filepath = PAN_DIR / filename
filepath.write_bytes(content)
# 3. 创建分享记录
share = Share(
file_name=file.filename,
file_path=str(filepath),
password_hash=hash_password(password),
password_plain=password, # 明文存储,方便管理员查看
download_limit=download_limit,
)
两种上传方式:
/api/pan/upload— 需提供管理员密码(适用于从前端页面上传)/api/pan/admin-upload— 需 JWT 认证(适用于从后台管理上传)
下载文件
@router.get("/{share_id}/download")
def download_file(share_id: int, token: str, db: Session = Depends(get_db)):
# 验证 token
if token != expected:
raise HTTPException(403, "token 无效或已过期")
# 记录下载 + 递增计数器
share.download_count += 1
db.add(DownloadLog(share_id=share.id))
db.commit()
return FileResponse(str(filepath), filename=share.file_name)
数据库设计
两张表:
Share — 文件记录
| 字段 | 类型 | 说明 |
|---|---|---|
| file_name | VARCHAR | 原始文件名(用户下载时看到的名字) |
| file_path | VARCHAR | 服务器上的实际路径(UUID 重命名) |
| password_hash | VARCHAR | SHA256 |
| password_plain | VARCHAR | 明文(管理员查看用) |
| download_limit | INT | 0=不限 |
| download_count | INT | 当前已下载次数 |
| expire_at | DATETIME | 过期后自动拒绝下载 |
DownloadLog — 下载记录
| 字段 | 说明 |
|---|---|
| share_id | 文件 ID |
| downloaded_at | 下载时间 |
前端页面
/pan/— 上传页面(输入管理员密码后显示上传表单)/pan/[id]— 下载页面(输入文件密码后显示下载按钮和倒计时)- 后台
/admin/pan— 文件管理列表(查看密码明文、复制链接、删除、查看下载记录)
安全措施总结
- SHA256 密码存储
- 无状态 token(时间窗口算法,5 分钟有效)
- 上传权限控制(管理员密码 或 JWT)
- 下载次数限制
- 文件过期自动失效
- 文件物理存储使用 UUID 重命名,防止路径遍历
最后更新:2026年6月29日CC BY-NC-SA 4.0
评论
暂无评论,来写第一条吧
