Go 安全编程:防范常见漏洞

全面介绍 Go 语言的安全编程实践,包括输入验证、SQL 注入防护、XSS 防御、密码存储和 JWT 安全

Go 安全编程:防范常见漏洞

安全是软件开发中最容易被忽视,却又最重要的环节。你可能写出功能完美、性能卓越的代码,但只要一个安全漏洞,就可能让整个系统崩溃,甚至导致用户数据泄露。

今天,我们就来深入探讨 Go 语言中的安全编程实践,学习如何防范常见的安全漏洞。

输入验证:第一道防线

永远不要信任用户输入。这是安全编程的黄金法则。

基础验证

package main

import (
    "errors"
    "regexp"
    "strings"
    "unicode/utf8"
)

// ValidationError 验证错误
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return e.Field + ": " + e.Message
}

// Validator 输入验证器
type Validator struct {
    errors []ValidationError
}

// NewValidator 创建验证器
func NewValidator() *Validator {
    return &Validator{}
}

// Check 检查条件
func (v *Validator) Check(field string, condition bool, message string) {
    if !condition {
        v.errors = append(v.errors, ValidationError{
            Field:   field,
            Message: message,
        })
    }
}

// HasErrors 是否有错误
func (v *Validator) HasErrors() bool {
    return len(v.errors) > 0
}

// Errors 获取所有错误
func (v *Validator) Errors() []ValidationError {
    return v.errors
}

// ValidateEmail 验证邮箱格式
func ValidateEmail(email string) error {
    v := NewValidator()
    
    email = strings.TrimSpace(email)
    v.Check("email", email != "", "邮箱不能为空")
    v.Check("email", utf8.RuneCountInString(email) <= 254, "邮箱长度不能超过 254 个字符")
    
    emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
    v.Check("email", emailRegex.MatchString(email), "邮箱格式不正确")
    
    if v.HasErrors() {
        return errors.New(v.Errors()[0].Message)
    }
    return nil
}

// ValidatePassword 验证密码强度
func ValidatePassword(password string) error {
    v := NewValidator()
    
    v.Check("password", len(password) >= 8, "密码长度至少 8 位")
    v.Check("password", len(password) <= 72, "密码长度不能超过 72 位")
    
    hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password)
    hasLower := regexp.MustCompile(`[a-z]`).MatchString(password)
    hasNumber := regexp.MustCompile(`[0-9]`).MatchString(password)
    hasSpecial := regexp.MustCompile(`[!@#$%^&*(),.?":{}|<>]`).MatchString(password)
    
    v.Check("password", hasUpper, "密码必须包含大写字母")
    v.Check("password", hasLower, "密码必须包含小写字母")
    v.Check("password", hasNumber, "密码必须包含数字")
    v.Check("password", hasSpecial, "密码必须包含特殊字符")
    
    if v.HasErrors() {
        return errors.New(v.Errors()[0].Message)
    }
    return nil
}

func main() {
    // 测试邮箱验证
    if err := ValidateEmail("user@example.com"); err != nil {
        println("邮箱验证失败:", err.Error())
    }
    
    // 测试密码验证
    if err := ValidatePassword("MyPass123!"); err != nil {
        println("密码验证失败:", err.Error())
    }
}

防止路径遍历攻击

package main

import (
    "errors"
    "path/filepath"
    "strings"
)

// SafeFileAccess 安全的文件访问
func SafeFileAccess(basePath, userPath string) (string, error) {
    // 清理路径
    cleanPath := filepath.Clean(userPath)
    
    // 检查是否包含可疑字符
    if strings.Contains(cleanPath, "..") {
        return "", errors.New("invalid path: contains '..'")
    }
    
    // 构建完整路径
    fullPath := filepath.Join(basePath, cleanPath)
    
    // 确保最终路径在基础目录内
    absBase, _ := filepath.Abs(basePath)
    absFull, _ := filepath.Abs(fullPath)
    
    if !strings.HasPrefix(absFull, absBase) {
        return "", errors.New("access denied: path outside base directory")
    }
    
    return absFull, nil
}

