Go: Go 中间件与 JWT 认证

最后更新:2026-08-26

中间件是 Go 微服务的"管道"——认证、限流、log、恢复,每个横切关注点都是一个中间件。

当你需要在每个 API 端点检查 JWT 认证、记录日志、限制访问频率时,中间件模式让你写一次,处处生效。

1. 你将学到


2. 一个后端工程师的真实故事

(1) 痛点:每个路由手动检查 Token,忘了 3 次

Charlie 在 API 网关团队,负责所有端点的认证:

"老板要求所有 API 都要检查 JWT Token。我在每个 handler 里抄了 15 行 Token 验证代码。上线的第二天,我忘了在 /admin/users 里加认证——任何人可以直接删除用户。CTO 说'安全审计发现了一个严重漏洞'。"

GO
// 坏代码:每个 handler 手动检查
func deleteUser(w http.ResponseWriter, r *http.Request) {
    // 忘了写 Token 验证!严重安全漏洞
    userID := r.PathValue("id")
    deleteUserFromDB(userID)
}

func updateOrder(w http.ResponseWriter, r *http.Request) {
    token := r.Header.Get("Authorization")
    // 每次都抄 15 行验证代码
    if !validateToken(token) {
        http.Error(w, "unauthorized", 401)
        return
    }
    // 业务逻辑...
}

(2) Go 的解法:认证中间件

GO
// 好代码:认证中间件,一次实现处处生效
func AuthMiddleware(jwtSecret string) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            token := extractToken(r)
            claims, err := validateJWT(token, jwtSecret)
            if err != nil {
                writeError(w, http.StatusUnauthorized, "invalid token")
                return
            }
            // 把用户信息注入 Context
            ctx := context.WithValue(r.Context(), "user", claims)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// 注册时只需要一个 Middleware
mux.Handle("POST /api/orders", AuthMiddleware(secret)(orderHandler))

(3) 收益:手动 vs 中间件

维度 手动检查 中间件模式
漏检风险 每新增一个路由都可能忘
代码重复 每个 handler 15 行 0 行(handler 只关心业务)
修改逻辑 改 20 个 handler 改 1 个中间件
单元测试 每个 handler 都要测认证 只测中间件一次

3. JWT 认证

▶ 示例:JWT 生成与验证

GO 📖 仅展示
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "strings"
    "time"
)

// ---------- JWT 手动实现(不依赖第三方库)----------

type JWTClaims struct {
    UserID   int      `json:"user_id"`
    Role     string   `json:"role"`
    Permissions []string `json:"permissions"`
    ExpiresAt int64   `json:"exp"`
}

type JWTHeader struct {
    Alg string `json:"alg"`
    Typ string `json:"typ"`
}

func base64Encode(data []byte) string {
    return strings.TrimRight(base64.URLEncoding.EncodeToString(data), "=")
}

func base64Decode(s string) ([]byte, error) {
    // 补齐 padding
    switch len(s) % 4 {
    case 2:
        s += "=="
    case 3:
        s += "="
    }
    return base64.URLEncoding.DecodeString(s)
}

func createJWT(claims JWTClaims, secret string) (string, error) {
    header := JWTHeader{Alg: "HS256", Typ: "JWT"}
    headerJSON, _ := json.Marshal(header)
    claimsJSON, _ := json.Marshal(claims)

    headerEnc := base64Encode(headerJSON)
    claimsEnc := base64Encode(claimsJSON)

    // 签名
    message := headerEnc + "." + claimsEnc
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(message))
    signature := base64Encode(mac.Sum(nil))

    return message + "." + signature, nil
}

func validateJWT(token string, secret string) (*JWTClaims, error) {
    parts := strings.Split(token, ".")
    if len(parts) != 3 {
        return nil, fmt.Errorf("invalid token format")
    }

    // 验证签名
    message := parts[0] + "." + parts[1]
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(message))
    expectedSig := base64Encode(mac.Sum(nil))

    if !hmac.Equal([]byte(parts[2]), []byte(expectedSig)) {
        return nil, fmt.Errorf("invalid signature")
    }

    // 解析 claims
    claimsJSON, err := base64Decode(parts[1])
    if err != nil {
        return nil, err
    }

    var claims JWTClaims
    if err := json.Unmarshal(claimsJSON, &claims); err != nil {
        return nil, err
    }

    // 检查过期
    if time.Now().Unix() > claims.ExpiresAt {
        return nil, fmt.Errorf("token expired")
    }

    return &claims, nil
}

