beego 中必须在 servejson() 或 render() 前调用 this.ctx.output.header() 设置安全头,否则失效;静态文件服务默认不加头,需用反向代理或自定义 handler 补齐。

Beego 中设置安全响应头必须在 ServeJSON() 或 Render() 之前
Beego 的 ServeJSON() 和 Render() 都会自动调用 w.WriteHeader() 并写入响应体,一旦触发,w.Header().Set() 就失效。你如果在调用它们之后再设 X-Frame-Options 或 Content-Security-Policy,浏览器根本收不到。
正确顺序只有这一种:
- 先调用
this.Ctx.Output.Header("X-Frame-Options", "DENY") - 再调用
this.ServeJSON()或this.Render()
别信“中间件统一加头”这种说法——Beego 的中间件执行顺序不保证在控制器逻辑之前完成头设置,尤其当你用了自定义 FilterFunc 或嵌套了多个中间件时,极易被覆盖。
Beego 没有内置 CSP 支持,拼接字符串极易出错
Content-Security-Policy 对语法极其敏感:少一个单引号、多一个空格、指令名拼错(比如 script-src 写成 scripts-src),整条策略就被浏览器静默忽略。Beego 不提供 DSL 或校验机制,全靠你自己拼字符串。
常见翻车点:
- 用双引号包裹整个值,但策略内部又混用双引号(如
"script-src \"self\"")→ JSON 转义混乱 - 动态插入 nonce 时直接
fmt.Sprintf("script-src 'nonce-%s'", nonce)→ 若nonce含非法字符或为空,策略失效 - 静态资源路径写死为
https://cdn.example.com,但测试环境走http://localhost:8080→ 策略拒绝加载
建议开发期先用 Content-Security-Policy-Report-Only 头,配合 report-uri 查看真实拦截日志,确认无误后再切到正式头。
Beego 的 Output.Header() 是惰性写入,但会被 Finish() 或重定向覆盖
Beego 的 this.Ctx.Output.Header() 底层仍走标准 http.ResponseWriter.Header().Set(),所以同样遵循“写头必须早于任何响应写出”的规则。但它还有额外陷阱:
- 调用
this.Redirect()会清空已设的头并写入Location和302状态码 - 调用
this.StopRun()或未捕获 panic 时,Beego 会执行Finish(),可能覆盖你设的安全头 - 使用
beego.InsertFilter()注册的全局 Filter,若在FinishRouter阶段操作头,已晚于控制器输出
最稳妥的做法:所有关键安全头(X-Content-Type-Options、Strict-Transport-Security)都在控制器方法第一行显式设置,不要依赖中间件或父类 Prepare() —— 除非你 100% 控制该方法的执行链路。
Beego 的静态文件服务(StaticDir)默认不带安全头
你在 app.conf 里配了 staticdir = /static,Beego 会用 http.ServeFile 提供文件,但它完全不设任何安全响应头。这意味着你的 index.html、main.js 全部裸奔。
解决方式只有两个:
- 不用 Beego 内置静态服务,改用自定义 handler 包装
http.FileServer,手动加头 - 把前端构建产物扔到 Nginx / Caddy 后面,由反向代理统一加头(更推荐,也更符合生产部署习惯)
别试图在 StaticDir 对应的控制器里加头——它压根不走控制器逻辑,Output.Header() 完全没机会执行。











