Skip to content
Go
Gin
安全

安全

本章涵盖 Gin 应用的安全加固措施:安全头、CORS、CSRF、限流、输入验证、XSS 防护、可信代理和 HTTPS/TLS。


安全响应头

添加安全头是加固 Web 应用最简单也最有效的手段之一。

各安全头的作用

防护类型
X-Content-Type-Options: nosniff MIME 类型嗅探攻击
X-Frame-Options: DENY 点击劫持(iframe 嵌套)
Content-Security-Policy XSS 和数据注入
X-XSS-Protection: 1; mode=block 浏览器内置 XSS 过滤(旧浏览器)
Strict-Transport-Security 协议降级和 Cookie 劫持
Referrer-Policy: strict-origin URL 参数泄露给第三方
Permissions-Policy 浏览器 API(摄像头、麦克风等)滥用
func SecurityHeaders() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Header("X-Frame-Options", "DENY")
        c.Header("X-Content-Type-Options", "nosniff")
        c.Header("X-XSS-Protection", "1; mode=block")
        c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
        c.Header("Content-Security-Policy", "default-src 'self'")
        c.Header("Referrer-Policy", "strict-origin")
        c.Header("Permissions-Policy", "geolocation=(), camera=(), microphone=()")
        c.Next()
    }
}

Host Header 防护

防止 Host Header 注入攻击(SSRF、开放重定向):

r.Use(func(c *gin.Context) {
    expectedHost := "api.example.com"
    if c.Request.Host != expectedHost {
        c.AbortWithStatusJSON(400, gin.H{"error": "invalid host header"})
        return
    }
    c.Next()
})

使用 gin-helmet

gin-helmet 提供了预设的安全头中间件:

import ginhelmet "github.com/danielkov/gin-helmet/ginhelmet"

func main() {
    r := gin.Default()
    r.Use(ginhelmet.Default()) // 一键添加所有安全头
}

CORS 配置

跨源资源共享(CORS)控制哪些外部域可以向你的 API 发出请求。

使用 gin-contrib/cors

go get github.com/gin-contrib/cors
import (
    "time"
    "github.com/gin-contrib/cors"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    r.Use(cors.New(cors.Config{
        AllowOrigins:     []string{"https://example.com"},
        AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
        AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
        ExposeHeaders:    []string{"Content-Length"},
        AllowCredentials: true,
        MaxAge:           12 * time.Hour, // 预检请求缓存时间
    }))

    r.Run()
}
配置项 说明
AllowOrigins 允许的请求来源域名
AllowMethods 允许的 HTTP 方法
AllowHeaders 允许客户端携带的自定义请求头
ExposeHeaders 前端 JS 可读取的响应头
AllowCredentials 是否允许携带 Cookie/HTTP 认证
MaxAge 预检请求结果缓存时间

🚨 严重陷阱永远不要AllowOrigins: []string{"*"}AllowCredentials: true 一起使用。浏览器强制规定:当 AllowCredentialstrue 时,AllowOrigins 不能是通配符 *。这是 CORS 规范的核心安全限制。


CSRF 保护

跨站请求伪造(CSRF)会诱骗已认证用户的浏览器向你的应用发送非预期的请求。

使用 gin-contrib/csrf

import (
    "github.com/gin-contrib/csrf"
    "github.com/gin-contrib/sessions"
    "github.com/gin-contrib/sessions/cookie"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    // CSRF 需要会话支持
    store := cookie.NewStore([]byte("session-secret"))
    r.Use(sessions.Sessions("mysession", store))

    r.Use(csrf.Middleware(csrf.Options{
        Secret: "csrf-token-secret",
        ErrorFunc: func(c *gin.Context) {
            c.String(403, "CSRF token mismatch")
            c.Abort()
        },
    }))

    r.GET("/form", func(c *gin.Context) {
        token := csrf.GetToken(c)
        c.JSON(200, gin.H{"csrf_token": token}) // 前端将此 token 放在表单中
    })

    r.POST("/form", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "submitted"})
    })

    r.Run()
}

💡 仅使用 Authorization 头(如 Bearer Token)的纯 API 不受 CSRF 攻击——浏览器不会自动附加这些头。如果你的应用依赖 Cookie 认证,CSRF 保护是必须的。


限流

限流可防止滥用、暴力攻击和资源耗尽。

基于令牌桶的按 IP 限流

使用 golang.org/x/time/rate 标准库扩展包:

import (
    "sync"
    "golang.org/x/time/rate"
)

