HSTS必须在HTTPS环境下设置,因HTTP响应中的HSTS头会被浏览器忽略;正确做法是仅当c.Request.TLS != nil或X-Forwarded-Proto为https时才设置,且中间件中需调用c.Next()确保响应完整。

为什么HSTS必须在HTTPS环境下才设置
HSTS(HTTP Strict Transport Security)本身是浏览器强制跳转到HTTPS的机制,如果在HTTP响应里发这个头,浏览器直接忽略。Gin中间件里常见错误就是无条件设置 Strict-Transport-Security,结果部署在HTTP服务上时不仅无效,还可能干扰调试。
正确做法是只在请求已走HTTPS(或被反向代理明确标记为HTTPS)时才写入该头:
-
c.Request.TLS != nil表示Go原生监听了HTTPS端口(如:443),此时可安全设置 -
c.GetHeader("X-Forwarded-Proto") == "https"适用于Nginx/Apache等反代场景,需确保反代配置了proxy_set_header X-Forwarded-Proto $scheme; - 两者用
||连接,覆盖自托管和反代两种主流部署方式
max-age=63072000 是什么含义
max-age=63072000 对应2年(63072000秒),这是HSTS预加载列表(如Chrome、Firefox内置列表)要求的最小值。如果你打算提交域名到HSTS preload list,必须设为至少1年,推荐2年。
但要注意:一旦浏览器收到这个头,就会在指定时间内强制所有对该域名的请求走HTTPS——包括手动输入 http://。所以:
- 上线前务必确认全站HTTPS已稳定运行,且所有子域名都支持HTTPS
- 开发/测试环境建议用
max-age=300(5分钟)临时验证,避免误配导致本地无法访问 -
includeSubDomains意味着api.example.com、static.example.com等也受约束,没部署HTTPS的子域名会直接失败
preload 参数不是加了就进预加载列表
preload 只是告诉浏览器“我愿意申请加入HSTS预加载列表”,不等于自动收录。真正生效需要:
- 域名必须满足
includeSubDomains+max-age >= 31536000(1年) - 所有子域名必须可HTTPS访问并返回相同HSTS头
- 主动提交到 hstspreload.org,人工审核通过后才写入浏览器发行版
- 未通过审核前,
preload字段对普通用户无实际作用
很多团队误以为加上 preload 就万事大吉,结果上线后发现部分子域名仍走HTTP,其实是预加载根本没生效。
中间件里漏掉 c.Next() 会导致HSTS失效
HSTS头必须出现在最终响应里,而不仅仅是中间件执行阶段。常见错误是在设置完头后忘记调用 c.Next(),导致后续handler没执行,响应体为空或出错,HSTS头虽写了但响应不完整。
典型错误写法:
func SecurityMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.TLS != nil || c.GetHeader("X-Forwarded-Proto") == "https" {
c.Header("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload")
// ❌ 忘记 c.Next(),请求链中断
}
}
}
正确顺序永远是:设置头 → c.Next() → 返回。否则中间件成了“拦截器”而非“增强器”。


















