tags: [组件]
categories: 业务场景解决方案


什么是跨域

由于浏览器的同源策略,导致只要协议、域名、端口有任何一个不同,都被当作是不同的域,从而导致了跨域访问的需求.

  • 服务器端不存在跨域

  • 不能通过ajax的方法去请求不同源中的文档

  • 不同域的框架之间是不能进行JS交互操作的,不同的框架之间是可以获取window对象的,但是无法获取响应的属性和方法

  • 如果是协议和端口造成的跨域问题“前台”是无能为力的

  • 在跨域问题上,域仅仅是通过“URL首部”来识别,而不会去尝试判断相同的IP地址对应着两个域或两个域是否在同一个IP上

    • URL首部:window.location.protocol + window.location.host
  • 针对接口的请求

  • 针对Dom的查询

跨域的方法

双向通信:即两个iframe,页面与iframe或是页面与页面之间
单向跨域:一般用来获取数据

iframe + document.domain

将两个页面的 document.domain 都设成相同的域名(只能设置成自身或更高一级的父域,且主域必须相同)

共享Cookie

服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.hello.com

  1. Set-Cookie: key=value; domain=.hello.com; path=/

共享数据

  1. <!--a页面-->
  2. <iframe src="http://bbb.hello.com/b.html" onload="load()" id="frame"></iframe>
  3. <script>
  4. document.domain = 'hello.com';
  5. let a = "this is a";
  6. // 获取b页面数据
  7. function load(){
  8. let frame = document.getElementById("frame")
  9. console.log(frame.contentWindow.b) // this is b
  10. }
  11. </script>
  1. <!--b页面-->
  2. <script>
  3. document.domain = 'hello.com';
  4. let b = "this is b"
  5. // 获取a页面数据
  6. console.log(window.parent.a); // this is a
  7. </script>

ifame + location.hash

父窗口可以对iframe进行URL读写,iframe也可以读写父窗口的URL
hash一般用于浏览器锚点定位,Server端并不关心这部分,应该说HTTP请求过程中不会携带hash,所以这部分的修改不会产生HTTP请求,但是会产生浏览器历史记录。

