【专题版】前端面试知识点(跨域问题)更新中

322 阅读3分钟

跨域问题

相关资料

一、相关原理

URL的组成

URL的组成部分

image.png

  • 协议(protocol) :互联网支持多种协议,必须指明网址使用哪一种协议,默认是HTTP协议,HTTPS是HTTP的加密版本,处于安全考虑,越来越多的网站使用这个协议。
  • 主机:主机是资源所在网站名或服务器的名字,又叫域名。
  • 端口:同一域名下面可能包含多个网站,它们之间通过port分开。
  • 路径:路径是资源网站的位置。如上图所示:
  • 查询参数:是提供给服务器的额外参数。参数的位置在路径后面,两者之间使用分隔。
  • 锚点:网页内部的定位点,浏览器加载页面以后,会自动滚动到锚点所在的位置。锚点是通过网页元素的id属性命名的。

跨域

同源策略:是浏览器对 JavaScript 实施的安全限制,只要协议、域名、端口有任何一个不同,都被当作是不同的域。

跨域:是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的。跨域只会出现在浏览器上,小程序和APP开发不会有跨域问题

跨域原理,即是通过各种方式,避开浏览器的安全限制

跨域场景

  • 项目上线后,访问地址与接口请求地址不一致,存在跨域
  • 项目开发阶段,前后端联调接口,存在跨域
  • iframe跨文档数据通信

解决跨域方案

1. CORS,跨域资源共享

CORS(Cross-Origin Resource Sharing)是基于http1.1的一种跨域解决方案,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的 Web 应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域名、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。而在 cors 中会有 简单请求 和 复杂请求的概念。

浏览器支持情况CORS 需要浏览器和后端同时支持,当使用 IE<=9, Opera<12, or Firefox<3.5 或者更加老的浏览器,这个时候请使用JSONP。

简单请求

不会触发 CORS 预检请求。这样的请求为“简单请求”,请注意,该术语并不属于 Fetch (其中定义了 CORS)规范。若请求同时满足所有下述条件,则该请求可视为“简单请求”:

  • 条件一:请求方式是GET、HEAD、POST 方法之中的一种

  • 条件二:请求头仅包含安全的字段,常见的安全字段如下:

    • AcceptAccept-Language
    • Content-LanguageContent-Type
    • DPRDownlink
    • Save-DataViewport-WidthWidth
  • 条件三:请求头如果包含Content-Type,仅限下面的值之一:

    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded

非简单请求

除以上情况外的。

2. Node 正向代理

实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。  代理服务器,需要做以下几个步骤:

  • 接受客户端请求。
  • 将请求 转发给服务器。
  • 拿到服务器 响应 数据。
  • 将 响应 转发给客户端。

3. Nginx 反向代理

实现原理类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。

使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。

实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

4. JSONP

JSONP原理:利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。

使用限制:仅支持 GET 方法,如果想使用完整的 REST 接口,请使用 CORS 或者其他代理方式。

和AJAX对比:JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)

JSONP优点:简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。

JSONP缺点

  • 仅支持get方法具有局限性,不安全可能会遭受XSS攻击
  • JSONP 需要后端配合返回指定格式的数据

JSONP的做法是:当需要跨域请求时,不使用AJAX,转而生成一个script元素去请求服务器,由于浏览器并不阻止script元素的请求,这样请求可以到达服务器。服务器拿到请求后,响应一段JS代码,这段代码实际上是一个函数调用,调用的是客户端预先生成好的函数,并把浏览器需要的数据作为参数传递到函数中,从而间接的把数据传递给客户端

JSONP:ajax 请求受同源策略影响,不允许进行跨域请求,而 script 标签 src 属性中的链接却可以访问跨域的 js 脚本,利用这个特性,服务端不再返回 JSON 格式的数据,而是 返回一段调用某个函数的 js 代码,在 src 中进行了调用,这样实现了跨域。 步骤:

  1. 去创建一个script标签
  2. script的src属性设置接口地址
  3. 接口参数,必须要带一个自定义函数名,要不然后台无法返回数据
  4. 通过定义函数名去接受返回的数据
      //动态创建 script
      var script = document.createElement('script');
      // 设置回调函数
      function getData(data) {
          console.log(data);
      }
      //设置 script 的 src 属性,并设置请求地址
      script.src = 'http://localhost:3000/?callback=getData';
      // 让 script 生效
      document.body.appendChild(script);

5. Websocket

Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。

原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

6. window.postMessage

window.postMessage()方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。

window.postMessage()方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的 iframe 消息传递
  • 上面三个场景的跨域数据传递

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递

otherWindow.postMessage(message, targetOrigin, [transfer]);

  • message: 将要发送到其他 window的数据。
  • targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。
  • transfer(可选):是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

7. document.domain + Iframe

该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式。 只需要给页面添加 document.domain ='test.com' 表示二级域名都相同就可以实现跨域。

实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。

例子如下:页面a.fp.cn:3000/a.html获取页面b.fp.cn:3000/b.html中a的值

// a.html
<body>
 html-a
  <iframe src="http://b.fp.cn:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
  <script>
    document.domain = 'zf1.cn'
    function load() {
      console.log(frame.contentWindow.a);
    }
  </script>
</body>
// b.html
<body>
   html-b
   <script>
     document.domain = 'fp.cn'
     var a = 100;
   </script>
</body>

8. window.location.hash + Iframe

原理:通过 url 带 hash ,通过一个非跨域的中间页面来传递数据。

9. window.name + Iframe

window 对象的 name 属性是一个很特别的属性,当该 window 的 location 变化,然后重新加载,它的 name 属性可以依然保持不变。 通过 iframe 的 src 属性由外域转向本地域,跨域数据即由 iframe 的 window.name 从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

10. 关闭浏览器策略

uni-app 项目 解决跨域办法

方法一:使用 HBuilderX 内置浏览器,内置浏览器是删除了浏览器的跨域限制的。

方法二:在项目根目录 mainfest.json 文件中添加 h5 配置。

"h5" : {
    "devServer":{
        "proxy":{
            "^/api":{
                "target":"http://127.0.0.1:8081",
                "ws": true,
                "changeOrigin": true
            }
        }
    }
}

页面请求代码示例

uni.request({
	url:'/api/1.json',    // url 要与 proxy 匹配,不能写成 xx.com/api.1.json
	method:'GET',
	success: (res) => {
		console.log(res);
	}
})

Vue.js 项目 解决跨域办法

在项目根目录 vue.config.js 文件中添加如下配置

/* 开发环境配置 */
devServer:{
    /* 代理目录 */
    proxy:{
        '^/api':{
            target:'http://127.0.0.1:8081',
            ws: true,
            changeOrigin: true
        }
    }
}

页面请求代码,以 axios 库为例

request.get('/api/1.json',{
    params
}).then(res=>{
    console.log(res)
})