Golang中如何限制API仅允许前端访问

Golang中如何限制API仅允许前端访问 我正在开发一个分布式应用程序,该程序将包含多个API后端节点和几个基本的JS前端节点,前端节点通过调用API获取动态内容来填充页面。后端将处理所有功能,包括用户管理和网站的其他功能,因此我正在实现JWT用于登录和用户访问控制。然而,我不希望用户能够自行访问API,因为有些人可能会通过观察对后端发起的调用,获取他们自己的JWT,并自动化执行一些他们被允许执行的任务。我并不担心用户的权限范围,这方面我已经控制得很好,我只是希望仅由我的前端来管理用户与API之间的通信。

是否可以通过某种静态API密钥来实现这一点,在用户使用前端时加密传递该密钥?如果可以,那么该密钥泄露的风险是什么?显然,它在前端节点上会受到很好的保护,但我不能简单地假设它永远不会被泄露。从概念上讲,我设想为每个前端节点颁发某种客户端证书,用作第二重身份验证因素,但这是否过于复杂,我不太确定。非常感谢任何建议。


更多关于Golang中如何限制API仅允许前端访问的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中如何限制API仅允许前端访问的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Golang中限制API仅允许前端访问,可以通过多种技术组合实现。以下是一个完整的实现方案,结合了静态API密钥、CORS限制和请求验证:

package main

import (
    "crypto/subtle"
    "net/http"
    "strings"
    "time"
    
    "github.com/gin-gonic/gin"
    "github.com/redis/go-redis/v9"
)

// 配置结构体
type APIConfig struct {
    FrontendAPIKey    string
    AllowedOrigins    []string
    RateLimitPerMin   int
    RequireUserAgent  bool
}

// 中间件:验证前端API密钥
func FrontendAuthMiddleware(apiKey string) gin.HandlerFunc {
    return func(c *gin.Context) {
        // 从Header获取API密钥
        clientKey := c.GetHeader("X-Frontend-Key")
        
        // 使用恒定时间比较防止时序攻击
        if subtle.ConstantTimeCompare([]byte(clientKey), []byte(apiKey)) != 1 {
            c.JSON(http.StatusUnauthorized, gin.H{
                "error": "Invalid frontend API key",
            })
            c.Abort()
            return
        }
        c.Next()
    }
}

// 中间件:严格的CORS控制
func StrictCorsMiddleware(allowedOrigins []string) gin.HandlerFunc {
    originSet := make(map[string]bool)
    for _, origin := range allowedOrigins {
        originSet[origin] = true
    }
    
    return func(c *gin.Context) {
        origin := c.Request.Header.Get("Origin")
        
        // 检查Origin是否在白名单中
        if origin != "" && originSet[origin] {
            c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
            c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
            c.Writer.Header().Set("Access-Control-Allow-Headers", 
                "Content-Type, X-Frontend-Key, Authorization")
            c.Writer.Header().Set("Access-Control-Allow-Methods", 
                "GET, POST, PUT, DELETE, OPTIONS")
            
            // 预检请求处理
            if c.Request.Method == "OPTIONS" {
                c.AbortWithStatus(http.StatusNoContent)
                return
            }
        } else if origin != "" {
            // 不在白名单中的Origin直接拒绝
            c.JSON(http.StatusForbidden, gin.H{
                "error": "Origin not allowed",
            })
            c.Abort()
            return
        }
        c.Next()
    }
}

// 中间件:验证请求来源(Referer检查)
func RefererValidationMiddleware(allowedDomains []string) gin.HandlerFunc {
    return func(c *gin.Context) {
        referer := c.Request.Header.Get("Referer")
        
        // 对于非GET请求,要求Referer验证
        if c.Request.Method != "GET" && referer != "" {
            isValid := false
            for _, domain := range allowedDomains {
                if strings.Contains(referer, domain) {
                    isValid = true
                    break
                }
            }
            
            if !isValid {
                c.JSON(http.StatusForbidden, gin.H{
                    "error": "Invalid request source",
                })
                c.Abort()
                return
            }
        }
        c.Next()
    }
}

// 中间件:用户代理验证
func UserAgentMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        userAgent := c.Request.Header.Get("User-Agent")
        
        // 拒绝明显无效的User-Agent
        if userAgent == "" || len(userAgent) < 5 {
            c.JSON(http.StatusBadRequest, gin.H{
                "error": "Invalid User-Agent header",
            })
            c.Abort()
            return
        }
        
        // 可以添加更多User-Agent验证逻辑
        c.Next()
    }
}