func main() {
    basePath := "/var/www/uploads"
    
    // 安全的路径
    path1, err := SafeFileAccess(basePath, "images/avatar.jpg")
    println(path1, err)
    
    // 危险的路径(路径遍历攻击)
    path2, err := SafeFileAccess(basePath, "../../etc/passwd")
    println(path2, err)
}

SQL 注入防护

SQL 注入是最常见也最危险的攻击之一。让我们看看如何在 Go 中防范。

错误的做法

// ❌ 危险:直接拼接 SQL
func getUserByIDUnsafe(db *sql.DB, id string) (*User, error) {
    query := "SELECT id, name, email FROM users WHERE id = " + id
    row := db.QueryRow(query)
    
    var user User
    err := row.Scan(&user.ID, &user.Name, &user.Email)
    return &user, err
}

// 攻击者可以传入: 1 OR 1=1
// 结果: SELECT id, name, email FROM users WHERE id = 1 OR 1=1
// 这会返回所有用户!

正确的做法

package main

import (
    "context"
    "database/sql"
    "fmt"
    _ "github.com/lib/pq"
)

// User 用户结构
type User struct {
    ID    int64
    Name  string
    Email string
}

// UserRepository 用户数据访问
type UserRepository struct {
    db *sql.DB
}

// NewUserRepository 创建用户仓库
func NewUserRepository(db *sql.DB) *UserRepository {
    return &UserRepository{db: db}
}

// GetByID 通过 ID 查询用户(安全)
func (r *UserRepository) GetByID(ctx context.Context, id int64) (*User, error) {
    // ✅ 使用参数化查询
    query := "SELECT id, name, email FROM users WHERE id = $1"
    row := r.db.QueryRowContext(ctx, query, id)
    
    var user User
    err := row.Scan(&user.ID, &user.Name, &user.Email)
    if err != nil {
        if err == sql.ErrNoRows {
            return nil, nil
        }
        return nil, err
    }
    
    return &user, nil
}

// Search 搜索用户(安全)
func (r *UserRepository) Search(ctx context.Context, keyword string) ([]User, error) {
    // ✅ 使用参数化查询
    query := "SELECT id, name, email FROM users WHERE name LIKE $1 OR email LIKE $1"
    rows, err := r.db.QueryContext(ctx, query, "%"+keyword+"%")
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    var users []User
    for rows.Next() {
        var user User
        if err := rows.Scan(&user.ID, &user.Name, &user.Email); err != nil {
            return nil, err
        }
        users = append(users, user)
    }
    
    return users, nil
}

// BatchGet 批量查询用户(安全)
func (r *UserRepository) BatchGet(ctx context.Context, ids []int64) ([]User, error) {
    if len(ids) == 0 {
        return nil, nil
    }
    
    // ✅ 动态构建参数占位符
    query := "SELECT id, name, email FROM users WHERE id = ANY($1)"
    
    // PostgreSQL 支持数组参数
    rows, err := r.db.QueryContext(ctx, query, pq.Array(ids))
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    var users []User
    for rows.Next() {
        var user User
        if err := rows.Scan(&user.ID, &user.Name, &user.Email); err != nil {
            return nil, err
        }
        users = append(users, user)
    }
    
    return users, nil
}

func main() {
    db, err := sql.Open("postgres", "postgres://user:pass@localhost/mydb?sslmode=disable")
    if err != nil {
        panic(err)
    }
    defer db.Close()
    
    repo := NewUserRepository(db)
    
    // 安全的查询
    user, err := repo.GetByID(context.Background(), 1)
    if err != nil {
        panic(err)
    }
    fmt.Printf("User: %+v\n", user)
}

XSS 防护(跨站脚本攻击)

XSS 攻击允许攻击者在用户的浏览器中执行恶意脚本。

HTML 转义

package main

import (
    "html/template"
    "net/http"
    "strings"
)

