Echo框架中Referer校验应允许空Referer并用正则匹配本域(如^https?://(www\.)?myapp\.com),避免字符串包含式匹配以防子域名绕过,且需为静态资源路径单独配置中间件。

如何用 Echo 框架校验 Referer 实现基础防盗链
直接读取 req.Referer() 并做字符串匹配是最常见的误操作——它既无法处理空 Referer(如直接浏览器地址栏访问),也容易被伪造。正确做法是结合正则和白名单校验:
- 允许空 Referer(
req.Referer() == "")和本域请求(如^https?://(www.)?myapp.com) - 避免用
strings.Contains()匹配域名,防止evil.com.myapp.com这类绕过 - 对静态资源路径(如
/files/.*.(pdf|zip|jpg))单独加中间件,不污染主路由逻辑
示例中间件片段:
func refererCheck() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
referer := c.Request().Referer()
if referer == "" {
return next(c)
}
matched, _ := regexp.MatchString(`^https?://(www.)?myapp.com(:[0-9]+)?(/|$)`, referer)
if matched {
return next(c)
}
return c.String(http.StatusForbidden, "Forbidden")
}
}
}
file.Get 下载时怎么避免路径遍历与类型越权
别直接拼接 c.Param("filename") 到 os.Open() ——用户传 ../../etc/passwd 就完蛋。关键动作是三步:路径规范化、白名单后缀、根目录隔离。
- 用
filepath.Clean()归一化路径,再用strings.HasPrefix(cleaned, "/data/files/")强制限定前缀 - 后缀必须从白名单中匹配(如
map[string]bool{".pdf": true, ".zip": true}),不能只靠 MIME 推断 - 文件系统层面把
/data/files/目录设为只读(或挂载为独立 volume),禁止执行权限
错误示范:os.Open("/data/files/" + c.Param("filename")) → 正确写法:
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
filename := filepath.Clean(c.Param("filename"))
if !strings.HasPrefix(filename, "2026-") || !validExt[filepath.Ext(filename)] {
return c.String(http.StatusNotFound, "Not found")
}
fullPath := filepath.Join("/data/files", filename)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
return c.String(http.StatusNotFound, "Not found")
}
为什么不能只靠 Token 防盗链,而要配合时间戳和签名
单纯用 ?token=abc123 的 URL 是静态的,一旦泄露就永久有效。真实场景中必须引入时效性和不可预测性。
- Token 应由服务端生成,包含时间戳(如
exp=1728856980)、随机 salt、文件路径哈希三元组 - 验证时先检查
exp是否过期(建议 ≤ 30 分钟),再用相同 salt 和路径重算签名比对 - 避免在日志里记录完整 token(尤其不要打到 access log),防止泄露
签名生成参考:hmac.New(sha256.New, []byte(salt)).Write([]byte(path + exp)),比 MD5/SHA1 更抗碰撞。
大文件下载时怎么避免内存爆掉和连接超时
echo.File() 内部调用 http.ServeFile(),看似省事,但会把整个文件读进内存再发——1GB PDF 直接 OOM。必须改用流式响应。
- 用
c.Response().Writer获取底层http.ResponseWriter,调用io.Copy()分块传输 - 手动设置
Content-Disposition: attachment; filename="xxx.pdf"和Content-Type - Nginx 前置时,关掉
proxy_buffering,否则它会缓存整块响应再吐给客户端
关键代码节选:
f, _ := os.Open(fullPath)
defer f.Close()
c.Response().Header().Set("Content-Disposition", `attachment; filename="`+safeName+`"`)
c.Stream(http.StatusOK, "application/pdf", func(w io.Writer) error {
_, err := io.Copy(w, f)
return err
})
Nginx 作为反向代理时,valid_referers 和 echo 的 Referer 校验会叠加生效,但要注意 Nginx 默认不透传原始 Referer 给后端——得显式加 proxy_set_header Referer $http_referer;,否则 c.Request().Referer() 拿到的是 Nginx 自己的地址。

















