gin框架中使用中间件gin-contrib/cors处理跨域请求

一、跨域问题的由来

同源策略(Same origin policy)是一种约定,它最早由浏览器厂商Netscape公司提出,现在已经成为浏览器最基础最核心的安全功能。所谓同源(即指在同一个域)就是两个页面的具有相同的协议(protocol),主机(host)和端口号(port),例如http://a.comhttps://a.com就不是同源的,因为协议不同。

浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。
在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域访问问题。

同源策略主要限制了三个方面:

  • 当前域下的 js 脚本不能够访问其他域下的 cookie、localStorage 和 indexDB。
  • 当前域下的 js 脚本不能够操作访问操作其他域下的 DOM。
  • 当前域下 ajax 无法发送跨域请求。

 

但是有三个标签是允许跨域加载资源:

  • <img src='xxx'>
  • <link href='xxx'>
  • <script src='xxx'>

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。

二、跨域的解决办法

常见的解决方案有很多种,比如:

  • JSONP跨域
  • 跨域资源共享 CORS
  • document.domain + iframe 跨域解决方案
  • window.name + iframe 跨域解决方案
  • location.hash + iframe 跨域解决方案
  • postMessage跨域解决方案
  • WebSocket协议跨域解决方案
  • node代理跨域解决方案
  • nginx代理跨域解决方案

在这些众多的解决方案中,最常用的是JSONP和CORS,其中比较传统的跨域解决方案是 JSONP。JSONP 虽然能解决跨域问题,但是有一个很大的局限性,那就是 只支持 GET 请求,而不支持其他类型的请求,在 RESTful 时代几乎就没什么用。

另外一种常见的解决方案是 CORS(跨域源资源共享,Cross-Origin Resource Sharing),它是一个 W3C 标准,或者说是一种针对浏览器的技术规范,提供了 Web 服务从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,进而实现跨域访问。

我这里主要是在Golang项目中利用CORS技术,来解决跨域方案。

三、CORS概述 

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。IE8+:IE8/9需要使用XDomainRequest对象来支持CORS。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。 因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨域通信。

浏览器将CORS请求分成两类:简单请求和非简单请求。

 简单请求

若请求满足所有下述条件,则该请求可视为简单请求:

  1. 请求方法是以下三种方法之一:HEAD、GET、POST。
  2. 除了被用户代理自动设置的头字段(例如 Connection、User-Agent等),HTTP的头信息不超出以下几种字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type。
  3. Content-Type标头所指定的媒体类型的值仅限于下列三者之一:
    1. text/plain
    2. multipart/form-data
    3. application/x-www-form-urlencoded
  4. 如果请求是使用 XMLHttpRequest 对象发出的,在返回的 XMLHttpRequest.upload 对象属性上没有注册任何事件监听器;也就是说,给定一个 XMLHttpRequest 实例 xhr,没有调用 xhr.upload.addEventListener(),以监听该上传请求。
  5. 请求中没有使用 ReadableStream对象。

简单请求基本流程 

  1. 对于简单请求,浏览器直接发出CORS请求。具体来说,就是在发送AJAX请求的1时候,浏览器如果发现是跨域的请求,浏览器会自动在请求中增加一个Origin字段。 Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
  2. GET /cors HTTP/1.1
    Origin: http://api.bob.com
    Host: api.alice.com
    Accept-Language: en-US
    Connection: keep-alive
    User-Agent: Mozilla/5.0
    ...
  3. 如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。当浏览器接收到服务器的响应之后会去响应头查找Access-Control-Allow-Origin字段,如果没有则会报错。抛出的错误会被XMLHttpRequest的onerror回调函数捕获。 
  4. 如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。 
字段必须/可选解释
Access-Control-Allow-Origin必须允许跨域的源,要么是一个*,表示接受任意域名的请求。
Access-Control-Allow-Credentials可选表示服务器是否允许客户端发送Cookie。默认情况下,Cookie可以包含在请求中,一起发给服务器,如果服务器不需要浏览器发送Cookie,删除该字段即可。
Access-Control-Expose-Headers可选CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。如指定Access-Control-Expose-Headers: FooBar,则可通过getResponseHeader(‘FooBar’)获取FooBar字段的值。
CORS简单请求跨域

非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。

预检请求

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

预检请求

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

除了Origin字段,"预检"请求的头信息包括两个特殊字段。

  • Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是POST。
  • Access-Control-Request-Headers:该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是Authorization和Content-Type

预检请求的回应

服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应

浏览器正常请求回应

一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

四、实战案例

这里以作者自己开发的前后端项目为例。

简单请求

我们可以看到在没有任何跨域配置的情况下跨域访问失败。

简单请求跨域配置

这里我们使用Gin官方提供的cors解决方案gin-contrib/cors

package middleware

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

// Cors 跨域配置
func Cors() gin.HandlerFunc {
	config := cors.DefaultConfig()
	config.AllowMethods = []string{"GET"}
	config.AllowHeaders = []string{"Origin"}
	config.AllowOrigins = []string{"http://localhost:8080"}
	config.AllowCredentials = true
	return cors.New(config)
}

重新运行服务器,发起请求:

可以看到响应包中增加了加了几个字段

响应数据也能正常接收到了。

复杂请求

使用POST方法向服务器发出点赞请求,可以看到浏览器发出了预检请求。

复杂请求跨域配置 

我们根据请求头和报错信息对cor中间件的配置进行相应修改:

package middleware

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

// Cors 跨域配置
func Cors() gin.HandlerFunc {
	config := cors.DefaultConfig()
	config.AllowMethods = []string{"GET", "POST"}
	config.AllowHeaders = []string{"Origin", "Content-Type", "Authorization"}
	config.AllowOrigins = []string{"http://localhost:8080"}
	config.AllowCredentials = true
	return cors.New(config)
}

重新运行服务器,发起请求: 

可以看到第二次的预检请求和发送请求都操作成功了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值