Golang中如何限制API仅允许前端访问
Golang中如何限制API仅允许前端访问 我正在开发一个分布式应用程序,该程序将包含多个API后端节点和几个基本的JS前端节点,前端节点通过调用API获取动态内容来填充页面。后端将处理所有功能,包括用户管理和网站的其他功能,因此我正在实现JWT用于登录和用户访问控制。然而,我不希望用户能够自行访问API,因为有些人可能会通过观察对后端发起的调用,获取他们自己的JWT,并自动化执行一些他们被允许执行的任务。我并不担心用户的权限范围,这方面我已经控制得很好,我只是希望仅由我的前端来管理用户与API之间的通信。
是否可以通过某种静态API密钥来实现这一点,在用户使用前端时加密传递该密钥?如果可以,那么该密钥泄露的风险是什么?显然,它在前端节点上会受到很好的保护,但我不能简单地假设它永远不会被泄露。从概念上讲,我设想为每个前端节点颁发某种客户端证书,用作第二重身份验证因素,但这是否过于复杂,我不太确定。非常感谢任何建议。
更多关于Golang中如何限制API仅允许前端访问的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于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密钥泄露,也能通过其他机制提供额外保护。

