跳转到内容

安全最佳实践

Web 应用是攻击者的主要目标。处理用户输入、存储数据或在反向代理后运行的 Gin 应用在进入生产环境之前需要有意的安全配置。本指南涵盖了最重要的防御措施,并展示如何使用 Gin 中间件和标准 Go 库来应用每项措施。

CORS 配置

跨源资源共享(CORS)控制哪些外部域可以向你的 API 发出请求。配置错误的 CORS 可以允许恶意网站代表已认证用户读取你服务器的响应。

使用 gin-contrib/cors 包获得经过充分测试的解决方案。

package main
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"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
r.GET("/api/data", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "ok"})
})
r.Run()
}

CSRF 保护

跨站请求伪造会诱骗已认证用户的浏览器向你的应用发送不需要的请求。任何依赖 cookie 进行认证的状态变更端点(POST、PUT、DELETE)都需要 CSRF 保护。

使用 gin-contrib/csrf 中间件添加基于令牌的保护。

package main
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()
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})
})
r.POST("/form", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "submitted"})
})
r.Run()
}

限流

限流可以防止滥用、暴力攻击和资源耗尽。你可以使用标准库的 golang.org/x/time/rate 包构建一个简单的按客户端限流中间件。

package main
import (
"net/http"
"sync"
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
)
func RateLimiter() gin.HandlerFunc {
type client struct {
limiter *rate.Limiter
}
var (
mu sync.Mutex
clients = make(map[string]*client)
)
return func(c *gin.Context) {
ip := c.ClientIP()
mu.Lock()
if _, exists := clients[ip]; !exists {
// Allow 10 requests per second with a burst of 20
clients[ip] = &client{limiter: rate.NewLimiter(10, 20)}
}
cl := clients[ip]
mu.Unlock()
if !cl.limiter.Allow() {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
"error": "rate limit exceeded",
})
return
}
c.Next()
}
}
func main() {
r := gin.Default()
r.Use(RateLimiter())
r.GET("/api/resource", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "ok"})
})
r.Run()
}

输入验证和 SQL 注入防护

始终使用 Gin 的模型绑定和结构体标签来验证和绑定输入。永远不要通过拼接用户输入来构造 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 createUserHandler(c *gin.Context) {
var req CreateUser
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// req is now validated; proceed safely
}

使用参数化查询

// DANGEROUS -- SQL injection vulnerability
row := db.QueryRow("SELECT id FROM users WHERE username = '" + username + "'")
// SAFE -- parameterized query
row := db.QueryRow("SELECT id FROM users WHERE username = $1", username)

这适用于每个数据库库。无论你使用 database/sql、GORM、sqlx 还是其他 ORM,都要使用参数占位符而不是字符串拼接。

XSS 防护

跨站脚本(XSS)发生在攻击者注入恶意脚本并在其他用户的浏览器中执行时。在多个层面进行防御。

转义 HTML 输出

渲染 HTML 模板时,Go 的 html/template 包默认转义输出。如果你将用户提供的数据作为 JSON 返回,请确保正确设置 Content-Type 头,以便浏览器不会将 JSON 解释为 HTML。

// Gin sets Content-Type automatically for JSON responses.
// Use c.JSON, not c.String, when returning structured data.
c.JSON(200, gin.H{"input": userInput})

使用 SecureJSON 进行 JSONP 保护

Gin 提供了 c.SecureJSON,它在前面添加 while(1); 以防止 JSON 劫持。

c.SecureJSON(200, gin.H{"data": userInput})

需要时显式设置 Content-Type

// For API endpoints, always return JSON
c.Header("Content-Type", "application/json; charset=utf-8")
c.Header("X-Content-Type-Options", "nosniff")

X-Content-Type-Options: nosniff 头可以防止浏览器进行 MIME 类型嗅探,阻止它们在服务器声明为其他类型时将响应解释为 HTML。

安全头中间件

添加安全头是最简单也是最有效的加固步骤之一。详细示例请参阅安全头页面。以下是基本头的简要概述。

func SecurityHeaders() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("X-Frame-Options", "DENY")
c.Header("X-Content-Type-Options", "nosniff")
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()
}
}
防护内容
X-Frame-Options: DENY通过 iframe 的点击劫持
X-Content-Type-Options: nosniffMIME 类型嗅探攻击
Strict-Transport-Security协议降级和 cookie 劫持
Content-Security-PolicyXSS 和数据注入
Referrer-Policy向第三方泄露敏感 URL 参数
Permissions-Policy未授权使用浏览器 API(摄像头、麦克风等)

可信代理

当你的应用在反向代理或负载均衡器后运行时,你必须告诉 Gin 信任哪些代理。没有此配置,攻击者可以伪造 X-Forwarded-For 头以绕过基于 IP 的访问控制和限流。

// Trust only your known proxy addresses
router.SetTrustedProxies([]string{"10.0.0.1", "192.168.1.0/24"})

详细说明和配置选项请参阅可信代理页面。

HTTPS 和 TLS

所有生产环境的 Gin 应用都应该通过 HTTPS 提供流量。Gin 支持通过 Let’s Encrypt 自动获取 TLS 证书。

import "github.com/gin-gonic/autotls"
func main() {
r := gin.Default()
// ... routes ...
log.Fatal(autotls.Run(r, "example.com"))
}

完整的设置说明(包括自定义证书管理器)请参阅支持 Let’s Encrypt 页面。

另请参阅