浏览器跨域请求问题
文章目录
背景
有一个前后端分离的项目,前端向后端业务发起了一个注册的请求,从 network 中看到下面的现象:
点击第一个请求,看到标头发现请求的URL是 http://localhost:28080/api/v1/users/signup
:
再点击第二个请求,标头发现请求的URL也是 http://localhost:28080/api/v1/users/signup
:
再看下服务器接收到的请求输出:
|
|
想一想:我明明发送点击了一次,为什么会有两次请求呢?
原因分析
上面问题其实就是跨域要解决的问题,就是请求会发送的一个 preflight
的请求,这个可以从控制台看到,比如 console 中的输出如下:
|
|
这个现象就是所谓的 跨域
。从上面信息可以知道两点:
http://localhost:3000
向http://localhost:28080/api/v1/users/signup
发起的请求被阻塞了。- 对预检请求的响应未通过访问控制检查,preflight 并没有传递
access control check
导致上面问题的原因就是因为请求的资源上没有设置 “Access-Control-Allow-Origin” 标头。 在哪里设置?是在 preflight request
中设置。
什么是跨域
一个网站请求另一个网站,两个网站存在协议(如http、https)、域名(如a.com, b.com)、端口(如a.com:8080, a.com:8081)任意一个不同,就认为是跨域请求。
浏览器是怎么判断是否是两个网站的呢?
- 通过协议判断
- 通过端口判断
- 通过域名判断
比如请求是从 localhost:3000
发送到后端 localhost:28080
的。在请求时,浏览器认为 localhost:3000
是一个网站,localhost:28080
是一个网站。现在是从一个网站发送请求到另一个网站,浏览器就不答应,因为攻击者可以伪造请求发送到后端服务器,浏览器是不允许这样的行为存在的。
主要是浏览器不允许这种行为,而后端无所谓,所以跨域请求其实就是浏览器阻止的。这种现象就是跨域请求。
协议、域名和端口任意一个不同,也都是跨域请求。正常来说,如果我们不做额外处理,是没有办法发送请求的,这就会出现跨域现象。
想一想 浏览器为什么这么设计呢?主要是为了防止黑客伪造一些乱七八糟的东西不断地发送请求。
如何解决跨域问题
解决思路:
- 跨域的问题是浏览器不准请求从一个网站发往另一个网站。要解决这个问题,那就让浏览器允许将请求从一个网站发往另一个网站。
- 是什么让浏览器不允许发送请求呢?就是浏览器觉得网站携带的内容可能存在危险。
- 怎么才能让浏览器认为携带的内容没有危险呢?就是让浏览器告诉背后的服务器,请求的带了什么,背后的服务器也明确出给这个什么东西不危险。此时浏览器才放心请求允许通过。
比如问浏览器,localhost:28080
是否可以接收从 localhost:3000
过来的请求。浏览器因为跨域不让发送请求过来,要解决这个问题就让浏览器允许它发送请求过来。
关键是怎么告诉浏览器呢?谁告诉谁?比如:localhost:18080
怎么告诉 localhost:3000
允许将请求发送过来呢?
需要通过一个叫 preflight
请求的机制。需要在 preflight
请求里面告诉浏览器,让其允许接收 localhost:3000
发过来的请求。
这就是为什么上面【想一想】提到的为什么会有两个请求,第一个请求是先问下 localhost:28080 能不能接收 localhost:3000 的请求,也就是下面这个请求,这就是 preflight
:
看到 preflight
可以理解为是提前问一下后端允不允许 localhost:3000
的请求过来。然后 localhost:28080
回复浏览器说要, 就着浏览器就会将 localhost:3000
的请求发过来,也就是这个请求:
下面就是如何告诉localhostL:28080它呢?关键就是要在 preflight
请求中配置一些参数,就要需要在 preflight
请求的响应里面进行配置,也就是在过来询问是否允许的时候告诉它。
preflight 请求流程
- preflight 请求会发送到同一个地址上,使用 Options 方法,没有请求参数。
比如正常请求注册接口 /users/signup
,就会有一个 preflight
的请求也发送到 /users/signup
,但是它的 method 是 Options
并告诉 /users/signup
请求携带了哪些内容,比如携带了 authorization,请求方式是 POST:
后端在收到 preflight
请求之后就要告诉它允许接收从 localhost:3000 发过来的请求(Allow-Origins: http://localhost:3000),也可以接收它在 Header 里面携带 Content-Type(Allow-Headers),以及能接受的方法包括(Allow-Methods: 全部方法)。
然后紧接着请求方就会把真正的请求发送过去,如何看出来的呢?可以看请求标头,如下图所示:
也就是 localhost:3000 告诉localhost:28080, 我接下来有个请求要发送给你,发送的请求要带两个头部,一个 authorization, 一个是 content-type,我发送的请求是 post 请求。
也有点像亲戚给你介绍对象,先告诉你,她那里有个姑娘170,100斤,很漂亮,你要不要(这就是 preflighst 请求),这时你告诉她要,然后她就会把人介绍给你,介绍给你这就是正式的业务请求。
后端如何应答 preflight 请求?
这其实就是后端的跨域解决方案的视线。大多数的web框架都提供的跨域的解决方案。
在 Go 中的解决方案就是跨域的 middleware。配置之后查看相应头,这些就是后端和浏览器preflight沟通后允许的请求的内容:
middleare 可以参考 [零散知识点整理]。
总结
解决跨域配置什么也是看请求的标头携带的什么 ,cors 跨域就配置什么?
- 跨域问题是因为发送请求的协议+域名+端口和接受请求的协议+域名+端口对应不上,比如上面的localhost:3000发送到 localhost:8080上。
- 解决跨域问题的关键是在 preflight 请求里面告诉浏览器自己愿意接受请求。
- 一般框架都有跨域解决方案。