在完成了文件上传、验证、存储和图片处理之后,另一个同样关键的问题是: 用户如何安全地下载这些文件?谁可以访问哪些文件?
如果直接暴露文件真实路径或云存储 URL,往往会带来以下风险:
- 任意用户可下载私有文件
- 文件链接被外部盗用
- 资源被恶意刷流量
- 敏感数据泄露
因此,在真实项目中,文件下载与访问控制几乎是文件系统的必备模块。本文将从实战角度,讲解如何在 Node.js 中设计一个安全、可控、可扩展的文件下载与访问控制机制。
一、为什么不能直接暴露文件地址
很多项目在早期会这样做:
/uploads/20240101-abc123.pdf
或者直接返回云存储的公网 URL。
这种方式存在明显问题:
- 没有权限校验
- 链接可被随意分享
- 无法控制访问次数
- 难以实现下载日志与审计
一旦链接泄露,文件就相当于“公开资源”,几乎无法回收权限。
二、文件下载的基本设计思路
一个相对安全的下载流程通常包括:
- 前端请求下载接口
- 后端校验用户身份
- 校验文件归属或权限
- 校验文件状态(是否删除、是否过期)
- 记录下载日志
- 返回文件流或临时下载地址
这种方式可以在服务端对所有访问行为进行控制。
三、本地文件下载实现
1. 基本下载接口
const path = require('path');
const fs = require('fs');
app.get('/download/:id', async (req, res) => {
const fileId = req.params.id;
const file = await getFileById(fileId); // 从数据库查询文件信息
if (!file) {
return res.status(404).json({ error: '文件不存在' });
}
const filePath = path.resolve(file.path);
if (!fs.existsSync(filePath)) {
return res.status(404).json({ error: '文件已丢失' });
}
res.download(filePath, file.originalName);
});
这里没有直接暴露真实路径,而是通过文件 ID 间接访问。
2. 使用流方式下载(大文件推荐)
app.get('/download/:id', async (req, res) => {
const file = await getFileById(req.params.id);
if (!file) return res.status(404).end();
const filePath = path.resolve(file.path);
const stream = fs.createReadStream(filePath);
res.setHeader('Content-Disposition', `attachment; filename="${file.originalName}"`);
res.setHeader('Content-Type', 'application/octet-stream');
stream.pipe(res);
});
这种方式可以避免一次性加载大文件到内存。
四、基础访问控制实现
1. 用户身份校验
在下载接口中,必须先校验用户身份:
function authMiddleware(req, res, next) {
if (!req.user) {
return res.status(401).json({ error: '未登录' });
}
next();
}
路由中使用:
app.get('/download/:id', authMiddleware, downloadHandler);
2. 文件归属与权限校验
function checkFilePermission(user, file) {
return file.ownerId === user.id || user.role === 'admin';
}
在下载前进行判断:
if (!checkFilePermission(req.user, file)) {
return res.status(403).json({ error: '无权访问该文件' });
}
五、下载 Token 机制(防盗链核心)
为了防止文件链接被直接分享,可以引入一次性下载 Token。
1. 生成临时 Token
const crypto = require('crypto');
function generateDownloadToken(fileId, userId) {
const raw = `${fileId}:${userId}:${Date.now()}`;
return crypto.createHash('md5').update(raw).digest('hex');
}
存储到 Redis 或数据库,并设置过期时间。
2. 使用 Token 下载
app.get('/download', async (req, res) => {
const { fileId, token } = req.query;
const record = await getTokenRecord(token);
if (!record || record.fileId !== fileId) {
return res.status(403).json({ error: '无效或过期链接' });
}
const file = await getFileById(fileId);
// 权限校验略
res.download(file.path, file.originalName);
});
这样即使链接被转发,过期后也无法继续使用。
六、云存储下载与访问控制
如果使用对象存储,推荐使用私有读 + 临时授权 URL。
1. 生成临时下载链接
以阿里云 OSS 为例:
const OSS = require('ali-oss');
const client = new OSS({
region: 'oss-cn-hangzhou',
accessKeyId: process.env.OSS_KEY,
accessKeySecret: process.env.OSS_SECRET,
bucket: 'my-bucket'
});
function generateSignedUrl(objectKey) {
return client.signatureUrl(objectKey, {
expires: 60 * 5
});
}
2. 下载流程
- 前端请求后端下载接口
- 后端校验权限
- 生成临时 URL
- 返回给前端
- 前端直接访问云存储
这种方式性能最好,且无需后端转发大文件流量。
七、下载日志与审计
在企业级系统中,通常需要记录下载行为:
- 下载人
- 下载时间
- 下载文件
- IP 地址
- User-Agent
示例:
await saveDownloadLog({
userId: req.user.id,
fileId: file.id,
ip: req.ip,
ua: req.headers['user-agent']
});
这在安全审计与纠纷处理时非常有价值。
八、常见安全风险与防护建议
1️⃣ 防止路径穿越 永远不要使用用户传入的路径直接访问文件。
2️⃣ 防止暴力下载 限制单 IP / 单用户下载频率。
3️⃣ 防止盗链 使用下载 Token 或云存储签名 URL。
4️⃣ 防止越权访问 严格校验文件归属关系。
5️⃣ 敏感文件加密存储 数据库备份、合同文件建议加密后存储。
九、完整下载流程总结
一个推荐的文件下载与访问控制流程:
- 前端请求下载接口
- 校验用户身份
- 校验文件权限
- 校验文件状态
- 生成下载 Token 或签名 URL
- 记录下载日志
- 返回文件流或临时链接
通过这种方式,可以最大程度保证文件系统的安全性与可控性。
十、总结
在 Node.js 文件上传与处理系统中,下载与访问控制是比上传更重要的一环。如果缺乏权限校验与防盗链机制,任何一个上传系统都可能演变成数据泄露的入口。
通过引入:
- 身份认证
- 权限校验
- 下载 Token
- 云存储签名 URL
- 下载日志
可以构建一个安全、稳定、可审计的文件访问系统。
在《Node.js 编程实战》系列中,文件下载模块为后续的文件管理、权限体系与合规模块打下了坚实基础。