func main() {
    secret := "my-secret-key"

    // 生成 Token
    claims := JWTClaims{
        UserID:   1,
        Role:     "admin",
        ExpiresAt: time.Now().Add(1 * time.Hour).Unix(),
    }

    token, _ := createJWT(claims, secret)
    fmt.Printf("JWT: %s\n", token)

    // 验证 Token
    validated, err := validateJWT(token, secret)
    if err != nil {
        fmt.Printf("验证失败: %v\n", err)
    } else {
        fmt.Printf("验证成功: user=%d, role=%s\n", validated.UserID, validated.Role)
    }
}
逻辑代码 85 行(超过 40 行限制,仅展示)
💡 提示: 生产环境请使用成熟的 JWT library(github.com/golang-jwt/jwt/v5),不要手动实现。上述代码仅为说明 JWT 工作原理。第三方库处理了更多的边缘情况(如 token 类型、密钥轮换、标准 claims 验证)。


4. Access Token + Refresh Token

▶ 示例:双令牌认证

GO 📖 仅展示
package main

import (
    "crypto/rand"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "net/http"
    "sync"
    "time"
)

// ---------- Token 服务 ----------

type TokenService struct {
    secret     string
    refreshTTL time.Duration
    // 存储 refresh token(生产环境用 Redis)
    refreshTokens map[string]int // token → userID
    mu            sync.RWMutex
}

func NewTokenService(secret string) *TokenService {
    return &TokenService{
        secret:         secret,
        refreshTTL:     7 * 24 * time.Hour,
        refreshTokens:  make(map[string]int),
    }
}

func (s *TokenService) GenerateTokens(userID int, role string) (accessToken, refreshToken string, err error) {
    // Access Token(15 分钟过期)
    accessClaims := JWTClaims{
        UserID:    userID,
        Role:      role,
        ExpiresAt: time.Now().Add(15 * time.Minute).Unix(),
    }
    accessToken, err = createJWT(accessClaims, s.secret)
    if err != nil {
        return "", "", err
    }

    // Refresh Token(随机字符串,7 天过期)
    bytes := make([]byte, 32)
    rand.Read(bytes)
    refreshToken = hex.EncodeToString(bytes)

    s.mu.Lock()
    s.refreshTokens[refreshToken] = userID
    s.mu.Unlock()

    return accessToken, refreshToken, nil
}

func (s *TokenService) RefreshAccessToken(refreshToken string) (string, error) {
    s.mu.RLock()
    userID, exists := s.refreshTokens[refreshToken]
    s.mu.RUnlock()

    if !exists {
        return "", fmt.Errorf("invalid refresh token")
    }

    // 生成新的 Access Token
    claims := JWTClaims{
        UserID:    userID,
        Role:      "user",
        ExpiresAt: time.Now().Add(15 * time.Minute).Unix(),
    }
    return createJWT(claims, s.secret)
}

func (s *TokenService) RevokeRefreshToken(refreshToken string) {
    s.mu.Lock()
    delete(s.refreshTokens, refreshToken)
    s.mu.Unlock()
}

// ---------- Auth Handler ----------

type AuthHandler struct {
    tokenSvc *TokenService
}

type loginRequest struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

type tokenResponse struct {
    AccessToken  string `json:"access_token"`
    RefreshToken string `json:"refresh_token"`
    TokenType    string `json:"token_type"`
    ExpiresIn    int64  `json:"expires_in"`
}

func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
    var req loginRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        writeError(w, http.StatusBadRequest, "invalid JSON")
        return
    }

    // 验证用户名密码(生产环境用 bcrypt)
    if req.Username != "admin" || req.Password != "password" {
        writeError(w, http.StatusUnauthorized, "invalid credentials")
        return
    }

    accessToken, refreshToken, err := h.tokenSvc.GenerateTokens(1, "admin")
    if err != nil {
        writeError(w, http.StatusInternalServerError, "token generation failed")
        return
    }

    writeJSON(w, http.StatusOK, tokenResponse{
        AccessToken:  accessToken,
        RefreshToken: refreshToken,
        TokenType:    "Bearer",
        ExpiresIn:    900, // 15 分钟
    })
}

