导读
曾几何时,大家为了应对跨域八仙过海各显神通,到现在都1202年了,跨域访问早已经有了非常标准的解法了,今天我们来看看在golang中该如何正确的开启跨域之旅。
同源策略
在讲跨域之前,我们简单看一下为什么需要跨域,同源策略(Same-Origin Policy)的要求,本质还是浏览器安全的要求。同源策略最早由浏览器厂商Netscape公司提出,现在已经成为浏览器最基础最核心的安全功能。所谓同源就是域名、协议、端口完全相同,例如http://a.com与https://a.com就不是同源的,因为协议不同。更详细准确的描述可以参考RFC6454。
同源策略带来的影响之一就是ajax请求只能同源,由此引发了众多的跨域请求方案的争奇斗艳。
本文主要讲述CORS方案,其他跨域方案可以参考资料1
注意,下文中跨源和跨域等同,同理同源等同于同域,在参考文档中,不同的文档叫法略有不同。
CORS概述
跨源域资源共享机制允许 Web 应用服务器进行跨源访问控制,从而使跨源数据传输得以安全进行。跨域资源共享(Cross-Origin Resource Share 简称CORS),CORS是一个W3C标准,它允许浏览器向跨源服务器发出XMLHttpRequest请求,从而突破了ajax只能使用同源服务的局限性。
CORS允许服务器声明哪些源站通过浏览器有权限访问哪些资源,并且对于那些对服务器可能产生副作用的HTTP请求,例如POST、DELETE请求等,浏览器必须首先使用OPTIONS方法发起一个预检请求(prelight request),从而获知服务器是否允许该跨域请求。而且在预检请求的返回中,服务器端也可以通知客户端是否需要携带身份凭证,例如cookie等。
CORS请求失败浏览器会产生错误,但是为了安全,在JavaScript代码层面是无法获知到底具体是哪里出了问题。你只能查看浏览器的控制台以得知具体是哪里出现了错误。这一点需要额外注意,特别是在定位服务接口的问题时往往只能看到接口报错了,但是却看不到具体的错误信息,如果直接就去排查服务是否有问题就会绕远路浪费时间了。
image-20211009233655326CORS实现机制
CORS标准新增了一组HTTP头字段来实现该机制。服务端通过设置相应的头部字段来控制是否允许该次跨域请求,如果服务端没有返回正确的响应头部,则请求方不会收到任何数据。
核心Header介绍
请求Header
头名 | 值 | 说明 |
---|---|---|
Origin | 本次请求来自哪个源(协议 + 域名 + 端口) | 服务器根据这个值,决定是否同意这次请求。简单请求和预检请求均包含该Header |
Access-Control-Request-Method | 请求方法列表 | 预检请求时,该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法 |
Access-Control-Request-Headers | 额外的Headers | 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段 |
响应Header
头名 | 值 | 说明 |
---|---|---|
Access-Control-Allow-Origin | 该字段是必须的。它的值要么是请求时Origin 字段的值,要么是一个* ,表示接受任意域名的请求。 |
每次回应都必定包含的。 |
Access-Control-Allow-Credentials | 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认是false,不发送Cookie。 | 设为true ,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true ,如果服务器不要浏览器发送Cookie,删除该字段即可。另外当该字段为true 时,Access-Control-Allow-Origin 字段不能是* ,只能为Origin字段的值。 |
Access-Control-Expose-Headers | 该字段可选。 | CORS请求时,XMLHttpRequest 对象的getResponseHeader() 方法只能拿到6个基本字段:Cache-Control 、Content-Language 、Content-Type 、Expires 、Last-Modified 、Pragma 。如果想拿到其他字段,就必须在Access-Control-Expose-Headers 里面指定。 |
Access-Control-Allow-Methods | 预检请求返回字段,必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法 | 返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。 |
Access-Control-Allow-Headers | 预检请求返回字段,如果浏览器请求包括Access-Control-Request-Headers 字段,则Access-Control-Allow-Headers 字段是必需的。 |
它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。 |
Access-Control-Max-Age | 预检请求返回字段,可选,用来指定本次预检请求的有效期,单位为秒。 | 允许缓存该条回应Access-Control-Max-Age 秒,在此期间,不用发出另一条预检请求。 |
实战案例
跨域设置在实际工程项目中已经是一个非常普遍的需求了,现在我们看看常用的web框架都提供了什么样的支持呢。
gin
在gin框架中,官方并没有提供一个开箱可用的中间件,一般是由使用者自己定义一个中间件来实现的。
一个简单实现示例如下:
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
origin := c.Request.Header.Get("Origin") //请求头部
if origin != "" {
// 当Access-Control-Allow-Credentials为true时,将*替换为指定的域名
c.Header("Access-Control-Allow-Origin", "http://a.com")
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, X-Extra-Header, Content-Type, Accept, Authorization")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
c.Header("Access-Control-Max-Age", "86400") // 可选
c.Set("content-type", "application/json") // 可选
}
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
c.Next()
}
}
func main() {
r := gin.Default()
r.Use(Cors()) //开启中间件 允许使用跨域请求
// 其他路由设置
r.run()
}
示例代码中并没有对Origin进行更多的逻辑鉴权处理,如果是更复杂的业务场景时,则需要根据实际做更多的逻辑鉴权处理。
虽然gin官方没有提供一个开箱即用的cors包,但是瑕不掩瑜,gin仍然是非常值得学习和使用的高质量高性能的web框架。
另外需要注意的是,上述跨域中间件必须在路由设置之前开启,否则会不生效。
这里留一个小练习,这样设置跨域为什么会不生效。
r := gin.Default()
fileGroup := r.Group("file")
{
fileGroup.POST("/upload", Upload)
}
r.Use(middlewares.Cors()) // TODO 为什么不生效?
beego
相较于gin,beego提供了更为系统和强大的解决方式,开箱可用的cors包。
先看一下官方提供的示例
import (
"github.com/beego/beego/v2"
"github.com/beego/beego/v2/server/web/filter/cors"
)
func main() {
// CORS for https://foo.* origins, allowing:
// - PUT and PATCH methods
// - Origin header
// - Credentials share
beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
AllowOrigins: []string{"https://*.foo.com"},
AllowMethods: []string{"PUT", "PATCH"},
AllowHeaders: []string{"Origin"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
}))
beego.Run()
}
可以看到,beego提供的cors包不仅有完整的封装,更有比较灵活的配置方式,例如Credentials为true时,AllowOrigin也可以使用模糊匹配,这就大大增强了服务端的跨域鉴权能力。
更多的使用方法可以直接通过浏览源官方代码或者文档进行查阅学习。
echo
与beego类似,echo编程框架也提供了官方的cors包,而且也同样提供了丰富强大的配置能力。
使用示例
e := echo.New()
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://a.com", "https://a.net"},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
}))
更多用法同样可以通过官方代码进行挖掘,或者参考该文档,注意这是v3版本的文档,echo最新已经是v4版本了。
参考资料
- https://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
- https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
- https://segmentfault.com/a/1190000017579464
新文章将会第一时间在公众号发布,更多更新文章欢迎订阅【增哥微学堂】
网友评论