功能写完不加固,上线就是靶子。安全头防浏览器端攻击,CORS 管跨域信任,输入校验和参数化查库防注入。这一篇把 Gin 服务该有的几道安全防线一次补齐。
1. 安全响应头
中间件统一加,浏览器照此收紧行为:
func SecureHeaders() gin.HandlerFunc { return func(c *gin.Context) { c.Header("X-Content-Type-Options", "nosniff") c.Header("X-Frame-Options", "DENY") c.Header("Content-Security-Policy", "default-src 'self'") c.Header("Referrer-Policy", "no-referrer") c.Next() } }
nosniff 防 MIME 嗅探、X-Frame-Options 防被 iframe 嵌去钓鱼、CSP 限制资源来源。
2. CORS 正确姿势
第 3 篇提过 cors 中间件,生产必须显式白名单,别 *:
cors.New(cors.Config{ AllowOrigins: []string{"https://wxbuluo.com"}, AllowMethods: []string{"GET", "POST"}, AllowHeaders: []string{"Origin", "Content-Type", "Authorization"}, AllowCredentials: true, MaxAge: 12 * time.Hour, })
AllowCredentials: true 时 AllowOrigins 不能写 *,否则任何域都能带用户 cookie 打你接口。
3. 防 SQL 注入
全程用参数化查询(GORM、预编译),别拼字符串:
// 错:拼字符串,用户输入进 SQL db.Raw("SELECT * FROM users WHERE name = '" + name + "'") // 对:参数化,驱动转义 db.Where("name = ?", name).Find(&users)
第 7 篇的 GORM 作用域天然参数化,手写 Raw 时尤其小心。
4. 防 SSRF
服务端代发请求(抓网页、调内网)要校验目标地址,否则被当跳板打内网:
func safeGet(u string) (*http.Response, error) { p, err := url.Parse(u) if err != nil || (p.Scheme != "http" && p.Scheme != "https") { return nil, ErrBlocked } ip := net.ParseIP(p.Hostname()) if ip != nil && (ip.IsPrivate() || ip.IsLoopback()) { return nil, ErrBlocked } return http.Get(u) }
只允许 http/https,解析出的 IP 是私网/回环直接拒。
5. 限制请求体与上传
防大 body 打满内存:
r := gin.New() r.Use(gin.MaxMultipartMemory(8 << 20)) // 上传内存上限 8MB r.POST("/upload", func(c *gin.Context) { f, err := c.FormFile("file") if err != nil { c.JSON(400, gin.H{"error": "无文件"}) return } if f.Size > 10<<20 { c.JSON(413, gin.H{"error": "文件过大"}) return } })
MaxMultipartMemory 控内存,f.Size 再卡一道体积。
6. 校验放最外层
第 2/4 篇的 binding 和租户校验都在这层。安全校验也放最前,非法请求别进业务逻辑。所有用户输入当成不可信,输出到 HTML 时转义防 XSS。
7. 上线清单
安全头全站加,CSP 按资源来源收紧。CORS 显式白名单,带凭据时不写 *。查库全程参数化,禁字符串拼 SQL。服务端代发请求校验协议和 IP,防 SSRF 打内网。上传限大小、校验类型。所有输入不可信,输出转义。
十篇走完,进阶季从 GraphQL 一路补到安全加固,一套能上线、能扛量、能防攻击的 Gin 服务骨架齐了。