防盗链原理是校验Referer头是否来自白名单域名,Gin需手动实现中间件校验:解析Referer提取host并精确匹配白名单,空Referer需显式策略,校验失败调用c.AbortWithStatus(403)中断执行。

防盗链原理与Gin中间件判断逻辑
防盗链不是靠加密或权限令牌,而是靠检查 Referer 请求头是否来自白名单域名。Gin 本身不内置防盗链逻辑,必须手动在中间件或路由处理中读取并校验 r.Header.Get("Referer")。
注意:空 Referer(如直接访问图片 URL、curl 不带 -H、某些浏览器隐私模式)默认被多数防盗链策略拒绝,但业务可能需要放行 —— 这得显式判断 referer == "" 并决定是否允许。
常见错误是只匹配子串(如 strings.Contains(referer, "example.com")),结果被 evil-example.com 绕过。必须用完整域名比对,推荐用 net/url.Parse 提取 host 后精确匹配。
Gin 中间件实现 Referer 校验
把校验逻辑封装成中间件最干净,避免每个图片路由重复写。中间件需支持配置白名单、是否允许空 Referer、是否记录拒绝日志。
立即学习“go语言免费学习笔记(深入)”;
- 白名单建议用
map[string]struct{}实现 O(1) 查找,而不是切片遍历 - 用
url.Parse(req.Referer)解析后取u.Host,再小写归一化(防止EXAMPLE.COM被拒) - 若校验失败,应返回
c.AbortWithStatus(403),而非c.Status(403)—— 后者不中断后续 handler 执行 - 不要在中间件里调用
c.Data()或c.File(),那是业务 handler 的事
示例中间件片段:
func RefererCheckMiddleware(whitelist map[string]struct{}, allowEmpty bool) gin.HandlerFunc {
return func(c *gin.Context) {
referer := c.Request.Header.Get("Referer")
if referer == "" {
if allowEmpty {
return
}
c.AbortWithStatus(403)
return
}
u, err := url.Parse(referer)
if err != nil || u.Host == "" {
c.AbortWithStatus(403)
return
}
host := strings.ToLower(u.Host)
if _, ok := whitelist[host]; !ok {
c.AbortWithStatus(403)
return
}
}
}
图片服务路由与静态文件安全响应
图片路径不能直接暴露真实文件系统路径,否则容易被遍历(如 ../../etc/passwd)。Gin 的 c.File() 不做路径净化,必须自己校验。
推荐做法:用 http.Dir + http.ServeFile 配合路径白名单,或更稳妥地用 os.Open 读取后通过 c.Data() 返回。
- 禁止直接拼接用户传入的
c.Param("name")到文件路径,必须用path.Clean()并检查是否仍以预期目录开头 - 图片 MIME 类型要显式设置,比如 PNG 用
"image/png",避免浏览器因类型缺失而下载而非显示 - 加
c.Header("Cache-Control", "public, max-age=31536000")提升 CDN 缓存效率,但注意防盗链和缓存的组合效应 —— 某些 CDN 会缓存 403 响应
关键校验示例:
filename := path.Clean(c.Param("name"))
if !strings.HasPrefix(filename, "images/") || strings.Contains(filename, "..") {
c.AbortWithStatus(403)
return
}
absPath := filepath.Join("./static", filename)
if _, err := os.Stat(absPath); os.IsNotExist(err) {
c.AbortWithStatus(404)
return
}
data, _ := os.ReadFile(absPath)
c.Data(200, "image/jpeg", data)
测试防盗链行为与绕过风险点
本地测试时 curl 默认不发 Referer,得手动加:curl -H "Referer: https://example.com" http://localhost:8080/images/test.jpg。浏览器直接输入地址也会触发空 Referer,这是正常现象,不是 bug。
容易被忽略的绕过方式:
- HTTP/HTTPS 协议不一致:白名单写了
example.com,但请求来自https://example.com(host 相同,没问题),真正风险是http://sub.example.com和https://example.com被当成不同 host —— 通常应统一处理为 host 级别,忽略 scheme 和 port - Referer 可被客户端任意伪造,所以它只防“懒人盗链”,不防恶意爬虫。真要高安全,得结合 token 签名或登录态校验
- CDN 或反向代理可能剥离或改写 Referer,需确认上游是否透传,必要时在 Nginx 层用
proxy_set_header Referer $http_referer;
生产环境上线前,务必用真实浏览器 DevTools 的 Network 面板检查 Referer 字段值,再对照中间件日志确认匹配逻辑是否符合预期。


















