CORS跨域 #
一、CORS概述 #
1.1 什么是跨域 #
跨域是指浏览器出于安全考虑,限制从一个源访问另一个源的资源:
text
同源策略:协议 + 域名 + 端口 必须相同
示例:
http://localhost:3000 → http://localhost:8080 跨域(端口不同)
http://example.com → https://example.com 跨域(协议不同)
http://a.example.com → http://b.example.com 跨域(域名不同)
1.2 CORS原理 #
CORS(Cross-Origin Resource Sharing)是一种允许跨域请求的机制:
text
浏览器 → OPTIONS预检请求 → 服务器返回允许的源
浏览器 → 实际请求 → 服务器返回数据
1.3 相关响应头 #
| 响应头 | 说明 |
|---|---|
| Access-Control-Allow-Origin | 允许的源 |
| Access-Control-Allow-Methods | 允许的方法 |
| Access-Control-Allow-Headers | 允许的请求头 |
| Access-Control-Allow-Credentials | 允许携带凭证 |
| Access-Control-Max-Age | 预检请求缓存时间 |
二、基本CORS配置 #
2.1 简单CORS中间件 #
go
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
func main() {
r := gin.Default()
r.Use(CORSMiddleware())
r.Run()
}
2.2 指定允许的源 #
go
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.GetHeader("Origin")
// 允许的源列表
allowedOrigins := []string{
"http://localhost:3000",
"https://example.com",
}
allowed := false
for _, o := range allowedOrigins {
if origin == o {
allowed = true
break
}
}
if allowed {
c.Header("Access-Control-Allow-Origin", origin)
}
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
三、使用cors库 #
3.1 安装 #
bash
go get github.com/gin-contrib/cors
3.2 基本配置 #
go
import "github.com/gin-contrib/cors"
func main() {
r := gin.Default()
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:3000"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
r.Run()
}
3.3 允许所有源 #
go
r.Use(cors.New(cors.Config{
AllowAllOrigins: true,
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Content-Type", "Authorization"},
}))
3.4 动态源配置 #
go
r.Use(cors.New(cors.Config{
AllowOriginFunc: func(origin string) bool {
// 允许所有localhost端口
if strings.HasPrefix(origin, "http://localhost") {
return true
}
// 允许特定域名
if strings.HasSuffix(origin, ".example.com") {
return true
}
return false
},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowHeaders: []string{"Content-Type", "Authorization"},
AllowCredentials: true,
}))
3.5 完整配置示例 #
go
func main() {
r := gin.Default()
config := cors.Config{
// 允许的源
AllowOrigins: []string{
"http://localhost:3000",
"http://localhost:8080",
"https://example.com",
},
// 或使用函数动态判断
AllowOriginFunc: func(origin string) bool {
return strings.HasPrefix(origin, "http://localhost") ||
strings.HasSuffix(origin, ".example.com")
},
// 允许的方法
AllowMethods: []string{
"GET",
"POST",
"PUT",
"DELETE",
"OPTIONS",
"PATCH",
},
// 允许的请求头
AllowHeaders: []string{
"Content-Type",
"Authorization",
"X-Requested-With",
"X-Request-ID",
},
// 暴露的响应头
ExposeHeaders: []string{
"Content-Length",
"X-Request-ID",
},
// 允许携带凭证
AllowCredentials: true,
// 预检请求缓存时间
MaxAge: 12 * time.Hour,
}
r.Use(cors.New(config))
r.Run()
}
四、预检请求处理 #
4.1 什么是预检请求 #
对于非简单请求,浏览器会先发送OPTIONS请求:
text
简单请求:
- 方法:GET、POST、HEAD
- 请求头:Accept、Content-Type(仅限简单值)
非简单请求:
- 方法:PUT、DELETE
- 请求头:Authorization、Content-Type: application/json
4.2 预检请求流程 #
text
1. 浏览器发送 OPTIONS /api/users
2. 服务器返回允许的方法和头
3. 浏览器发送实际请求 POST /api/users
4. 服务器返回数据
4.3 处理预检请求 #
go
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.GetHeader("Origin")
// 设置CORS头
c.Header("Access-Control-Allow-Origin", origin)
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")
c.Header("Access-Control-Max-Age", "86400")
// 处理预检请求
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
五、携带凭证 #
5.1 允许Cookie #
go
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:3000"}, // 不能使用*
AllowCredentials: true,
AllowMethods: []string{"GET", "POST"},
AllowHeaders: []string{"Content-Type"},
}))
5.2 前端配置 #
javascript
// fetch
fetch('http://localhost:8080/api/users', {
credentials: 'include',
})
// axios
axios.get('http://localhost:8080/api/users', {
withCredentials: true,
})
5.3 注意事项 #
text
当 AllowCredentials 为 true 时:
1. AllowOrigins 不能使用 "*"
2. 必须指定具体的源
3. 前端需要设置 credentials: 'include'
六、常见问题 #
6.1 跨域错误示例 #
text
Access to XMLHttpRequest at 'http://localhost:8080/api/users'
from origin 'http://localhost:3000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
6.2 解决方案 #
go
// 确保CORS中间件在路由之前
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:3000"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowHeaders: []string{"Content-Type", "Authorization"},
}))
// 确保处理OPTIONS请求
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
6.3 自定义请求头 #
go
AllowHeaders: []string{
"Content-Type",
"Authorization",
"X-Custom-Header", // 添加自定义请求头
},
七、生产环境配置 #
7.1 环境区分 #
go
func getCORSConfig() cors.Config {
if gin.Mode() == gin.ReleaseMode {
return cors.Config{
AllowOrigins: []string{
"https://example.com",
"https://api.example.com",
},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowHeaders: []string{"Content-Type", "Authorization"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}
}
return cors.Config{
AllowAllOrigins: true,
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowHeaders: []string{"Content-Type", "Authorization"},
AllowCredentials: false,
}
}
func main() {
r := gin.Default()
r.Use(cors.New(getCORSConfig()))
r.Run()
}
7.2 配置文件 #
yaml
cors:
allowed_origins:
- https://example.com
- https://api.example.com
allowed_methods:
- GET
- POST
- PUT
- DELETE
allowed_headers:
- Content-Type
- Authorization
allow_credentials: true
max_age: 43200
八、总结 #
8.1 核心要点 #
| 要点 | 说明 |
|---|---|
| CORS头 | 设置正确的响应头 |
| 预检请求 | 处理OPTIONS请求 |
| 凭证 | AllowCredentials配置 |
| 安全 | 生产环境限制源 |
8.2 最佳实践 #
| 实践 | 说明 |
|---|---|
| 使用cors库 | 使用成熟的CORS库 |
| 环境区分 | 开发和生产不同配置 |
| 限制源 | 生产环境限制允许的源 |
| 缓存预检 | 设置合理的MaxAge |
8.3 下一步 #
现在你已经掌握了CORS跨域,接下来让我们学习 日志系统,了解Gin的日志管理!
最后更新:2026-03-28