Skip to content

CORS 跨域

CORS (Cross-Origin Resource Sharing) 是一种机制,允许 Web 应用从不同域访问资源。

📋 学习目标

  • 理解跨域的概念和原因
  • 掌握 CORS 的工作原理
  • 学会在 Go 中实现 CORS
  • 理解预检请求
  • 掌握安全配置
  • 了解常见问题

🎯 CORS 简介

什么是跨域

跨域是指浏览器从一个域名的网页去请求另一个域名的资源。同源策略限制了这种行为。

同源策略

同源需要满足:

  • 协议相同 (http/https)
  • 域名相同
  • 端口相同

为什么需要 CORS

  • 前后端分离: 前端和后端可能部署在不同域名
  • 微服务架构: 不同服务可能在不同域名
  • 第三方 API: 需要允许跨域访问

🚀 快速开始

基本 CORS 配置

go
package main

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-contrib/cors"
)

func main() {
	r := gin.Default()
	
	// 配置 CORS
	config := cors.DefaultConfig()
	config.AllowOrigins = []string{"http://localhost:3000"}
	config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}
	config.AllowHeaders = []string{"Origin", "Content-Type", "Authorization"}
	
	r.Use(cors.New(config))
	
	r.GET("/api/data", func(c *gin.Context) {
		c.JSON(200, gin.H{"message": "Hello CORS"})
	})
	
	r.Run(":8080")
}

允许所有来源

go
config := cors.DefaultConfig()
config.AllowAllOrigins = true
r.Use(cors.New(config))

🔧 详细配置

完整配置示例

go
package main

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-contrib/cors"
	"time"
)

func main() {
	r := gin.Default()
	
	config := cors.Config{
		AllowOrigins:     []string{"http://localhost:3000", "https://example.com"},
		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"},
		AllowHeaders:     []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With"},
		ExposeHeaders:    []string{"Content-Length", "X-Total-Count"},
		AllowCredentials: true,
		MaxAge:           12 * time.Hour,
	}
	
	r.Use(cors.New(config))
	
	r.GET("/api/users", func(c *gin.Context) {
		c.JSON(200, gin.H{"users": []string{"user1", "user2"}})
	})
	
	r.Run(":8080")
}

自定义 CORS 中间件

go
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func corsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		origin := c.GetHeader("Origin")
		
		// 允许的源列表
		allowedOrigins := []string{
			"http://localhost:3000",
			"https://example.com",
		}
		
		// 检查源是否允许
		allowed := false
		for _, allowedOrigin := range allowedOrigins {
			if origin == allowedOrigin {
				allowed = true
				break
			}
		}
		
		if allowed {
			c.Header("Access-Control-Allow-Origin", origin)
			c.Header("Access-Control-Allow-Credentials", "true")
			c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
			c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
			c.Header("Access-Control-Max-Age", "86400")
		}
		
		// 处理预检请求
		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
			return
		}
		
		c.Next()
	}
}

func main() {
	r := gin.Default()
	r.Use(corsMiddleware())
	
	r.GET("/api/data", func(c *gin.Context) {
		c.JSON(200, gin.H{"message": "Hello"})
	})
	
	r.Run(":8080")
}

🔍 预检请求

什么是预检请求

对于复杂请求,浏览器会先发送 OPTIONS 请求进行预检。

处理预检请求

go
func corsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 设置 CORS 头
		c.Header("Access-Control-Allow-Origin", "http://localhost:3000")
		c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
		c.Header("Access-Control-Allow-Credentials", "true")
		
		// 处理预检请求
		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
			return
		}
		
		c.Next()
	}
}

🏃‍♂️ 实践应用

环境相关配置

go
package main

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-contrib/cors"
	"os"
)

func getCORSConfig() cors.Config {
	config := cors.DefaultConfig()
	
	// 根据环境设置允许的源
	env := os.Getenv("ENV")
	if env == "production" {
		config.AllowOrigins = []string{
			"https://example.com",
			"https://www.example.com",
		}
	} else {
		config.AllowAllOrigins = true
	}
	
	config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}
	config.AllowHeaders = []string{"Origin", "Content-Type", "Authorization"}
	config.AllowCredentials = true
	
	return config
}

func main() {
	r := gin.Default()
	r.Use(cors.New(getCORSConfig()))
	
	r.GET("/api/data", func(c *gin.Context) {
		c.JSON(200, gin.H{"message": "Hello"})
	})
	
	r.Run(":8080")
}

⚠️ 安全注意事项

1. 不要允许所有源

go
// ❌ 生产环境不要这样做
config.AllowAllOrigins = true

// ✅ 明确指定允许的源
config.AllowOrigins = []string{"https://example.com"}

2. 凭证安全

go
// 使用凭证时,不能使用通配符
config.AllowCredentials = true
config.AllowOrigins = []string{"https://example.com"} // 必须明确指定

3. 方法限制

go
// ✅ 只允许需要的方法
config.AllowMethods = []string{"GET", "POST"}

// ❌ 不要允许所有方法
config.AllowMethods = []string{"*"}

📚 扩展阅读

⏭️ 下一章节

限流与熔断 → 学习限流和熔断器


💡 提示: CORS 是前后端分离开发中必须掌握的知识,正确配置可以确保应用安全运行!

基于 VitePress 构建