func RateLimiter() gin.HandlerFunc {
    type client struct {
        limiter  *rate.Limiter
        lastSeen time.Time
    }

    var (
        mu      sync.Mutex
        clients = make(map[string]*client)
    )

    // 后台清理过期条目
    go func() {
        for {
            time.Sleep(time.Minute)
            mu.Lock()
            for ip, c := range clients {
                if time.Since(c.lastSeen) > 3*time.Minute {
                    delete(clients, ip)
                }
            }
            mu.Unlock()
        }
    }()

    return func(c *gin.Context) {
        ip := c.ClientIP()

        mu.Lock()
        if _, exists := clients[ip]; !exists {
            // 每秒填充 10 个令牌,桶容量 20
            clients[ip] = &client{
                limiter: rate.NewLimiter(10, 20),
            }
        }
        cl := clients[ip]
        cl.lastSeen = time.Now()
        mu.Unlock()

        if !cl.limiter.Allow() {
            c.AbortWithStatusJSON(429, gin.H{
                "error": "rate limit exceeded",
            })
            return
        }
        c.Next()
    }
}
参数 含义
rate.NewLimiter(10, 20) 每秒填充 10 个令牌,桶容量 20
Allow() 消费 1 个令牌,token 不足时返回 false
桶容量 允许的最大突发流量

🚨 陷阱:内存中的 limiter map 不会自动清理。生产环境需要定时清理过期条目。多实例部署时需要用 Redis 等实现分布式限流。


输入验证与 SQL 注入防护

使用结构体标签验证

type CreateUser struct {
    Username string `json:"username" binding:"required,alphanum,min=3,max=30"`
    Email    string `json:"email"    binding:"required,email"`
    Age      int    `json:"age"      binding:"required,gte=1,lte=130"`
}

func createUser(c *gin.Context) {
    var req CreateUser
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    // req 已通过所有验证
}

参数化查询

// ❌ 危险 — SQL 注入风险
row := db.QueryRow(
    "SELECT id FROM users WHERE username = '" + username + "'",
)

// ✅ 安全 — 参数占位符
row := db.QueryRow(
    "SELECT id FROM users WHERE username = $1", username,
)

💡 输入验证和参数化查询是抵御注入攻击最重要的两道防线——二者缺一不可。


XSS 防护

跨站脚本(XSS)防护需要多层防御:

1. 使用 html/template 自动转义

Go 的 html/template 默认转义输出。使用 c.JSON() 而非 c.String() 返回数据:

// ✅ 安全:JSON 不会被浏览器解释为 HTML
c.JSON(200, gin.H{"input": userInput})

// ❌ 危险:直接拼接 HTML
c.String(200, "<div>" + userInput + "</div>")

2. SecureJSON 防 JSON 劫持

c.SecureJSON(200, gin.H{"data": userInput})
// → while(1);{"data":"..."}

3. 设置正确的 Content-Type

c.Header("Content-Type", "application/json; charset=utf-8")
c.Header("X-Content-Type-Options", "nosniff")

💡 nosniff 头阻止浏览器进行 MIME 类型嗅探,防止将 JSON 响应错误解析为 HTML。


可信代理

当应用位于反向代理(Nginx、CDN、云负载均衡器)之后时,必须配置可信代理。

为什么需要

攻击者可以伪造 X-Forwarded-For 头来:

  • 绕过 IP 访问控制 — 伪造内部 IP 访问受限路由
  • 污染日志 — 使安全审计失效
  • 规避限流 — 每次请求伪造不同 IP

配置

// 仅信任已知代理
r.SetTrustedProxies([]string{"10.0.0.1", "192.168.1.0/24"})

// 无代理场景:完全禁用,提升性能
r.SetTrustedProxies(nil)

CDN 平台快捷方式

r.TrustedPlatform = gin.PlatformGoogleAppEngine  // Google
r.TrustedPlatform = gin.PlatformCloudflare       // Cloudflare
r.TrustedPlatform = gin.PlatformFlyIO            // Fly.io
r.TrustedPlatform = "X-CDN-Client-IP"            // 自定义 CDN 头

🚨 陷阱:如果不显式配置 SetTrustedProxies,Gin 默认信任所有代理——这是不安全的。上线前务必配置。


HTTPS 和 TLS

所有生产环境的 Gin 应用都应通过 HTTPS 提供服务。

使用 Let’s Encrypt

import "github.com/gin-gonic/autotls"

func main() {
    r := gin.Default()
    log.Fatal(autotls.Run(r, "example.com"))
}

自定义证书管理器

import "golang.org/x/crypto/acme/autocert"

func main() {
    r := gin.Default()

    m := autocert.Manager{
        Prompt:     autocert.AcceptTOS,
        HostPolicy: autocert.HostWhitelist("example.com"),
        Cache:      autocert.DirCache("/var/www/.cache"),
    }

    log.Fatal(autotls.RunWithManager(r, &m))
}

使用已有证书

r.RunTLS(":443", "cert.pem", "key.pem")

🚨 Let’s Encrypt 要求服务器可从公网通过 80 和 443 端口访问,这在本地开发环境无效。


安全配置检查清单

  • GIN_MODE=release(生产环境)
  • 配置 SetTrustedProxies
  • 启用安全响应头(X-Frame-Options、CSP 等)
  • 配置 CORS 且不使用 * + AllowCredentials
  • Cookie 设置 Secure: trueHttpOnly: true
  • 所有用户输入使用结构体标签验证
  • 数据库查询使用参数化查询
  • 启用限流中间件
  • HTTPS + HSTS 头
  • X-Content-Type-Options: nosniff
  • 日志中不记录敏感数据(密码、Token)