// SanitizeInput 清理用户输入
func SanitizeInput(input string) string {
    // 移除危险字符
    input = strings.ReplaceAll(input, "<script", "&lt;script")
    input = strings.ReplaceAll(input, "</script", "&lt;/script")
    input = strings.ReplaceAll(input, "<", "&lt;")
    input = strings.ReplaceAll(input, ">", "&gt;")
    input = strings.ReplaceAll(input, "\"", "&quot;")
    input = strings.ReplaceAll(input, "'", "&#x27;")
    return input
}

// SafeHandler 安全的 HTTP 处理器
func SafeHandler(w http.ResponseWriter, r *http.Request) {
    userInput := r.URL.Query().Get("name")
    
    // ✅ 方法 1: 使用 html/template 自动转义
    tmpl := template.Must(template.New("page").Parse(`
        <!DOCTYPE html>
        <html>
        <head><title>Hello</title></head>
        <body>
            <h1>Hello, {{.Name}}!</h1>
        </body>
        </html>
    `))
    
    data := struct {
        Name string
    }{
        Name: userInput, // template 会自动转义
    }
    
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    tmpl.Execute(w, data)
}

// UnsafeHandler 不安全的 HTTP 处理器(反面教材)
func UnsafeHandler(w http.ResponseWriter, r *http.Request) {
    userInput := r.URL.Query().Get("name")
    
    // ❌ 危险:直接输出用户输入
    html := fmt.Sprintf(`
        <!DOCTYPE html>
        <html>
        <head><title>Hello</title></head>
        <body>
            <h1>Hello, %s!</h1>
        </body>
        </html>
    `, userInput)
    
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    w.Write([]byte(html))
    
    // 攻击者可以传入: <script>alert('XSS')</script>
}

func main() {
    http.HandleFunc("/safe", SafeHandler)
    http.HandleFunc("/unsafe", UnsafeHandler)
    http.ListenAndServe(":8080", nil)
}

Content Security Policy

package main

import (
    "net/http"
)

// CSPMiddleware 添加 Content Security Policy 头
func CSPMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 设置严格的 CSP
        csp := "default-src 'self'; " +
            "script-src 'self' 'unsafe-inline' https://trusted.cdn.com; " +
            "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " +
            "img-src 'self' data: https:; " +
            "font-src 'self' https://fonts.gstatic.com; " +
            "connect-src 'self' https://api.example.com; " +
            "frame-ancestors 'none'; " +
            "base-uri 'self'; " +
            "form-action 'self'"
        
        w.Header().Set("Content-Security-Policy", csp)
        
        // 其他安全头
        w.Header().Set("X-Content-Type-Options", "nosniff")
        w.Header().Set("X-Frame-Options", "DENY")
        w.Header().Set("X-XSS-Protection", "1; mode=block")
        w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
        
        next.ServeHTTP(w, r)
    })
}

func handler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello, World!"))
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", handler)
    
    // 应用 CSP 中间件
    http.ListenAndServe(":8080", CSPMiddleware(mux))
}

CSRF 防护(跨站请求伪造)

CSRF 攻击利用用户已登录的身份,在用户不知情的情况下执行恶意请求。

CSRF Token 实现

package main

import (
    "crypto/rand"
    "encoding/base64"
    "fmt"
    "html/template"
    "net/http"
    "sync"
    "time"
)

// CSRFToken CSRF 令牌
type CSRFToken struct {
    Token     string
    ExpiresAt time.Time
}

// CSRFStore CSRF 令牌存储
type CSRFStore struct {
    mu     sync.RWMutex
    tokens map[string]CSRFToken
}

// NewCSRFStore 创建 CSRF 存储
func NewCSRFStore() *CSRFStore {
    return &CSRFStore{
        tokens: make(map[string]CSRFToken),
    }
}

// GenerateToken 生成新的 CSRF 令牌
func (s *CSRFStore) GenerateToken(sessionID string) (string, error) {
    // 生成随机令牌
    bytes := make([]byte, 32)
    if _, err := rand.Read(bytes); err != nil {
        return "", err
    }
    token := base64.URLEncoding.EncodeToString(bytes)
    
    // 存储令牌
    s.mu.Lock()
    s.tokens[sessionID] = CSRFToken{
        Token:     token,
        ExpiresAt: time.Now().Add(1 * time.Hour),
    }
    s.mu.Unlock()
    
    return token, nil
}