页面a:[http://www.hello.com/a.html](http://www.hahaha0.com/a.html)

  1. <!--a中通过iframe引入了b-->
  2. <iframe id="frame" src="http://www.hello1.com/b.html"></iframe>
  3. <script> let frame = document.getElementById('frame');
  4. // 向b传hash值
  5. frame.src = frame.src + '#a=我是a';
  6. // 给同域c使用的回调方法
  7. function cb(data) {
  8. console.log(data) // 打印 我是a+b
  9. }</script>

页面b:[http://www.hello1.com/b.html](http://www.hahaha1.com/b.html)

  1. <!--b中通过iframe引入了中间人c-->
  2. <iframe id="frame" src="http://www.hello0.com/c.html"></iframe>
  3. <script> let frame = document.getElementById('frame');
  4. // 监听a传来的hash值,传给c.html
  5. window.onhashchange = function () {
  6. frame.src = frame.src + location.hash + '+b';
  7. };</script>

页面c:[http://www.hello0.com/c.html](http://www.hahaha0.com/c.html)

  1. <script> // 监听 b 的hash值变化
  2. window.onhashchange = function () {
  3. // c调用父亲的父亲,来操作同域a的js回调,将结果传回
  4. window.parent.parent.cb(location.hash.replace('#a=', ''));
  5. };</script>

iframe + window.name

页面a:[http://www.hello1.com/abc/a.html](http://www.hahaha1.com/abc/a.html)

  1. <iframe src="http://www.hello2.com/abc/b.html" id="frame" onload="load()"></iframe>
  2. <script> let flag = true
  3. // onload事件会触发2次
  4. // 第1次onload跨域页b成功后,留下数据window.name,后切换到同域代理页面
  5. // 第2次onload同域页c成功后,读取同域window.name中数据
  6. function load() {
  7. if(flag){
  8. // 第1次
  9. let frame = document.getElementById('frame')
  10. frame.src = 'http://www.hello1.com/abc/c.html'
  11. flag = false
  12. }else{
  13. // 第二次
  14. console.log(frame.contentWindow.name) // 我是b
  15. }
  16. }</script>

页面b:[http://www.hello2.com/abc/b.html](http://www.hahaha2.com/abc/b.html)

  1. <script> window.name = '我是b' </script>

H5 的 postMessage

IE8+

  • 发送信息的postMessage事件: otherWindow.postMessage(message, targetOrigin)

    • otherWindow: 目标窗口,也就是给哪个window发消息,是window.frames属性的成员或者window.open创建的窗口

    • message:要发送的消息,String | Object

    • targetOrigin: 限定消息接收范围,不限制请使用“*”

    • transfer(可选): 是一串和 message 同时传递的 「Transferable」 对象,这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权
  • 监听接收信息的 message 事件

  • 既可以处理 Get 请求也可以处理 POST 请求

页面a:[http://www.hello1.com/abc/a.html](http://www.hahaha1.com/abc/a.html),创建跨域 iframe 并发送信息

  1. <iframe src="http://www.hello2.com/abc/b.html" id="frame" onload="load()"></iframe>
  2. <script>
  3. function load() {
  4. let frame = document.getElementById('frame')
  5. // 发送
  6. frame.contentWindow.postMessage('哈喽,我是a', 'http://www.hahaha2.com/abc/b.html')
  7. // 接收
  8. window.onmessage = function(e) {
  9. console.log(e.data) // 你好,我是b
  10. }
  11. }</script>

页面b:[http://www.hello2.com/abc/b.html](http://www.hahaha2.com/abc/b.html),接收数据并返回信息

  1. <script> // 接收
  2. window.onmessage = function(e) {
  3. console.log(e.data) // 哈喽,我是a
  4. // 返回数据
  5. e.source.postMessage('你好,我是b', e.origin)
  6. }</script>

JSONP

通过script标签引入的JS是不受同源策略的限制的,可以通过script标签引入一个文件,此文件必须返回一个JS函数的调用(要和后端约定好)
只能实现GET请求
jquery的getJSON()会自动判断是否跨域,不跨域就调用普通的ajax();
跨域则会以异步加载JS文件的形式来调用JSONP的回调函数

JSONP优点:

  • 不像XHR对象实现Ajax请求那样受到同源策略的限制

  • 兼容性更好,不需要XHR或者ActiveX的支持

  • 在请求完毕后可以通过调用callback的方式回传结果

JSONP缺点:

  • 只支持GET而不支持POST等其它类型的HTTP请求

  • 只支持跨域HTTP请求的情况,不能解决不同域的两个页面之间如何进行JS调用的问题

页面a:[http://www.hello1.com/abc/a.html](http://www.hahaha1.com/abc/a.html)

  1. <script type="text/javascript"> //回调函数
  2. function cb(res) {
  3. console.log(res.data.b) // 我是b
  4. }</script>
  5. <script type="text/javascript" src="http://www.hello2.com/abc/b.js"></script>

页面b:[http://www.hello2.com/abc/b.js](http://www.hahaha2.com/abc/b.js)

  1. var b = "我是b"
  2. // 调用cb函数,并以json数据形式作为参数传递
  3. cb({
  4. code:200,
  5. msg:"success",
  6. data:{
  7. b: b
  8. }
  9. })

CORS

IE10+

CORS:

  • 使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。

  • 实现CORS通信的关键是服务器,只要服务器实现了CORS接口,就可以跨源通信。

  • 服务器端对于CORS的支持,主要通过设置Access-Control-Allow-Origin来进行。

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

CORS与JSONP相比,无疑更为先进、方便和可靠:

  • JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求

  • 使用CORS,开发者可以使用普通的XHR发起请求和获得数据,比起JSONP有更好的错误处理

  • JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS

  1. // 来源
  2. Origin: http://www.hahaha.com
  3. // 该CORS请求的请求方法
  4. Access-Control-Request-Method: POST
  5. // 额外发出的头信息字段
  6. Access-Control-Request-Headers: X-Token, X-Test
  7. // 允许发送 Cookie 和 HTTP 认证信息, 同时客户端也需要设置‘xhr.withCredentials = true’
  8. Access-Control-Allow-Credentials: true

:::tips 注意,如要发送 CookieAccess-Control-Allow-Origin 字段就不能设为星号,必须指定明确的、与请求网页一致的域名,同时,Cookie 依然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传,其他域名的 Cookie 并不会上传,且(跨源)原网页代码中的 document.cookie 也无法读取服务器域名下的 Cookie ,下面还会提到 :::

预检请求报错

  1. No 'Access-Control-Allow-Origin' header is present on the requested resource
  2. The response had HTTP status code 404

告诉后端请允许下 OPTIONS 请求:服务端没有设置允许 OPTIONS 请求,那么在发起该预检请求时响应状态码会是404,因为无法找到对应接口地址.

  1. No 'Access-Control-Allow-Origin' header is present on the requested resource
  2. The response had HTTP status code 405

告诉后端请关闭对应的安全配置:服务端已经允许了 OPTIONS 请求,但是一些配置文件中(如安全配置)阻止了 OPTIONS 请求.

  1. No 'Access-Control-Allow-Origin' header is present on the requested resource
  2. OPTIONS 请求 status 200

告诉后端请增加对应的头部支持:服务器端允许了 OPTIONS 请求,配置文件中也没有阻止,但是头部匹配时出现不匹配现象。所谓头部匹配,就比如 Origin 头部检查不匹配,或者少了一些头部的支持(如 X-Requested-With 等),然后服务端就会将 Response 返回给前端,前端检测到这个后就触发 XHR.onerror ,从而导致报错。

  1. OPTIONS 请求 status 500

告诉后端检测到预检请求时,请把它搞成200:服务端针对 OPTIONS 请求的代码出了问题,或者没有响应。

其他

  • 中间件跨域

  • 服务器代理跨域

  • Flash URLLoader跨域

  • 动态创建 script 标签

  • websocket协议支持跨域: ws没有同源限制,客户端可以与任意服务器通信

遇到的跨域问题

单点登陆嵌入子系统页面

通过 iframe + document.domain 的方式解决

icon-font 放到 GitHub 上导致跨域问题

通过将icon-font 的 url 改成 base64 的形式直接嵌入到页面中

这样会产生跨域问题

  1. @font-face {
  2. font-family: 'idoll-icon-online';
  3. src: url('@{icon-url}/idoll-icon-online.eot?67rfls');
  4. src: url('@{icon-url}/idoll-icon-online.eot?67rfls#iefix') format('embedded-opentype'),
  5. url('@{icon-url}/idoll-icon-online.ttf?67rfls') format('truetype'),
  6. url('@{icon-url}/idoll-icon-online.woff?67rfls') format('woff'),
  7. url('@{icon-url}/idoll-icon-online.svg?67rfls#idoll-icon-online') format('svg');
  8. font-weight: normal;
  9. font-style: normal;
  10. }

添加了base64的方式便不会跨域了

  1. @font-face {font-family: "idoll-icon-pro2";
  2. src: url('@{icon-url}/idoll-icon-pro2.eot?t=1544695441349'); /* IE9*/
  3. src: url('@{icon-url}/idoll-icon-pro2.eot?t=1544695441349#iefix') format('embedded-opentype'), /* IE6-IE8 */
  4. url('data:application/x-font-woff;charset=utf-8;base64,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') format('woff'),
  5. url('@{icon-url}/idoll-icon-pro2.ttf?t=1544695441349') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
  6. url('@{icon-url}/idoll-icon-pro2.svg?t=1544695441349#iconfont') format('svg'); /* iOS 4.1- */
  7. }

资源

前端跨域知识总结
最直白的跨域访问原理
浏览器的同源策略
不要再问我跨域的问题了
前端跨域问题解决方案(基于node与nginx) - 掘金
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS