不能直接用http.servefile暴露文件路径,因其不校验来源、无过期限次权限控制,url泄露即永久可访;安全临时链接须绑定路径、时效、使用次数,令牌用crypto/rand生成并存redis,路由为/s/token,验证后用http.servecontent流式响应,禁用重定向以防路径泄露。

为什么不能直接用 http.ServeFile 暴露文件路径
因为 http.ServeFile 不校验请求来源,也不支持过期、限次、权限控制。一旦 URL 泄露,任何人都能永久访问,相当于把文件目录直接挂到公网。临时链接的核心是「令牌必须绑定文件路径 + 过期时间 + 可选使用次数」,且验证逻辑不能暴露原始路径给客户端。
如何生成安全的单次/限时令牌
不要手搓 base64 或简单哈希——它们容易被预测或重放。推荐用 crypto/rand 生成随机令牌,并存入内存或 Redis(避免 SQLite 等低并发存储):
-
token := make([]byte, 32)+rand.Read(token)→ 得到 64 字符 hex 字符串 - 对应关系存为
map[string]struct{ FilePath string; ExpiresAt time.Time; MaxUses int; Used int } - 若用 Redis,键设为
share:TOKEN,值序列化为 JSON,TTL 设为ExpiresAt.Sub(time.Now()) - 务必在验证后立即递增
Used并检查是否超限,防止并发重复使用
如何设计路由和响应逻辑
共享链接形如 /s/TOKEN,而非 /files/xxx.pdf。处理时需严格分离「令牌解析」和「文件读取」两步:
- 先调用
validateToken(r.URL.Path)获取合法的FilePath和剩余次数 - 再用
http.ServeContent(非ServeFile)流式返回内容,传入os.Stat获取的modTime和size - 禁止拼接用户输入构造路径:
filepath.Join("/safe/dir", filePath)后必须用strings.HasPrefix校验结果是否仍在白名单目录内 - 响应头加
Content-Disposition: inline; filename="shared.pdf"控制浏览器打开方式
为什么临时链接要避免重定向到真实路径
重定向(302)看似简洁,但会把原始文件路径泄露到 Location 响应头,且无法控制后续请求的 Referer 或缓存行为。更糟的是,CDN 或代理可能缓存该重定向,导致过期链接仍生效。
正确做法是服务端直接读取文件并透传响应体:file, _ := os.Open(filePath) → io.Copy(w, file),同时设置 w.Header().Set("Cache-Control", "no-store")。如果文件很大,用 http.ServeContent 自动处理 Range 请求和 ETag,否则大文件下载中断后无法续传。











