Beego黑名单过滤必须注册在beego.BeforeRouter阶段,因其在路由匹配前执行,可覆盖所有请求(含/api/、/static/、/login),且避免session初始化干扰;IP需解析X-Forwarded-For,User-Agent用ctx.Request.UserAgent()获取;拦截应使用ctx.AbortWithStatusJSON(403, ...)确保响应生效。

Beego 中间件做黑名单过滤,必须用 beego.InsertFilter() 按路径注册,不能靠中间件内部手动判断跳过;IP 或 User-Agent 黑名单要存内存 map 或 Redis,且必须在 BeforeRouter 阶段拦截,否则静态资源和预检请求会逃逸。
黑名单该注册在哪个 Filter 位置
必须用 beego.BeforeRouter。这个阶段在路由匹配前执行,能覆盖所有请求(包括 /api/、/static/、/login),且 session 尚未初始化——这对 IP 黑名单很关键,避免因 session 初始化失败导致拦截失效。
-
beego.BeforeStatic只对静态资源生效,漏掉 API 和页面请求 -
beego.BeforeExec已完成路由匹配,此时 Controller 的Prepare()可能已执行部分逻辑(比如日志打点或 DB 连接) -
beego.BeeApp.FilterType是别名,实际等价于BeforeRouter,但语义不清晰,建议直接写beego.BeforeRouter
怎么提取并比对黑名单标识
常见标识是客户端 IP 和 User-Agent,提取方式有差异:
- IP 要用
ctx.Request.RemoteAddr,但注意反向代理场景下需读X-Forwarded-For头:ctx.Request.Header.Get("X-Forwarded-For"),取第一个非空值 -
User-Agent直接读:ctx.Request.UserAgent(),不要用ctx.Input.Header("User-Agent")(键名自动驼峰化为"User-Agent"→"User-Agent",但 Beego 实际转成"User-Agent"→"User-Agent",实测仍可用;稳妥起见还是用UserAgent()方法) - 黑名单存储建议用
map[string]struct{}(内存级),查 O(1);若需跨进程共享,用 Redis 的SET+SISMEMBER,但得加超时防止误拉黑永久生效
拦截后怎么返回才真正生效
只写 return 或 ctx.Abort(403) 不够——前者不阻断后续 filter,后者不输出响应体,前端可能收空包或超时。
- 推荐用
ctx.AbortWithStatusJSON(403, map[string]string{"error": "blocked by blacklist"}),它自动设状态码、Content-Type 和 JSON body - 如果需要返回纯文本(比如给爬虫看),改用
ctx.ResponseWriter.WriteHeader(403)+fmt.Fprint(ctx.ResponseWriter, "blocked"),但得先清空 header 缓存:ctx.ResponseWriter.Header().Set("Content-Type", "text/plain; charset=utf-8") - 绝对不要在黑名单 filter 里调
ctx.Redirect(),302 会暴露内部路径,且对 API 请求不友好
容易被忽略的绕过点
OPTIONS 预检请求默认不触发任何自定义 filter(Beego 2.3.5 仍如此),所以如果黑名单含 CORS 相关 UA 或 IP,它们会直通。解决办法只有两个:
- 在 filter 开头加硬判断:
if ctx.Request.Method == "OPTIONS" { ctx.Abort(204); return },然后单独做 OPTIONS 黑名单检查 - 或者干脆把黑名单逻辑下沉到 HTTP Server 层(如用 net/http.Handler 包一层再交给 Beego),但这会绕过 Beego 的上下文体系
另外,/static/ 下的请求虽走 BeforeRouter,但若你同时配了 beego.StaticDir,而黑名单规则又没覆盖到具体子路径(比如只写了 /static/* 却漏了 /static 根路径),就可能漏判。最稳的是注册 "/*" 全局匹配,再在函数内按需过滤。











