目录
一、概述
CORS(Cross-Origin Resource Sharing)是一种浏览器安全机制,用于控制在Web应用程序中不同源(Origin)之间的资源共享。一个源是由协议(例如http或https)、主机(例如 www.example.com)、以及端口(例如80或443)组成的组合。CORS允许服务器定义哪些源可以访问其资源,以及哪些HTTP方法和头部可以在跨源请求中使用。
二、简单请求和预检请求
简单请求
1、判定条件
需要同时满足以下条件,浏览器会认为它是一个简单请求
- 请求方法属于其中一种:GET、POST、HEAD
- 请求包仅包含安全的字段,常见字段:Accept、Accept-Language、Content-Language、Content-Type、DPR、Downlink、Save-Data、Viewport-Width、Width
- 请求头如果包含Content-Type,仅限如下值:text/plain、multipart/form-data、application/x-www-form-urlencoded
2、简单请求交互
①当浏览器判某个跨域请求时简单请求时,会在请求头中自动添加Origin字段
GET /cors HTTP/1.1
Host: example.com
Connection: keep-alive
...
Referer: http://local.com/index.html
Origin: http://local.com //Origin字段会告诉服务器,是哪个源地址在跨域请求
②服务器响应头中应包含Access-Control-Allow-Origin ,当服务器收到请求后,如果允许改请求跨域访问,需要在响应头中添加Access-Control-Allow-Origin字段
HTTP/1.1 200 OK
Date: Tue, 21 Jun 2023 08:03:35 GMT
...
Access-Control-Allow-Origin: http://local.com
...
消息体中的数据
当浏览器看到服务器允许自己访问后,于是,它就把响应顺利的交给js
预检请求
1、对于预检请求,请求步骤如下:
2、步骤请求演示
①浏览器发送预检请求
OPTIONS /cors HTTP/1.1
Host: example.com
...
Origin: http://local.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: a, b, content-type
ps:这并非想要发出的真实请求,请求中不包含响应头和也没有消息体。
②服务器允许
服务器收到预检请求后,可以检查预检请求中包含的信息,如果允许这样的请求,需要响应下面的消息格式
HTTP/1.1 200 OK
...
Access-Control-Allow-Origin: http://local.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: a, b, content-type
Access-Control-Max-Age: 86400
...
对于预检请求,不需要响应任何的消息体,只需要在响应头中添加:
③浏览器发送真实请求
预检被服务器允许后,浏览器就会发送真实请求了,上面的代码会发送下面的请求数据
POST /cors HTTP/1.1
Host: example.com
Connection: keep-alive ...
Referer: http://local.com/index.html
Origin: http://local.com
{"name": "admin", "age": 20}
④服务器响应真实请求
HTTP/1.1 200 OK
Date: Tue, 21 Jun 2023 08:03:35 GMT
...
Access-Control-Allow-Origin: http://local.com
...
添加用户成功
三、使用go的gin框架实现cors配置
使用go的gin框架的话,说白了就是添加一个中间件,Gin官方提供CORS中间件,可以很方便的使用 cors解决跨域问题。
1、安装
使用命令安装该中间件
go get github.com/gin-contrib/cors
2、函数
cors中间件提供三个函数,代表三种使用方式,分别是NEW、DefaultConfig、Default。
- NEW方式
可以接收 CORS 中间件的配置项,可通过自定义配置项,满足任意需要跨域的场景。
示例:
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://foo.com"},
AllowMethods: []string{"PUT", "PATCH"},
AllowHeaders: []string{"Origin"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
AllowOriginFunc: func(origin string) bool {
return origin == "https://github.com"
},
MaxAge: 12 * time.Hour,
}))
//New 函数接收配置项,返回一个用户自定义的 CORS 中间件,绑定到路由中。
- DefaultConfig方式
默认设置一些通用配置项,我们可以直接使用,也可以在此基础上添加我们需要的其他配置项。
func DefaultConfig() Config {
return Config{
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type"},
AllowCredentials: false,
MaxAge: 12 * time.Hour,
}
}
- Default方式
在 DefaultConfig
方式的基础上,设置 AllowAllOrigins
选项为 true
,因为 DefaultConfig
方式默认不允许任意请求源,所以需要单独设置 AllowAllOrigins
选项为 true
func Default() gin.HandlerFunc {
config := DefaultConfig()
config.AllowAllOrigins = true
return New(config)
}
3、某段配置代码分析
//设置cors中间件
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method //获取请求包的http请求方法
origin := c.Request.Header.Get("Origin") //获取请求包的head头的Origin参数值
var headerkeys []string
for k := range c.Request.Header {
headerkeys = append(headerkeys, k)
}
headerStr := strings.Join(headerkeys, ", ") //将请求包中head头部分的参数用","连接成字符串
//检查请求头不为空
if headerStr != "" {
//如果请求头不为空,将请求头的键名添加到一个字符串中,以便在设置响应头时包含这些键名。
headerStr = fmt.Sprintf("access-control-allow-origin, access-control-allow-headers,%s", headerStr)
} else {
headerStr = "access-control-allow-origin, access-control-allow-headers"
}
//检查请求头中origin是否为空
if origin != "" {
//设置响应头中的"Access-Control-Allow-Origin"字段,允许任何域名访问资源(这是一个简单的CORS策略,实际应用中可能需要更严格的控制)。
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE, UPDATE")
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") // 跨域关键设置 让浏览器可以解析
c.Header("Access-Control-Max-Age", "172800") // 缓存请求信息 单位为秒
c.Header("Access-Control-Allow-Credentials", "false") // 跨域请求是否需要带cookie信息 默认设置为true
c.Set("content-type", "application/json") // 设置返回格式是json
}
//放行所有OPTIONS方法
if method == "OPTIONS" {
c.JSON(http.StatusOK, "Options Request!")
}
}
}