func (h *AuthHandler) Refresh(w http.ResponseWriter, r *http.Request) {
    var req struct {
        RefreshToken string `json:"refresh_token"`
    }
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        writeError(w, http.StatusBadRequest, "invalid JSON")
        return
    }

    accessToken, err := h.tokenSvc.RefreshAccessToken(req.RefreshToken)
    if err != nil {
        writeError(w, http.StatusUnauthorized, "invalid refresh token")
        return
    }

    writeJSON(w, http.StatusOK, map[string]string{"access_token": accessToken})
}

func main() {
    tokenSvc := NewTokenService("my-secret-key")
    handler := &AuthHandler{tokenSvc: tokenSvc}

    mux := http.NewServeMux()
    mux.HandleFunc("POST /login", handler.Login)
    mux.HandleFunc("POST /refresh", handler.Refresh)

    fmt.Println("Auth server starting on :8080")
    http.ListenAndServe(":8080", mux)
}
逻辑代码 119 行(超过 40 行限制,仅展示)
100%
sequenceDiagram
    participant Client
    participant API as API Gateway
    participant Auth as Auth Service

    Client->>API: POST /login (username, password)
    API->>Auth: 验证凭据
    Auth-->>API: access_token (15min) + refresh_token (7d)
    API-->>Client: 返回双令牌

    Client->>API: GET /orders (Bearer access_token)
    API->>API: 验证 access_token
    API-->>Client: 200 OK

    Client->>API: GET /orders (access_token 过期)
    API-->>Client: 401 Unauthorized

    Client->>API: POST /refresh (refresh_token)
    API->>Auth: 验证 refresh_token
    Auth-->>API: 新的 access_token
    API-->>Client: 返回新 access_token

5. RBAC 权限中间件

▶ 示例:RBAC 实现

GO 📖 仅展示
package main

import (
    "context"
    "fmt"
    "encoding/json"
    "net/http"
)

type UserRole string

const (
    RoleAdmin UserRole = "admin"
    RoleUser  UserRole = "user"
    RoleGuest UserRole = "guest"
)

// 权限定义
var rolePermissions = map[UserRole][]string{
    RoleAdmin: {"read:users", "write:users", "delete:users", "read:orders", "write:orders"},
    RoleUser:  {"read:orders", "write:orders"},
    RoleGuest: {"read:products"},
}

type Claims struct {
    UserID int
    Role   UserRole
}

// 从 Context 获取认证信息
func GetClaims(r *http.Request) *Claims {
    claims, _ := r.Context().Value("claims").(*Claims)
    return claims
}

// RBAC middleware
func RequirePermission(permission string) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            claims := GetClaims(r)
            if claims == nil {
                writeError(w, http.StatusUnauthorized, "not authenticated")
                return
            }

            permissions, exists := rolePermissions[claims.Role]
            if !exists {
                writeError(w, http.StatusForbidden, "no permissions defined for role")
                return
            }

            // 检查权限
            hasPermission := false
            for _, p := range permissions {
                if p == permission {
                    hasPermission = true
                    break
                }
            }

            if !hasPermission {
                writeError(w, http.StatusForbidden, "insufficient permissions")
                return
            }

            next.ServeHTTP(w, r)
        })
    }
}

// ---------- 路由注册 ----------

func setupRoutes() http.Handler {
    mux := http.NewServeMux()

    // 公开端点
    mux.HandleFunc("POST /login", loginHandler)

    // 需要认证的端点
    mux.Handle("GET /api/orders", RequirePermission("read:orders")(authMiddleware(http.HandlerFunc(listOrders))))
    mux.Handle("POST /api/orders", RequirePermission("write:orders")(authMiddleware(http.HandlerFunc(createOrder))))
    mux.Handle("DELETE /api/users/{id}", RequirePermission("delete:users")(authMiddleware(http.HandlerFunc(deleteUser))))

    return mux
}

func main() {
    fmt.Println("RBAC server starting on :8080")
    http.ListenAndServe(":8080", setupRoutes())
}
逻辑代码 66 行(超过 40 行限制,仅展示)

(1) 中间件管线

TEXT 📖 仅展示
Request → Auth Middleware → RBAC Middleware → Handler
              |                   |
         验证 JWT Token     检查 Role 权限
         注入 Claims       用户有权限?
层级 middleware 职责
1 Recovery 捕获 panic,返回 500
2 Logging 记录请求耗时
3 CORS 处理跨域
4 Auth 验证 JWT,注入用户信息
5 RBAC 检查角色权限
6 RateLimit 令牌桶限流
7 Handler 业务逻辑

6. 令牌桶限流

▶ 示例:令牌桶实现

GO 📖 仅展示
package main