// 基于Redis的速率限制
func RateLimitMiddleware(rdb *redis.Client, limit int, window time.Duration) gin.HandlerFunc {
    return func(c *gin.Context) {
        clientIP := c.ClientIP()
        key := "rate_limit:" + clientIP
        
        // 使用Redis实现滑动窗口限流
        current := rdb.LLen(c, key).Val()
        if current >= int64(limit) {
            c.JSON(http.StatusTooManyRequests, gin.H{
                "error": "Rate limit exceeded",
            })
            c.Abort()
            return
        }
        
        // 添加当前时间戳到列表
        rdb.RPush(c, key, time.Now().Unix())
        // 设置过期时间
        rdb.Expire(c, key, window)
        // 移除窗口之外的时间戳
        rdb.LTrim(c, key, -limit, -1)
        
        c.Next()
    }
}

// API处理器示例
func main() {
    r := gin.Default()
    
    // 配置
    config := APIConfig{
        FrontendAPIKey:   "your-secure-api-key-here-12345",
        AllowedOrigins:   []string{"https://yourdomain.com", "https://app.yourdomain.com"},
        RateLimitPerMin:  100,
        RequireUserAgent: true,
    }
    
    // 初始化Redis(用于速率限制)
    rdb := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })
    
    // 应用中间件
    apiGroup := r.Group("/api")
    apiGroup.Use(StrictCorsMiddleware(config.AllowedOrigins))
    apiGroup.Use(FrontendAuthMiddleware(config.FrontendAPIKey))
    apiGroup.Use(RefererValidationMiddleware([]string{"yourdomain.com"}))
    
    if config.RequireUserAgent {
        apiGroup.Use(UserAgentMiddleware())
    }
    
    apiGroup.Use(RateLimitMiddleware(rdb, config.RateLimitPerMin, time.Minute))
    
    // 受保护的API端点
    apiGroup.GET("/data", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "This API can only be accessed by the frontend",
            "data":    "sensitive information",
        })
    })
    
    apiGroup.POST("/submit", func(c *gin.Context) {
        // 处理业务逻辑
        c.JSON(http.StatusOK, gin.H{
            "status": "success",
        })
    })
    
    r.Run(":8080")
}

对于客户端证书方案,这里是一个实现示例:

// TLS客户端证书验证
func setupTLSClientAuth() *http.Server {
    // 创建证书池,添加CA证书
    caCert, _ := os.ReadFile("ca.crt")
    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)
    
    tlsConfig := &tls.Config{
        ClientCAs:  caCertPool,
        ClientAuth: tls.RequireAndVerifyClientCert,
        MinVersion: tls.VersionTLS12,
    }
    
    server := &http.Server{
        Addr:      ":8443",
        TLSConfig: tlsConfig,
    }
    
    return server
}

// 在中间件中验证客户端证书
func ClientCertMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        if c.Request.TLS != nil && len(c.Request.TLS.PeerCertificates) > 0 {
            cert := c.Request.TLS.PeerCertificates[0]
            
            // 验证证书主题或序列号
            if cert.Subject.CommonName != "frontend-client" {
                c.JSON(http.StatusForbidden, gin.H{
                    "error": "Invalid client certificate",
                })
                c.Abort()
                return
            }
        } else {
            c.JSON(http.StatusForbidden, gin.H{
                "error": "Client certificate required",
            })
            c.Abort()
            return
        }
        c.Next()
    }
}

关于API密钥泄露的风险和缓解措施:

// 动态API密钥轮换机制
type APIKeyManager struct {
    currentKey string
    previousKey string
    mu sync.RWMutex
}

func (m *APIKeyManager) RotateKey(newKey string) {
    m.mu.Lock()
    defer m.mu.Unlock()
    m.previousKey = m.currentKey
    m.currentKey = newKey
}

func (m *APIKeyManager) ValidateKey(key string) bool {
    m.mu.RLock()
    defer m.mu.RUnlock()
    
    // 允许当前密钥和上一个密钥(平滑过渡)
    return subtle.ConstantTimeCompare([]byte(key), []byte(m.currentKey)) == 1 ||
           subtle.ConstantTimeCompare([]byte(key), []byte(m.previousKey)) == 1
}

// 使用HMAC签名验证请求
func VerifyRequestSignature(secret, body []byte, signature string) bool {
    h := hmac.New(sha256.New, secret)
    h.Write(body)
    expectedSignature := hex.EncodeToString(h.Sum(nil))
    
    return subtle.ConstantTimeCompare(
        []byte(expectedSignature), 
        []byte(signature),
    ) == 1
}

这个方案通过多层防御机制来限制API仅允许前端访问,即使API密钥泄露,也能通过其他机制提供额外保护。

回到顶部