c.header()必须在c.json()前调用,因c.json()会强制重设content-type为application/json; charset=utf-8,后置设置将被覆盖;正确顺序是先c.header()再c.json()。

直接设置安全响应头是 Gin 应用最基础、最有效的防护手段之一,但光写 c.Header() 不够——顺序、条件、覆盖逻辑稍错,头就失效或被覆盖。
为什么 c.Header() 必须在 c.JSON() 之前调用
Gin 的 c.JSON() 内部会强制重设 Content-Type 为 application/json; charset=utf-8。如果你在它之后调用 c.Header("Content-Type", "application/json"),会被立即覆盖;反之,前置设置能生效,因为 http.ResponseWriter.Header() 允许多次写入,后写者胜出。
- 错误写法:
c.JSON(200, data); c.Header("Content-Type", "application/json")→ 无效 - 正确写法:
c.Header("Content-Type", "application/json"); c.JSON(200, data)→ 生效 - 更稳妥做法:封装成统一函数(如
JSON(c, code, data)),把Header固定前置
HTTPS 下才该设置 Strict-Transport-Security
Strict-Transport-Security 是个“一旦设置就不可撤回”的策略,如果误在 HTTP 环境下发,浏览器会忽略但可能误导运维判断。Gin 中必须显式判断是否走 HTTPS:
- 检查
c.Request.TLS != nil(直连 HTTPS) - 或检查代理头:
c.GetHeader("X-Forwarded-Proto") == "https"(常见于 Nginx/ALB 后端) - 两者取其一即可,不必同时验证;但只靠
X-Forwarded-Proto有被伪造风险,需确保反向代理可信
防 iframe 嵌套要双保险:X-Frame-Options + Content-Security-Policy
仅设 X-Frame-Options: DENY 已不够——现代浏览器优先遵循 Content-Security-Policy,且后者支持更细粒度控制。Gin 中应同时设置:
-
c.Header("X-Frame-Options", "DENY")—— 兼容老浏览器(IE、旧版 Safari) -
c.Header("Content-Security-Policy", "frame-ancestors 'none'")—— 主力防护,明确禁止所有嵌套 - 避免混用
frame-ancestors 'self'和X-Frame-Options: SAMEORIGIN,语义冲突可能导致策略失效
中间件里设置头时,c.Next() 不能漏
安全头中间件本质是“请求前设置响应头 + 执行后续逻辑”,漏掉 c.Next() 会导致整个请求链中断,handler 不执行,返回空响应。
- 典型错误:写完一堆
c.Header()后忘记c.Next(),接口永远 200 但无 body - 注意位置:所有
c.Header()必须放在c.Next()之前,否则无法影响最终响应 - 调试技巧:加
log.Println("security middleware hit")在c.Next()前后,确认中间件是否完整执行
真正容易被忽略的不是某一行代码,而是头设置的“时机依赖”和“条件边界”——比如 HSTS 只在 HTTPS 下发、CSP 的 frame-ancestors 不兼容 X-Frame-Options 的宽松值、c.Header() 被 c.JSON() 覆盖的静默失败。这些都不是语法错误,而是运行时逻辑陷阱。











