什么是跨域?
在了解跨域之前,我们需要了解浏览器一个非常重要的安全策略——同源策略(Same Origin Policy)。
所谓同源策略,是指浏览器只允许 JavaScript 读取相同【协议、域名、端口】下的数据以保证用户的信息安全,防止恶意网站窃取数据。其限制的内容主要包括:Cookie、LocalStorage 和 IndexDB 中数据的读取;AJAX 请求的发送;DOM 元素的获取等。该安全策略 1995 年由网景公司(Netscape)提出。
跨域即非同源策略请求,是想要访问不同【协议、域名、端口】下的数据。就好像显示生活中你想要的获取别人家银行卡密码,这显然是不应当被允许的。所以说,应当尽可能地避免允许跨域请求。
为什么要跨域?
虽然我们希望尽可能地避免跨域请求,但是面对一些实际的场景又不得不跨域请求。比如:
- 前后端分离开发,开发过程中后端与前端网络地址不同
- 实际生产环境中,有必要将前后端分别部署到多个服务器上(大型项目考虑安全及性能问题可能会将数据服务器、网站服务器、文件服务器分离)
- 项目中需要请求一些第三方 API 服务,比如一些图文识别、语音识别、人脸识别、天气预报、在线支付等
如何跨域?
常见的跨域资源包括 JSONP 或者 CORS 等,下面分别简单说明一下。
注意:所有的跨域都需要在服务器端做一些设置
1.JSONP 跨域
一些带 src 或 href 属性的 HTML 标签可以不受同源策略限制,比如:script、img、link、iframe 等。JSONP 即是通过动态创建 script 标签,引入外部 JavaScript 文件实现跨域的。与其说是一种技术,JSONP 更像是一个 BUG。 值得注意的是 JSONP 仅支持 GET 请求。
原生 JavaScript 实现JSONP
服务器代码
使用基于 nodejs 的 express 搭建的服务器,注意 handle 处理函数
客户端代码
jQuery 实现JSONP
上面原生方法中需要服务器提供一个具体名称的处理函数(比如这里的 handle),这样不可避免的会增大开发过程中前后端的沟通成本,为此我们考虑可以由前端传递一个函数名给后端,后端根据传递的函数名动态生成处理函数名会更方便。jQuery 中就封装了这么一个方法实现 JSONP 请求。
jQuery 通过 JSONP 发起跨域请求的时候,需要在请求地址后带上 ?callback=functionName 这么一个参数,functionName 为告诉后端前端处理数据的函数,后端动态创建此函数名为处理函数即可。
服务端代码
使用基于 nodejs 的 express 搭建的服务器,注意动态生成的处理函数
客户端代码
2.CORS 跨域
跨源资源共享 (Cross Origin Resource Sharing) 是一种基于 HTTP 头的机制,它允许服务器标示除了同源请求以外的其它请求。相比于 JSONP 只能使用 GET 请求,CORS 可以自定义允许的请求。CORS 的配置项包括:
- Access-Control-Allow-Origin :设置允许通过的请求源【协议、域名、端口】(只能写一个源,不太好用)
- Access-Control-Allow-Methods :设置允许的请求方式【GET、POST、OPTION、HEAD、PUT、DELETE、TRACE、CONNECT】
- Access-Control-Allow-Credentials : 设置是否允许证书验证【true 或 false】
- Access-Control-Max-Age :设置允许预检请求的最大超时时间,单位:秒(s)
- Access-Control-Allow-Headers :设置允许的请求头信息【Content-Type,Authorization…】
- Access-Control-Expose-Headers : 设置允许公开的请求头信息【Content-Type,Authorization…】
- Access-Control-Request-Headers :设置请求预检时让服务器知道哪些头信息将在正式请求时被使用【Content-Type,Authorization…】
- Access-Control-Request-Methods :设置请求预检时让服务器知道哪些方法将在正式请求时被使用【GET、POST、OPTION、HEAD、PUT、DELETE、TRACE、CONNECT】
服务端代码
使用基于 nodejs 的 express 搭建的服务器,注意 CORS 相关设置
客户端代码
前端代码与正常请求代码一样即可。
3.HTTP 代理服务器跨域
服务器与服务器之间不存在跨域问题。通过设置本地服务器去访问目标服务器,然后本地服务器返回数据就不存在跨域问题。WebpPack 中提供 devServer 参数用于配置开发服务器,其中有个 proxy 配置项可用于配置代理服务器,实现开发阶段的跨域需求。
webpack.config.js 配置
注意,这里 WebPack 会自动将 /proxy-server 下的请求信息,自动代理到 http://127.0.0.1:3000 源下。更多配置可参考WebPack devServer
请求代码
请求时正常请求即可,不过需要注意的是,这只适合开发阶段的跨域需求。
4.Nginx 反向代理
使用 Nginx 反向代理将 www.a.com/apis/ 的请求代理到 www.b.com 的源下
5.iframe + postMessage 跨域
该方法使用 iframe 标签及 postMessage 接口实现了 Window 对象之间的跨域通信。
B页面(服务端)核心代码
A页面(客户端)核心代码
更多 postMessage 接口信息参考:postMessage
其他通过 iframe 标签实现跨域的方式有:
- iframe + document.domain 跨域
- iframe + window.name 跨域
- iframe + location.hash 跨域
各有优缺点,基本上不会用到,此处略过。
6.WebSocket 跨域
WebSocket 是基于 TCP 的一种新的网络通信协议,它实现了服务器与浏览器间的全双工通信(允许服务器主动发送信息给客户端)。从 HTML5 开始,RFC6455 定义了它的通信标准。WebSocket 对象作为一个构造函数,在使用前需要将其实例化:
- url :必填,请求的地址
- protocol :可选,可接受的子协议
属性
- readyState :只读,连接状态(0-未连接;1-已连接,可通信;2-正在关闭;3-已经关闭)
- bufferedAmount :只读,已被 send 放入队列中等待传输,但还没有发出的 UTF-8 文本字节数
事件
- onopen :连接建立时触发
- onmessage :客户端接收服务端数据时触发
- onerror :通信发生错误时触发
- onclose :连接关闭时触发
方法
- send() :使用连接发送数据
- close() :关闭连接
服务器代码
这里服务器端使用的 express-ws 建立的 WebSocket 链接
客户端代码
更多 WebSocket 内容参考:WebSocket