跨域问题的来源是浏览器为了请求安全而引入的基于同源策略 的安全特性,当页面和请求的协议、主机名或端口不一致时,浏览器判定两者不同源,即为跨域请求。
注意事项:
- 跨域请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了
- 表单的方式可以发起跨域请求是因为表单并不会获取新的内容
- 有三个标签是允许跨域加载资源:
img、link、script
- 跨域是浏览器的限制,服务端并不受此影响
1 CORS (Cross-Origin Resource Sharing)
CORS
是目前最为广泛的解决跨域问题的方案。方案依赖服务端/后端在响应头中添加Access-Control-Allow-*
头,告知浏览器端通过此请求。
涉及到的端
CORS 只需要服务端/后端支持即可,不涉及前端改动。
具体实现方式
CORS 将请求分为简单请求(Simple Requests) 和需预检请求(Preflighted requests) ,不同场景有不同的行为。
简单请求
不会触发预检请求的称为简单请求。当请求同时满足以下条件时就是一个简单请求:
- 请求方法:GET、HEAD、POST。
- 请求头:Accept、Accept-Language、Content-Language、Content-Type。
- Content-Type 仅支持:application/x-www-form-urlencoded、multipart/form-data、text/plain。
需预检请求
当一个请求不满足以上简单请求的条件时,浏览器会自动向服务端发送一个 OPTIONS 请求
js
OPTIONS /resource HTTP/1.1
Host: example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
通过服务端返回的 Access-Control-Allow-*
判定请求是否被允许
js
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
CORS
引入了以下几个以Access-Control-Allow-*
开头:
Access-Control-Allow-Origin
表示允许的来源Access-Control-Allow-Methods
表示允许的请求方法Access-Control-Allow-Headers
表示允许的请求头Access-Control-Allow-Credentials
表示允许携带认证信息
当请求符合响应头的这些条件时,浏览器才会发送并响应正式的请求。
2 反向代理
反向代理解决跨域问题的方案依赖同源的服务端对请求做一个转发处理,将请求从跨域请求转换成同源请求。
涉及到的端
反向代理只需要服务端/后端支持,几乎不涉及前端改动,只用切换接口即可。
具体实现方式
反向代理的实现方式为在页面同域下配置一套反向代理服务,页面请求同域的服务端,服务端请求上游的实际的服务端,之后将结果返回给前端。
3 JSONP
JSONP
是一个相对古老的跨域解决方案。利用 script
标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据
- 需要对方的服务器做支持
- 优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。
- 缺点是仅支持get方法具有局限性、不安全可能会遭受XSS攻击
JSONP
都是GET和异步请求的,且jQuery默认就会给JSONP
的请求清除缓存
具体实现方式
- 声明一个回调函数,其函数名(如show)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)。
- 创建一个script标签,把那个跨域的API数据接口地址,赋值给script的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=show)。
- 服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是show,它准备好的数据是show('我不爱你')。
- 最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(show),对返回的数据进行操作。
非常用方式
postMessage
- 即在两个 origin 下分别部署一套页面 A 与 B,A 页面通过 iframe 加载 B 页面并监听消息,B 页面发送消息。
window.name
- 主要是利用 window.name 页面跳转不改变的特性实现跨域,即 iframe 加载一个跨域页面,设置 window.name,跳转到同域页面,可以通过 $('iframe').contentWindow.name 拿到跨域页面的数据。
document.domain
- 可将相同一级域名下的子域名页面的 document.domain 设置为一级域名实现跨域。
- 可将同域不同端口的 document.domain 设置为同域名实现跨域(端口被置为 null)。
websocket
Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
原生WebSocket API使用起来不太方便,可以使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
总结
-
CORS支持所有类型的HTTP请求,可以更细粒度地控制哪些域可以访问资源
-
JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
-
不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制。
-
日常工作中,用得比较多的跨域方案是cors和nginx反向代理