// ValidateToken 验证 CSRF 令牌
func (s *CSRFStore) ValidateToken(sessionID, token string) bool {
    s.mu.RLock()
    defer s.mu.RUnlock()
    
    stored, exists := s.tokens[sessionID]
    if !exists {
        return false
    }
    
    // 检查是否过期
    if time.Now().After(stored.ExpiresAt) {
        return false
    }
    
    // 比较令牌
    return stored.Token == token
}

var csrfStore = NewCSRFStore()

// FormHandler 处理表单显示
func FormHandler(w http.ResponseWriter, r *http.Request) {
    // 从 session 获取 sessionID(这里简化处理)
    sessionID := "user-session-123"
    
    // 生成 CSRF 令牌
    token, err := csrfStore.GenerateToken(sessionID)
    if err != nil {
        http.Error(w, "Failed to generate token", 500)
        return
    }
    
    // 将令牌嵌入表单
    tmpl := template.Must(template.New("form").Parse(`
        <!DOCTYPE html>
        <html>
        <head><title>Transfer Money</title></head>
        <body>
            <form method="POST" action="/transfer">
                <input type="hidden" name="csrf_token" value="{{.Token}}">
                <label>To Account: <input type="text" name="to_account"></label><br>
                <label>Amount: <input type="number" name="amount"></label><br>
                <button type="submit">Transfer</button>
            </form>
        </body>
        </html>
    `))
    
    tmpl.Execute(w, struct{ Token string }{Token: token})
}

// TransferHandler 处理转账请求
func TransferHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        http.Error(w, "Method not allowed", 405)
        return
    }
    
    sessionID := "user-session-123"
    token := r.FormValue("csrf_token")
    
    // ✅ 验证 CSRF 令牌
    if !csrfStore.ValidateToken(sessionID, token) {
        http.Error(w, "Invalid CSRF token", 403)
        return
    }
    
    // 处理转账逻辑
    toAccount := r.FormValue("to_account")
    amount := r.FormValue("amount")
    
    fmt.Fprintf(w, "Transferred %s to account %s", amount, toAccount)
}

func main() {
    http.HandleFunc("/form", FormHandler)
    http.HandleFunc("/transfer", TransferHandler)
    http.ListenAndServe(":8080", nil)
}

密码安全存储

永远不要明文存储密码!

使用 bcrypt 加密

package main

import (
    "fmt"
    "golang.org/x/crypto/bcrypt"
)

// HashPassword 加密密码
func HashPassword(password string) (string, error) {
    // 使用默认成本因子 10
    bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    return string(bytes), err
}

// CheckPassword 验证密码
func CheckPassword(password, hash string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
    return err == nil
}

func main() {
    password := "MySecurePassword123!"
    
    // 加密密码
    hash, err := HashPassword(password)
    if err != nil {
        panic(err)
    }
    fmt.Println("Hashed password:", hash)
    
    // 验证正确密码
    if CheckPassword(password, hash) {
        fmt.Println("✅ Password is correct")
    }
    
    // 验证错误密码
    if !CheckPassword("WrongPassword", hash) {
        fmt.Println("❌ Password is incorrect")
    }
}

完整的用户认证系统

package main

import (
    "context"
    "database/sql"
    "errors"
    "golang.org/x/crypto/bcrypt"
    "time"
)

// User 用户结构
type User struct {
    ID           int64
    Email        string
    PasswordHash string
    CreatedAt    time.Time
}

// AuthRepository 认证数据访问
type AuthRepository struct {
    db *sql.DB
}

// NewAuthRepository 创建认证仓库
func NewAuthRepository(db *sql.DB) *AuthRepository {
    return &AuthRepository{db: db}
}

// CreateUser 创建用户(密码加密)
func (r *AuthRepository) CreateUser(ctx context.Context, email, password string) (*User, error) {
    // 加密密码
    hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    if err != nil {
        return nil, err
    }
    
    // 插入用户
    query := "INSERT INTO users (email, password_hash, created_at) VALUES ($1, $2, $3) RETURNING id"
    var id int64
    err = r.db.QueryRowContext(ctx, query, email, string(hash), time.Now()).Scan(&id)
    if err != nil {
        return nil, err
    }
    
    return &User{
        ID:           id,
        Email:        email,
        PasswordHash: string(hash),
    }, nil
}