import (
    "fmt"
    "net/http"
    "sync"
    "time"
)

type TokenBucket struct {
    mu         sync.Mutex
    tokens     float64
    maxTokens  float64
    refillRate float64
    lastRefill time.Time
}

func NewTokenBucket(rate float64, burst int) *TokenBucket {
    return &TokenBucket{
        tokens:     float64(burst),
        maxTokens:  float64(burst),
        refillRate: rate,
        lastRefill: time.Now(),
    }
}

func (tb *TokenBucket) Allow() bool {
    tb.mu.Lock()
    defer tb.mu.Unlock()

    // Refill tokens
    now := time.Now()
    elapsed := now.Sub(tb.lastRefill).Seconds()
    tb.tokens = minFloat(tb.tokens+elapsed*tb.refillRate, tb.maxTokens)
    tb.lastRefill = now

    // Take a token
    if tb.tokens >= 1 {
        tb.tokens--
        return true
    }
    return false
}

func minFloat(a, b float64) float64 {
    if a < b {
        return a
    }
    return b
}

// IP 限流器
type IPRateLimiter struct {
    mu       sync.RWMutex
    buckets  map[string]*TokenBucket
    rate     float64
    burst    int
}

func NewIPRateLimiter(rate float64, burst int) *IPRateLimiter {
    return &IPRateLimiter{
        buckets: make(map[string]*TokenBucket),
        rate:    rate,
        burst:   burst,
    }
}

func (rl *IPRateLimiter) GetBucket(ip string) *TokenBucket {
    rl.mu.Lock()
    defer rl.mu.Unlock()

    bucket, exists := rl.buckets[ip]
    if !exists {
        bucket = NewTokenBucket(rl.rate, rl.burst)
        rl.buckets[ip] = bucket
    }
    return bucket
}

// RateLimiterMiddleware
func RateLimiterMiddleware(rate float64, burst int) Middleware {
    limiter := NewIPRateLimiter(rate, burst)

    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ip := r.RemoteAddr
            bucket := limiter.GetBucket(ip)

            if !bucket.Allow() {
                w.Header().Set("Retry-After", "1")
                writeError(w, http.StatusTooManyRequests, "rate limit exceeded")
                return
            }

            next.ServeHTTP(w, r)
        })
    }
}

func main() {
    limiter := RateLimiterMiddleware(10, 5)
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "OK")
    })
    fmt.Println("Rate-limited server on :8080")
    http.ListenAndServe(":8080", limiter(mux))
}
逻辑代码 88 行(超过 40 行限制,仅展示)

7. 完整示例:API 网关中间件管线

GO
// api_gateway.go
package main

import (
    "context"
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "strings"
    "sync"
    "syscall"
    "time"
)

// ---------- 类型定义 ----------

type Middleware func(http.Handler) http.Handler

type Claims struct {
    UserID int
    Role   string
}

// ---------- JWT 验证 ----------

func validateJWT(token string, secret string) (*Claims, error) {
    parts := strings.Split(token, ".")
    if len(parts) != 3 {
        return nil, fmt.Errorf("invalid token format")
    }

    message := parts[0] + "." + parts[1]
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(message))
    expectedSig := strings.TrimRight(base64.URLEncoding.EncodeToString(mac.Sum(nil)), "=")

    if !hmac.Equal([]byte(parts[2]), []byte(expectedSig)) {
        return nil, fmt.Errorf("invalid signature")
    }

    payload, err := base64.URLEncoding.DecodeString(parts[1])
    if err != nil {
        return nil, fmt.Errorf("invalid payload")
    }

    var claims Claims
    if err := json.Unmarshal(payload, &claims); err != nil {
        return nil, err
    }

    return &claims, nil
}

// ---------- 令牌桶限流 ----------

type TokenBucket struct {
    mu         sync.Mutex
    tokens     float64
    maxTokens  float64
    refillRate float64
    lastRefill time.Time
}

func NewTokenBucket(rate float64, burst int) *TokenBucket {
    return &TokenBucket{
        tokens:     float64(burst),
        maxTokens:  float64(burst),
        refillRate: rate,
        lastRefill: time.Now(),
    }
}

func (tb *TokenBucket) Allow() bool {
    tb.mu.Lock()
    defer tb.mu.Unlock()

    now := time.Now()
    elapsed := now.Sub(tb.lastRefill).Seconds()
    tb.tokens = minFloat(tb.tokens+elapsed*tb.refillRate, tb.maxTokens)
    tb.lastRefill = now

    if tb.tokens >= 1 {
        tb.tokens--
        return true
    }
    return false
}

