referer校验必须解析url后精确匹配host而非字符串包含,因strings.contains会被子域名伪造绕过;gin中间件中c.abort()后须立即return,否则后续逻辑仍执行。

Referer 校验不能只靠 c.Request.Referer() 字符串简单匹配,否则会被子域名或路径伪造绕过。
为什么直接用 strings.Contains 匹配 Referer 会失效
Referer 是 HTTP 请求头字段,但 Go 的 net/http.Request.Referer() 是方法而非字段,调用它返回的是完整 URL 字符串(如 "https://evil.com.example.com/path")。如果白名单是 "example.com",用 strings.Contains(referer, "example.com") 会误判恶意域名 "evil.com.example.com" 合法。
- 必须先用
url.Parse()解析 Referer,再检查u.Host是否非空且有效 - 推荐用精确域名匹配(
u.Host == whiteDomain),而不是strings.HasSuffix或strings.Contains - 注意协议无关性:HTTP 和 HTTPS 的 Referer 都可能被发送,但校验时只关心 Host
gin.Context.Abort() 后不 return 会导致后续 handler 仍执行
这是 Gin 中间件最常踩的坑。校验失败调用 c.Abort() 只是中断当前中间件链,不会自动跳出函数体。若后面还有逻辑(比如日志、数据库操作),仍会继续运行。
- 必须在
c.Abort()后紧跟return - 错误响应建议统一用
c.AbortWithStatusJSON(403, gin.H{"error": "forbidden by referer policy"}) - 别写成
c.Abort(); c.JSON(403, ...)——c.JSON在已 Abort 的上下文中可能 panic 或被忽略
如何支持多域名白名单并区分 API 与静态资源
生产环境通常需要对不同路由组设置不同 Referer 策略,比如 /api/ 接口禁止外部 Referer,而 /static/ 允许 CDN 域名访问。
- 用路由分组 + 中间件参数化:定义
RefererMiddleware(whitelist []string),按需传入不同白名单 - 对
/static/*路由组,白名单可包含"cdn.example.com"、"*.example.com"(注意:通配符需手动解析,Gin 不内置) - 避免全局注册该中间件;应只挂载到明确需要防盗链的 Group,例如
api := r.Group("/api").Use(RefererMiddleware([]string{"app.example.com"}))
Referer 为空或不可信时的处理边界
浏览器可能因隐私策略(如 Referrer Policy: no-referrer)、HTTPS→HTTP 跳转、或用户禁用 Referer 导致 c.Request.Referer() 返回空字符串。此时不应直接拒绝,而要结合业务判断。
- 空 Referer 对于 API 接口通常允许(如 curl 直接调用、定时任务),但对图片等静态资源应拒绝
- 不要依赖 Referer 做唯一鉴权手段;它本质是客户端提示,可被篡改,仅作辅助防护
- 若需强校验,应配合 token、签名或 IP 白名单等后端可信机制
Referer 白名单真正起作用的前提,是上游反向代理(如 Nginx)没剥离或覆盖原始 Referer 头。检查 proxy_set_header Referer $http_referer; 是否配置,否则 Gin 收到的永远是空。











