beego中静态资源请求唯一拦截点是beego.beforestatic,需用精确正则路径注册;真实ip须手动解析x-real-ip/x-forwarded-for并校验可信代理;黑白名单应支持cidr且线程安全;健康探针和公开静态资源须硬编码豁免。

Beego 中静态资源请求必须用 beego.BeforeStatic 插入过滤器
静态文件(如 /static/ 下的图片、JS、CSS)在 Beego 中不走常规路由,beego.BeforeRouter 和 beego.BeforeExec 完全不会触发。如果你把 IP 校验逻辑放在这两个钩子里,对 /static/users/123/private/report.pdf 这类路径完全无效。
正确入口只有 beego.BeforeStatic,它在静态处理器执行前调用,是唯一能拦截静态资源请求的位置。
- 注册时路径要带通配符:
"/static/users/:id([0-9]+)/private/*",不能写成"/static/users/*/private/*"—— Beego 的正则参数解析不支持星号嵌套 - 过滤器函数内需手动提取
ctx.Request.URL.Path,ctx.Input.Param(":id")在BeforeStatic阶段不可用 - 若路径含中文或特殊字符,需先
url.PathUnescape()再匹配,否则正则可能失败
真实客户端 IP 提取必须绕过 c.ClientIP()
c.ClientIP() 在 Beego v2 中默认只信任 127.0.0.1,线上经 Nginx/Traefik/CDN 后,它返回的几乎总是 127.0.0.1 或内网地址(如 10.0.1.5),不是用户真实出口 IP。
必须显式解析请求头,并结合可信代理链判断:
- 优先读
c.Request.Header.Get("X-Real-IP"),前提是 Nginx 已配置proxy_set_header X-Real-IP $remote_addr - 若为空,再从
X-Forwarded-For取最左非私有 IP(注意:不是最右!最右可伪造),且仅当该 IP 属于你配置的可信代理网段时才继续向左查 - Beego 不提供
SetTrustedProxies方法,需自己校验:比如代理集群在192.168.10.0/24,那么只有该网段发来的X-Forwarded-For才可信
黑白名单数据结构必须支持 CIDR 且线程安全
用 map[string]bool 存 IP 列表看似简单,但无法匹配网段(如 "192.168.1.0/24"),也不能处理并发更新时的读写竞争——规则热加载瞬间,正在执行的请求可能看到新旧混合状态。
推荐组合:net.IPMask + sync.RWMutex + atomic.Value 包裹切片:
- 预解析所有 CIDR 到
[]struct{ ip net.IP; mask net.IPMask },避免每次请求都调用net.ParseCIDR - 更新规则时构造新切片,用
atomic.StorePointer原子替换,读取时atomic.LoadPointer转回切片,零锁读 - 黑名单匹配优先于白名单:先查是否在黑名单,是则直接
ctx.Abort(403);不在黑名单再查白名单,不在白名单也拒绝
健康探针和静态公开资源必须豁免过滤
像 /healthz、/metrics、/static/public/ 这类路径如果被 IP 过滤器拦住,会导致监控告警失真、CDN 回源失败、前端资源加载 403。
豁免逻辑不能靠“路径前缀判断”,而要精确匹配:
- 在过滤器开头加硬编码检查:
if strings.HasPrefix(path, "/healthz") || path == "/metrics" || strings.HasPrefix(path, "/static/public/") - 避免用正则匹配豁免路径,正则编译开销大,且易误伤(如
/healthz-log被误认为/healthz) - 豁免检查必须放在 IP 解析之后、黑白名单比对之前——否则伪造的
X-Real-IP可能绕过校验直接进入豁免分支