type IPRateLimiter struct {
    mu      sync.RWMutex
    buckets map[string]*TokenBucket
    rate    float64
    burst   int
}

func NewIPRateLimiter(rate float64, burst int) *IPRateLimiter {
    return &IPRateLimiter{
        buckets: make(map[string]*TokenBucket),
        rate:    rate,
        burst:   burst,
    }
}

func (rl *IPRateLimiter) GetBucket(ip string) *TokenBucket {
    rl.mu.Lock()
    defer rl.mu.Unlock()

    bucket, exists := rl.buckets[ip]
    if !exists {
        bucket = NewTokenBucket(rl.rate, rl.burst)
        rl.buckets[ip] = bucket
    }
    return bucket
}

// ---------- middleware ----------

// Recovery
func Recovery(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("[PANIC] %v", err)
                writeError(w, http.StatusInternalServerError, "internal error")
            }
        }()
        next.ServeHTTP(w, r)
    })
}

// Logging
func Logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("[%s] %s %s", r.Method, r.URL.Path, r.RemoteAddr)
        next.ServeHTTP(w, r)
        log.Printf("[%s] %s → %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// CORS
func CORS(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusNoContent)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// Auth(JWT 认证)
func Auth(secret string) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            authHeader := r.Header.Get("Authorization")
            if !strings.HasPrefix(authHeader, "Bearer ") {
                writeError(w, http.StatusUnauthorized, "missing token")
                return
            }

            token := authHeader[7:]
            claims, err := validateJWT(token, secret)
            if err != nil {
                writeError(w, http.StatusUnauthorized, err.Error())
                return
            }

            ctx := context.WithValue(r.Context(), "claims", &Claims{
                UserID: claims.UserID,
                Role:   claims.Role,
            })
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// RBAC
func RequireRole(roles ...string) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            claims, ok := r.Context().Value("claims").(*Claims)
            if !ok {
                writeError(w, http.StatusUnauthorized, "not authenticated")
                return
            }

            for _, role := range roles {
                if claims.Role == role {
                    next.ServeHTTP(w, r)
                    return
                }
            }

            writeError(w, http.StatusForbidden, "insufficient permissions")
        })
    }
}

// Rate Limiter
var rateLimiter = NewIPRateLimiter(10, 20) // 10 req/s, burst 20

func RateLimit(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ip := r.RemoteAddr
        bucket := rateLimiter.GetBucket(ip)
        if !bucket.Allow() {
            writeError(w, http.StatusTooManyRequests, "rate limit exceeded")
            return
        }
        next.ServeHTTP(w, r)
    })
}

// ---------- Chain ----------

func Chain(h http.Handler, mws ...Middleware) http.Handler {
    for i := len(mws) - 1; i >= 0; i-- {
        h = mws[i](h)
    }
    return h
}

// ---------- Handlers ----------

type OrderHandler struct{}

func (h *OrderHandler) ListOrders(w http.ResponseWriter, r *http.Request) {
    writeJSON(w, http.StatusOK, map[string]string{"orders": "list"})
}

func (h *OrderHandler) CreateOrder(w http.ResponseWriter, r *http.Request) {
    writeJSON(w, http.StatusCreated, map[string]string{"order": "created"})
}

type AdminHandler struct{}

func (h *AdminHandler) DeleteUser(w http.ResponseWriter, r *http.Request) {
    writeJSON(w, http.StatusOK, map[string]string{"deleted": "user"})
}

// ---------- 工具函数 ----------

func writeJSON(w http.ResponseWriter, status int, data interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}

func writeError(w http.ResponseWriter, status int, msg string) {
    writeJSON(w, status, map[string]string{"error": msg})
}

// ---------- Main ----------

