前言
跨域问题,已困扰面试者久已。今天,就把它安排的明明白白的。
什么是跨域
首先得知道什么是跨域,为什么会有跨域这个问题。
原因是:浏览器的同源策略
原因总结:
出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。 (译者注:这段描述不准确,并不一定是浏览器限制了发起跨站请求,也可能是跨站请求可以正常发起,但是返回结果被浏览器拦截了。)
上面一段话摘录于MDN,但不太准确,我个人认为:跨域的问题,不是浏览器拦截了请求的发送,而是浏览器拦截了请求的响应
触发跨域的判断条件
只要出现下面的任一情况,就算不同源(即跨域)
- 协议不同(例如:http和https)
- 端口不同
- 域名不同(例如:a.baidu.com和b.baidu.com)
tips:在我们日常开发的时候,本地前端项目起了一个8080端口的服务,然后又起一个3000端口的后端项目服务。这个时候,就跨域了(端口不一致)
怎么解决跨域呢
老生常谈的jsonp
(前端+后端)
应该没人在用这个方式去解决跨域问题了,但不排除可能出现适合使用jsonp
的场景。
前端构造script标签请求指定URL(由script标签发出的GET请求不受同源策略限制),服务器返回一个函数 执行语句,该函数名称通常由查询参callback的值决定,函数的参数为服务器返回的json数据。该函数在前 端执行后即可获取数据。
代理服务器(前端或者nginx)
请求同源服务器,通过该服务器转发请求至目标服务器,得到结果再转发给前端。
前端开发中测试服务器的代理功能就是采用的该解决方案,但是最终发布上线时如果web应用和接口服务器 不在一起仍会跨域。
CORS(Cross Origin Resource Share) - 跨域资源共享(后端)
后端只需要在响应报文中设置一个请求头即可:
(图片来自MDN)如上图的例子:
- 客户端发起请求的时候带上了
Origin: Server-b.com
- 服务端响应的时候返回了
Access-Control-Allow-Origin: *
使用 Origin
和 Access-Control-Allow-Origin
就能完成最简单的访问控制。
本例中,服务端返回的 Access-Control-Allow-Origin: *
表明,该资源可以被任意外域访问。
假如只想被http://foo.example
访问,则可以这么设置:Access-Control-Allow-Origin: http://foo.example
Proxy代理模式
处理跨域的时候,还有一个比较重要的方式:代理
原理是:
-
跨域是在浏览器才会发生的,在服务器端是不存在这个概念的。
-
浏览器发送请求时,还是按正常的请求发送,例如:
http://foo.example
网页要请求http://hotao.example/api/user
接口。(直接按详细的url请求会跨域)http://foo.example
网页发出http://foo.example/api/user
的请求
-
然后,在本地服务器(比如webpack启动的node服务器)对接口做一次拦截,如果是
/api/
所匹配的,则把这个接口转发到目标地址http://hotao.example/api/user
中。 -
这样,浏览器发送请求和接收请求时,都是同一个源。而转发的操作在node服务器上完成的。所以,不会触发跨域。
具体案例
vue-cli的项目中,可以为开发环境做代理配置:
上述配置中,端口5678
且请求地址匹配/api/
则代理到http://localhost:4000
。
例如:请求http://localhost:5678/api/userList
将会被代理到http://localhost:4000/api/userList
nginx中的代理配置
nginx中的原理和vue-cli的也是比较类似。稍微做一点配置即可。
图中的配置,将会把80
端口的/api
请求代理到http://127.0.0.1:4000
服务器上。
例如:http://www.hotao.work/api/userList
会被代理到http://127.0.0.1:4000/api/userLis
跨域的问题就到这里了。
后记
实际工作中,经常会遇到请求发送两遍:
- options请求
- 原请求
为什么会出现这个情况呢?
这与cors的预检请求
有关,相关资料在MDN上有详细解释,有兴趣可以了解一下。