// Authenticate 用户认证
func (r *AuthRepository) Authenticate(ctx context.Context, email, password string) (*User, error) {
    // 查询用户
    query := "SELECT id, email, password_hash, created_at FROM users WHERE email = $1"
    row := r.db.QueryRowContext(ctx, query, email)
    
    var user User
    err := row.Scan(&user.ID, &user.Email, &user.PasswordHash, &user.CreatedAt)
    if err != nil {
        if err == sql.ErrNoRows {
            return nil, errors.New("invalid email or password")
        }
        return nil, err
    }
    
    // 验证密码
    err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password))
    if err != nil {
        return nil, errors.New("invalid email or password")
    }
    
    return &user, nil
}

func main() {
    db, err := sql.Open("postgres", "postgres://user:pass@localhost/mydb?sslmode=disable")
    if err != nil {
        panic(err)
    }
    defer db.Close()
    
    repo := NewAuthRepository(db)
    
    // 创建用户
    user, err := repo.CreateUser(context.Background(), "user@example.com", "SecurePass123!")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created user: %+v\n", user)
    
    // 认证用户
    authedUser, err := repo.Authenticate(context.Background(), "user@example.com", "SecurePass123!")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Authenticated user: %+v\n", authedUser)
}

JWT 安全实践

JWT(JSON Web Token)常用于 API 认证,但使用不当会带来安全风险。

安全的 JWT 实现

package main

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

// Claims JWT 声明
type Claims struct {
    UserID int64  `json:"user_id"`
    Email  string `json:"email"`
    jwt.RegisteredClaims
}

// JWTManager JWT 管理器
type JWTManager struct {
    secretKey     []byte
    tokenDuration time.Duration
}

// NewJWTManager 创建 JWT 管理器
func NewJWTManager(secretKey string, tokenDuration time.Duration) *JWTManager {
    return &JWTManager{
        secretKey:     []byte(secretKey),
        tokenDuration: tokenDuration,
    }
}

// GenerateToken 生成 JWT 令牌
func (m *JWTManager) GenerateToken(userID int64, email string) (string, error) {
    claims := Claims{
        UserID: userID,
        Email:  email,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(m.tokenDuration)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            NotBefore: jwt.NewNumericDate(time.Now()),
            Issuer:    "myapp",
        },
    }
    
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(m.secretKey)
}

// ValidateToken 验证 JWT 令牌
func (m *JWTManager) ValidateToken(tokenString string) (*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 m.secretKey, nil
    })
    
    if err != nil {
        return nil, err
    }
    
    claims, ok := token.Claims.(*Claims)
    if !ok || !token.Valid {
        return nil, errors.New("invalid token")
    }
    
    // ✅ 验证发行者
    if claims.Issuer != "myapp" {
        return nil, errors.New("invalid issuer")
    }
    
    return claims, nil
}

func main() {
    // 使用强密钥(至少 32 字节)
    secretKey := "your-256-bit-secret-key-here-1234567890"
    manager := NewJWTManager(secretKey, 24*time.Hour)
    
    // 生成令牌
    token, err := manager.GenerateToken(123, "user@example.com")
    if err != nil {
        panic(err)
    }
    fmt.Println("Generated token:", token)
    
    // 验证令牌
    claims, err := manager.ValidateToken(token)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Token claims: UserID=%d, Email=%s\n", claims.UserID, claims.Email)
}

JWT 中间件

package main

import (
    "context"
    "net/http"
    "strings"
)

type contextKey string

const userContextKey contextKey = "user"

