一文讲透go服务框架CORS设置

导读

曾几何时,大家为了应对跨域八仙过海各显神通,到现在都1202年了,跨域访问早已经有了非常标准的解法了,今天我们来看看在golang中该如何正确的开启跨域之旅。

同源策略

在讲跨域之前,我们简单看一下为什么需要跨域,同源策略(Same-Origin Policy)的要求,本质还是浏览器安全的要求。同源策略最早由浏览器厂商Netscape公司提出,现在已经成为浏览器最基础最核心的安全功能。所谓同源就是域名、协议、端口完全相同,例如http://a.comhttps://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-20211009233655326

更全面的介绍可以参考资料2 资料3

CORS实现机制

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-LanguageContent-Type、Expires、Last-ModifiedPragma。如果想拿到其他字段,就必须在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版本了。

参考资料

  1. https://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
  2. https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
  3. https://segmentfault.com/a/1190000017579464

新文章将会第一时间在公众号发布,更多更新文章欢迎订阅【增哥微学堂】

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容