func main() {
    jwtSecret := "super-secret-key"
    orders := &OrderHandler{}

    mux := http.NewServeMux()

    // 公开端点
    mux.HandleFunc("POST /login", func(w http.ResponseWriter, r *http.Request) {
        writeJSON(w, http.StatusOK, map[string]string{"token": "login-implementation"})
    })

    // 认证 + 限流的端点
    mux.Handle("GET /api/orders", Chain(
        http.HandlerFunc(orders.ListOrders),
        Auth(jwtSecret),
        RateLimit,
    ))

    mux.Handle("POST /api/orders", Chain(
        http.HandlerFunc(orders.CreateOrder),
        Auth(jwtSecret),
        RateLimit,
    ))

    // 管理员端点(Auth + RBAC + RateLimit)
    mux.Handle("DELETE /api/users/{id}", Chain(
        http.HandlerFunc((&AdminHandler{}).DeleteUser),
        Auth(jwtSecret),
        RequireRole("admin"),
        RateLimit,
    ))

    // 全局中间件(Recovery → Logging → CORS → route)
    app := Chain(mux, Recovery, Logging, CORS)

    server := &http.Server{Addr: ":8080", Handler: app}

    go func() {
        sigCh := make(chan os.Signal, 1)
        signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
        <-sigCh
        log.Println("关闭中...")
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
        server.Shutdown(ctx)
    }()

    log.Println("API 网关启动于 :8080")
    if err := server.ListenAndServe(); err != http.ErrServerClosed {
        log.Fatal(err)
    }
}
🔥 易错: 中间件的顺序至关重要。 Recovery 必须是最外层(捕获所有 panic)。Auth 和 RBAC 必须在 RateLimit 之前——限流应该对未认证的请求也生效,但不应该先让未认证用户消耗令牌。一般顺序:Recovery → Logging → CORS → Auth → RBAC → RateLimit → Handler。


❓ 常见问题

Q JWT 怎么实现?
A JWT 由三部分组成:Header(算法+类型)、Payload(claims)、Signature(签名)。Go 推荐用 github.com/golang-jwt/jwt/v5。Access Token 短期(15 分钟),Refresh Token 长期(7 天)存储在服务端。
Q Access Token 和 Refresh Token 区别?
A Access Token 用于 API 认证,短期有效(15 分钟),包含用户信息和权限。Refresh Token 用于获取新的 Access Token,长期有效(7 天),存储在服务端。Access Token 过期后客户端用 Refresh Token 自动获取新的 Access Token,无需用户重新登录。
Q RBAC 怎么实现?
A RBAC(Role-Based Access Control)给每个角色分配一组权限,中间件检查当前用户的角色是否有请求所需的权限。实现:rolePermissions[role] = []permission + 中间件检查 permission in rolePermissions[claims.Role]
Q 限流算法选哪种?
A 令牌桶(Token Bucket)最常用——允许突发(burst),平均速率可控。实现:每秒补充 rate 个令牌,桶上限 burst。请求消耗一个令牌,令牌不够返回 429。适合 API 限流。
Q 如何防止 recovery 中间件吞掉 panic 信息?
A 在 recovery 中记录完整的 stack trace,然后用自定义错误类型返回。debug.Stack() 打印堆栈。日志级别用 ERROR。生产环境不要返回堆栈给客户端——只记录在服务端日志中。
Q 中间件管线执行顺序?
A 洋葱模型。Chain(h, A, B, C) → 请求经过 A→B→C→Handler→C→B→A。注册顺序 = 外层到内层。Recovery 在最外层(捕获任何层抛出的 panic),Handler 在最内层(业务逻辑)。
Q JWT 如何撤销?
A JWT 是无状态的——签发后无法撤销直到过期。解决方案:(1) 短期 Token + Refresh Token(Refresh Token 可撤销);(2) 黑名单(Redis 存储已撤销的 JWT ID);(3) 版本号(用户密码变更后递增版本号,旧 Token 失效)。

📖 小节


📝 作业

  1. 基础题(难度⭐):实现一个简单的认证中间件。创建一个 /api/protected 端点,要求请求头包含 Authorization: Bearer token123。Token 正确返回 {"message": "access granted"},错误返回 401。

  2. 进阶题(难度⭐⭐):实现一个完整的 JWT 认证系统。要求:(1) POST /register 注册(bcrypt 加密密码);(2) POST /login 登录(返回 access_token + refresh_token);(3) Auth 中间件验证所有 /api/* 端点的 access_token;(4) POST /refresh 用 refresh_token 换新 access_token;(5) POST /logout 撤销 refresh_token。

  3. 挑战题(难度⭐⭐⭐):实现一个带 RBAC 和限流的博客管理 API。要求:(1) 三种角色:admin/editor/reader;(2) admin:所有权限;(3) editor:创建/编辑/删除自己的文章;(4) reader:只能阅读;(5) 每个角色有不同的速率限制;(6) 所有端点通过中间件管线:Recovery → Logging → Auth → RBAC → RateLimit → Handler;(7) 用 -race 验证并发安全。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