// AuthMiddleware JWT 认证中间件
func AuthMiddleware(jwtManager *JWTManager) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // 从 Authorization 头获取令牌
            authHeader := r.Header.Get("Authorization")
            if authHeader == "" {
                http.Error(w, "Missing authorization header", 401)
                return
            }
            
            // 验证 Bearer 格式
            parts := strings.SplitN(authHeader, " ", 2)
            if len(parts) != 2 || parts[0] != "Bearer" {
                http.Error(w, "Invalid authorization header format", 401)
                return
            }
            
            tokenString := parts[1]
            
            // 验证令牌
            claims, err := jwtManager.ValidateToken(tokenString)
            if err != nil {
                http.Error(w, "Invalid or expired token", 401)
                return
            }
            
            // 将用户信息存入上下文
            ctx := context.WithValue(r.Context(), userContextKey, claims)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// ProtectedHandler 受保护的处理器
func ProtectedHandler(w http.ResponseWriter, r *http.Request) {
    claims := r.Context().Value(userContextKey).(*Claims)
    fmt.Fprintf(w, "Hello, user %d (%s)!", claims.UserID, claims.Email)
}

func main() {
    jwtManager := NewJWTManager("your-secret-key", 24*time.Hour)
    
    mux := http.NewServeMux()
    mux.Handle("/protected", AuthMiddleware(jwtManager)(http.HandlerFunc(ProtectedHandler)))
    
    http.ListenAndServe(":8080", mux)
}

依赖安全审计

第三方依赖可能包含安全漏洞。定期审计依赖是必要的。

使用 govulncheck

# 安装 govulncheck
go install golang.org/x/vuln/cmd/govulncheck@latest

# 扫描项目依赖
govulncheck ./...

# 输出示例:
# Scanning your dependencies...
# Your code is affected by 1 vulnerability that we found in your imported symbols.
# 
# CVE-2023-XXXXX: Packages
#   - github.com/vulnerable/package@v1.0.0
#     Fixed in: v1.0.1
#     More info: https://pkg.go.dev/vuln/GO-2023-XXXX

自动化依赖更新

# .github/workflows/dependency-review.yml
name: Dependency Review

on:
  pull_request:
    paths:
      - 'go.mod'
      - 'go.sum'

jobs:
  dependency-review:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Set up Go
        uses: actions/setup-go@v4
        with:
          go-version: '1.21'
      
      - name: Install govulncheck
        run: go install golang.org/x/vuln/cmd/govulncheck@latest
      
      - name: Run govulncheck
        run: govulncheck ./...
      
      - name: Dependency Review
        uses: actions/dependency-review-action@v3
        with:
          fail-on-severity: high

TLS 配置

确保 HTTPS 配置安全:

package main

import (
    "crypto/tls"
    "net/http"
    "time"
)

func main() {
    // ✅ 安全的 TLS 配置
    tlsConfig := &tls.Config{
        // 最低 TLS 1.2
        MinVersion: tls.VersionTLS12,
        
        // 禁用弱密码套件
        CipherSuites: []uint16{
            tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
            tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
            tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
            tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
        },
        
        // 启用 HSTS
        CurvePreferences: []tls.CurveID{
            tls.X25519,
            tls.CurveP256,
        },
        
        // 优先使用服务器密码套件
        PreferServerCipherSuites: true,
    }
    
    server := &http.Server{
        Addr:         ":443",
        TLSConfig:    tlsConfig,
        ReadTimeout:  15 * time.Second,
        WriteTimeout: 15 * time.Second,
        IdleTimeout:  60 * time.Second,
    }
    
    // 启动 HTTPS 服务器
    server.ListenAndServeTLS("cert.pem", "key.pem")
}

总结

安全编程是一个持续的过程。今天我们学习了:

核心原则:

  • 输入验证:永远不信任用户输入
  • 参数化查询:防止 SQL 注入
  • 输出转义:防止 XSS 攻击
  • CSRF 令牌:防止跨站请求伪造

最佳实践:

  • 密码加密:使用 bcrypt,永远不明文存储
  • JWT 安全:验证签名、过期时间和发行者
  • 依赖审计:定期扫描第三方库漏洞
  • TLS 配置:使用强密码套件和最新协议

记住,安全不是一次性的工作,而是需要持续关注和改进的过程。定期更新依赖、进行安全审计、关注安全公告,这些都是保护应用安全的重要环节。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页