beego 常规过滤点拦不住 /static/ 请求,因为其静态文件处理采用短路逻辑:一旦路径匹配 beego.staticdir 配置,便跳过路由解析与控制器流程,直接由静态处理器响应,故 beforerouter、beforeexec 等钩子完全不触发;唯一有效拦截点是 beego.beforestatic。

Beego 本身不内置敏感词过滤能力,必须通过自定义过滤器 + 外部匹配库(如 aho-corasick)组合实现;直接在 BeforeRouter 或 BeforeExec 注册过滤器,对静态资源(/static/)请求完全无效。
为什么 Beego 的常规过滤点拦不住 /static/ 请求
Beego 对静态文件的处理是短路逻辑:一旦请求路径匹配 beego.StaticDir 配置(如 /static),就跳过整个路由解析和控制器执行流程,直接由内置静态处理器响应。这意味着:
-
BeforeRouter、BeforeExec、AfterExec等钩子根本不会触发 - 即使你在这些位置写了权限校验代码,对
/static/users/123/private/report.pdf这类 URL 也毫无作用 - 唯一能拦截它的过滤点是
beego.BeforeStatic—— 它在静态处理器执行前调用,且仅为此场景设计
如何在 BeforeStatic 中安全读取并检测请求体
敏感词检测通常针对 POST/PUT 的 JSON 或表单数据,但 BeforeStatic 过滤器默认不解析 body。常见错误是直接调用 io.ReadAll(c.Request.Body) 后未恢复流,导致后续静态文件返回 404 或空内容。
- 只对
Content-Type: application/json或application/x-www-form-urlencoded类型做检测,跳过multipart/form-data(文件上传)等二进制类型 - 用
io.LimitReader限制读取长度(如 1MB),防止恶意大 payload 耗尽内存 - 读完后必须重置:
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)),否则静态处理器无法读取原始 body - 检测命中后立即
c.Abort(403)并 return,避免继续执行
AC 自动机实例必须复用且热更新需加锁
每次新建 ac.AhoCorasick 实例会重建整个状态机,开销远大于一次初始化。词库变更时若直接替换实例,高并发下可能造成部分请求使用旧词库、部分使用新词库。
- 初始化阶段构建单例:
var acInstance *ac.AhoCorasick,全局复用 - 词库热更新时,用
sync.RWMutex保护读写:写操作(重建实例)加写锁,匹配时只加读锁 - 预处理词库:统一转小写、去首尾空格、过滤空字符串,否则
"苹果 "和"苹果"会被视为两个词 - 匹配结果
[]ac.Match包含Start/End坐标,脱敏时应基于这些偏移做原位替换,而非正则全局替换
别在过滤器里做“替换”,拦截才是第一原则
有人习惯用 strings.ReplaceAll 或正则把敏感词替换成 *** 再放行,这在内容安全场景中极其危险:
- 语义被破坏:“石马” → “***”,但“石马镇”可能变成“***镇”,引发误伤或绕过
- 重叠匹配失效:“苹果酱”含“苹果”和“果酱”,正则替换会错切,AC 自动机可精确返回两段坐标
- 大小写混排漏判:“ShiMa” 不会被
strings.Contains("shima")捕获,而 AC 支持 ignore-case 模式 - 真正需要脱敏时,应在匹配后按
Match.Start/Match.End逐段覆盖,确保字节级精准
最易被忽略的一点:静态资源过滤器必须用 beego.BeforeStatic,且路径模式要写成 /static/users/:id([0-9]+)/private/* —— 少一个 * 或错用 :userId,都会导致规则不生效。











