Referer校验必须先解析URL再比对host,禁用strings.Contains;白名单需精确匹配、区分大小写;空Referer需按业务策略处理;Gin中Abort后必须return;Referer仅作辅助,不可替代Token或签名。

Referer 校验必须先解析再比对,不能用 strings.Contains
直接写 strings.Contains(r.Header.Get("Referer"), "example.com") 会漏掉协议差异(https:// vs http://)、子域名绕过(evil.com.example.com)、端口干扰(example.com:8080)和路径污染(example.com/path?x=1)。浏览器也可能发空 Referer(比如 HTTPS 页面跳转到 HTTP 资源),硬匹配会把合法请求拦死。
正确做法是:用 url.Parse() 解析 Referer 字符串 → 提取 u.Scheme 和 u.Host → 去掉默认端口(:80、:443)→ 只比对规范化后的 host。
- 白名单应存为
[]string{"app.example.com", "admin.example.net"},不带协议、不带斜杠、不带端口 - 空 Referer 不等于非法,需按业务决定策略:允许(
none场景)、拒绝、或降级校验(如配合X-Auth-Token) - 别调用
r.Referer()—— 这是方法不是字段,写错会编译失败
Gin 中间件里 c.Abort() 后必须 return
很多人只写 c.AbortWithStatusJSON(403, ...) 就结束,但 Gin 不会自动中断后续 handler 执行。如果中间件之后还挂了图片读取逻辑(比如 c.File() 或 io.Copy()),资源仍会被吐出去。
务必在所有 c.Abort* 调用后紧跟 return,否则等于没拦。
- 错误示范:
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "referer not allowed"});(后面没return) - 正确写法:加
return,且建议统一用c.AbortWithStatusJSON+return组合 - 若校验逻辑分散在多个 handler(如不同图片路由),优先抽成中间件,避免重复判断
白名单匹配要用精确相等,不用 strings.HasSuffix
用 strings.HasSuffix(host, ".example.com") 看似方便,但会被 malicious.example.com.attacker.net 这类构造绕过——只要后缀匹配就放行。这不是子域名白名单,是字符串后缀陷阱。
真正安全的子域名控制,应由 Nginx 层用 valid_referers *.example.com 实现;Gin 层只做精确 host 匹配,更可控也更明确。
- 推荐循环遍历白名单,用
u.Host == domain判断 - 若需支持通配(如
*.admin.example.com),得自己实现前缀匹配逻辑,并限制只允许一个*在开头 - 注意大小写:HTTP Host 是 case-insensitive,Go 的
strings.EqualFold比==更稳妥
Referer 校验只是辅助手段,不能替代签名或 Token
Referer 字段完全由浏览器携带,curl、Postman、甚至前端 fetch 都能手动设置任意值。它防不住脚本批量刷图,也拦不了有心人伪造来源。
如果你的图片含敏感信息(如用户头像、后台截图)、或资源本身有价值(如付费课程视频),仅靠 Referer 白名单远远不够。
- 静态资源建议配合
secure_link(Nginx)或临时签名 URL(后端生成带 expiry + hash 的 path) - API 类接口必须用 token、JWT 或签名验证,Referer 只作日志标记或限流辅助
- 生产环境别把 Referer 校验当唯一防线——它适合挡普通盗链,不适合扛恶意攻击


















