GridFS文件预览不能直接暴露fs.files的_id,因其无内置权限控制,任何知悉_id的请求均可绕过鉴权直接读取;Token校验须在应用层中间件完成,且需流式返回文件、设置正确响应头并完善错误处理。

GridFS 文件预览为什么不能直接暴露 fs.files 的 _id?
因为 GridFS 本身不带权限控制——fs.files 和 fs.chunks 是普通集合,任何知道 _id 的请求(比如构造 /file/507f1f77bcf86cd799439011)都能绕过鉴权直接读取。Token 校验必须落在应用层中间件,且需在流式返回文件前完成。
Express 中间件如何安全提取并验证 JWT Token?
常见错误是只校验 Authorization 头是否存在,却忽略 Bearer 前缀、空格、过期或签名失效。Token 必须解析后比对用户权限字段(如 scope: ["view:files"]),而非仅检查是否有效。
-
必须用
req.headers.authorization?.split(' ') || []提取 token,避免Bearer undefined导致 500 -
验证失败时返回
401 Unauthorized或403 Forbidden,不要返回空响应或重定向 -
不要在验证前调用
res.setHeader('Content-Type', ...),否则无法再发 4xx 状态码 - 建议使用
jsonwebtoken.verify(token, secret, { algorithms: ['HS256'] })显式指定算法,防止 alg=none 攻击
如何用 GridFSBucket.openDownloadStream() 流式返回文件且不内存溢出?
直接 toArray() 或 findOne() 读整个文件会吃光内存;必须用流(stream)+ pipe 模式,并设置超时和限速。注意:openDownloadStream() 接收的是 ObjectId,不是字符串 ID。
- 传入前务必用
new ObjectId(req.params.id)转换,否则查不到且静默失败 - 调用前加
bucket.find({ _id: fileId }).limit(1).toArray()做存在性校验(可选,但能提前拦截非法 ID) - 用
res.writeHead(200, { 'Content-Type': file.contentType || 'application/octet-stream' })设置类型,别依赖文件扩展名 - 必须监听
stream.on('error', ...),否则 MongoDB 连接断开时 Node 进程会 unhandledRejection 崩溃
为什么预览 PDF/图片时浏览器仍可能触发下载而不是内嵌显示?
关键在 Content-Disposition 响应头。默认 GridFS 不设该头,浏览器按 Content-Type 自行决定行为;但若后端未显式设为 inline,部分浏览器(尤其 Chrome)对非白名单 MIME 类型会强制下载。
- 对 PDF 加
res.setHeader('Content-Disposition', 'inline; filename="' + encodeURIComponent(file.filename) + '"') - 对图片(
image/*)、文本(text/*)同样设inline,避免 Safari 把text/plain当附件 - 不要省略
filename参数——即使只是占位符,缺失会导致某些旧版 Edge 忽略inline - 如果前端用
<iframe src="/preview/..."></iframe>,还需确保响应头含X-Frame-Options: SAMEORIGIN或Content-Security-Policy: frame-ancestors 'self',否则被拦截
openDownloadStream() 加 { timeoutMS: 30000 } 选项。


















