Announcement

👇Official Account👇

图片

Welcome to join the group & private message

Article first/tail QR code

Skip to content

Golang Web 应用完整安全指南

Web 应用面临各种安全威胁。本文提供 Go Web 应用的完整安全防护方案。

一、认证与授权

1.1 JWT 认证

go
package auth

import (
    "time"
    "github.com/golang-jwt/jwt/v5"
)

type Claims struct {
    UserID   int64  `json:"user_id"`
    Username string `json:"username"`
    Role     string `json:"role"`
    jwt.RegisteredClaims
}

func GenerateToken(userID int64, username, role string, secret []byte) (string, error) {
    claims := Claims{
        UserID:   userID,
        Username: username,
        Role:     role,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            NotBefore: jwt.NewNumericDate(time.Now()),
        },
    }
    
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(secret)
}

func ParseToken(tokenString string, secret []byte) (*Claims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
        }
        return secret, nil
    })
    
    if err != nil {
        return nil, err
    }
    
    if claims, ok := token.Claims.(*Claims); ok && token.Valid {
        return claims, nil
    }
    
    return nil, fmt.Errorf("invalid token")
}

1.2 RBAC 授权

go
package auth

type Permission string

const (
    PermUserRead   Permission = "user:read"
    PermUserWrite  Permission = "user:write"
    PermAdmin      Permission = "admin"
)

type Role struct {
    Name        string
    Permissions []Permission
}

var roles = map[string]Role{
    "user": {
        Name:        "user",
        Permissions: []Permission{PermUserRead},
    },
    "admin": {
        Name:        "admin",
        Permissions: []Permission{PermUserRead, PermUserWrite, PermAdmin},
    },
}

func HasPermission(role string, permission Permission) bool {
    r, ok := roles[role]
    if !ok {
        return false
    }
    
    for _, p := range r.Permissions {
        if p == permission {
            return true
        }
    }
    return false
}

// Gin 中间件
func AuthMiddleware(secret []byte) gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenString := c.GetHeader("Authorization")
        if tokenString == "" {
            c.AbortWithStatusJSON(401, gin.H{"error": "missing token"})
            return
        }
        
        // 移除 "Bearer " 前缀
        tokenString = strings.TrimPrefix(tokenString, "Bearer ")
        
        claims, err := ParseToken(tokenString, secret)
        if err != nil {
            c.AbortWithStatusJSON(401, gin.H{"error": "invalid token"})
            return
        }
        
        // 将用户信息存入 context
        c.Set("user_id", claims.UserID)
        c.Set("username", claims.Username)
        c.Set("role", claims.Role)
        
        c.Next()
    }
}

func RequirePermission(permission Permission) gin.HandlerFunc {
    return func(c *gin.Context) {
        role, _ := c.Get("role")
        if !HasPermission(role.(string), permission) {
            c.AbortWithStatusJSON(403, gin.H{"error": "forbidden"})
            return
        }
        c.Next()
    }
}

二、输入验证

go
package validator

import (
    "github.com/go-playground/validator/v10"
)

var validate = validator.New()

type LoginRequest struct {
    Email    string `json:"email" validate:"required,email"`
    Password string `json:"password" validate:"required,min=8"`
}

type RegisterRequest struct {
    Username string `json:"username" validate:"required,min=3,max=20,alphanum"`
    Email    string `json:"email" validate:"required,email"`
    Password string `json:"password" validate:"required,min=8,containsany=ABCDEFGHIJKLMNOPQRSTUVWXYZ,containsany=abcdefghijklmnopqrstuvwxyz,containsany=0123456789"`
    Age      int    `json:"age" validate:"gte=13,lte=120"`
}

func ValidateStruct(s interface{}) error {
    return validate.Struct(s)
}

// 自定义验证器
func init() {
    validate.RegisterValidation("strong_password", validateStrongPassword)
}

func validateStrongPassword(fl validator.FieldLevel) bool {
    password := fl.Field().String()
    
    hasUpper := false
    hasLower := false
    hasNumber := false
    hasSpecial := false
    
    for _, char := range password {
        switch {
        case 'A' <= char && char <= 'Z':
            hasUpper = true
        case 'a' <= char && char <= 'z':
            hasLower = true
        case '0' <= char && char <= '9':
            hasNumber = true
        default:
            hasSpecial = true
        }
    }
    
    return hasUpper && hasLower && hasNumber && hasSpecial
}

三、CSRF 防护

go
package middleware

import (
    "crypto/rand"
    "encoding/base64"
    "github.com/gin-gonic/gin"
)

func CSRFMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 生成 CSRF Token
        if c.Request.Method == "GET" {
            token := generateCSRFToken()
            c.SetCookie("csrf_token", token, 3600, "/", "", true, true)
            c.Set("csrf_token", token)
            c.Next()
            return
        }
        
        // 验证 CSRF Token
        if c.Request.Method == "POST" || c.Request.Method == "PUT" || c.Request.Method == "DELETE" {
            cookieToken, _ := c.Cookie("csrf_token")
            headerToken := c.GetHeader("X-CSRF-Token")
            
            if cookieToken == "" || headerToken == "" || cookieToken != headerToken {
                c.AbortWithStatusJSON(403, gin.H{"error": "invalid csrf token"})
                return
            }
        }
        
        c.Next()
    }
}

func generateCSRFToken() string {
    b := make([]byte, 32)
    rand.Read(b)
    return base64.URLEncoding.EncodeToString(b)
}

四、安全头设置

go
package middleware

func SecurityHeaders() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Header("X-Content-Type-Options", "nosniff")
        c.Header("X-Frame-Options", "DENY")
        c.Header("X-XSS-Protection", "1; mode=block")
        c.Header("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'")
        c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
        c.Header("Referrer-Policy", "strict-origin-when-cross-origin")
        c.Header("Permissions-Policy", "geolocation=(), microphone=(), camera=()")
        
        c.Next()
    }
}

五、速率限制

go
package middleware

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

type IPRateLimiter struct {
    visitors map[string]*rate.Limiter
    mu       sync.RWMutex
    rate     rate.Limit
    burst    int
}

func NewIPRateLimiter(r rate.Limit, b int) *IPRateLimiter {
    return &IPRateLimiter{
        visitors: make(map[string]*rate.Limiter),
        rate:     r,
        burst:    b,
    }
}

func (i *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
    i.mu.Lock()
    defer i.mu.Unlock()
    
    limiter, exists := i.visitors[ip]
    if !exists {
        limiter = rate.NewLimiter(i.rate, i.burst)
        i.visitors[ip] = limiter
    }
    
    return limiter
}

func RateLimiterMiddleware() gin.HandlerFunc {
    limiter := NewIPRateLimiter(1, 5) // 每秒 1 个请求,突发 5 个
    
    return func(c *gin.Context) {
        ip := c.ClientIP()
        l := limiter.GetLimiter(ip)
        
        if !l.Allow() {
            c.AbortWithStatusJSON(429, gin.H{"error": "too many requests"})
            return
        }
        
        c.Next()
    }
}

六、总结

安全措施实现方式重要性
JWT 认证jwt-go
RBAC 授权自定义中间件
输入验证go-playground/validator
CSRF 防护Token 验证
安全头中间件
速率限制golang.org/x/time/rate

构建安全的 Web 应用需要多层防护,不能只依赖单一措施。

上次更新于: