常见跨域解决方案


前言

上期我们讲到CORS,这里就不讲解了,需要明确的是CORS是作为目前跨域http请求的根本解决方案。

本章仅对跨域方案做总结,如有遗漏或不对的地方请指正哈

一、同源策略简介

跨域是指由于浏览器同源策略限制的一类请求场景。 所谓同源,就是对于两个地址,协议://域名:端口,三者必须一致,若有一个不一致都会因为非同源而产生跨域。

例如:https://www.baidu.com访问http://www.baidu.com也是会产生跨域的。

同源策略总的来说是浏览器出于安全考虑,这主要表现在三个层面:

1. DOM层面。同源策略限制了来自不同源的JS脚本对当前DOM对象的读写操作。假如没有这样做,那么随便一个网站的脚本都可以对别的网址进行DOM操作,这是很危险的。

2. 数据层面。同源策略限制了不同源的站点读取当前站点的cookie、indexDB、LocalStorage等数据,如果没有同源策略,那么恶意获取用户的信息将变得很容易,这是极其不安全的。

3. 网络层面。同源策略限制了通过XMLHttpRequest等方式将站点的数据发送给不同源的站点。假如没有这么做,那作为资源提供商每天将接收处理外部链接请求,这将会加大资源提供商服务器的压力。

但是有三个特殊的标签不受此限制,他们分别是img标签、link标签、script标签。为什么是他们?我们都知道Web世界是开放的,同源策略的产生意味着HTML文件、CSS文件、JS文件、图片等资源将部署到同一个服务器下,这无疑违背了开放的初衷,假如将这些文件部署在不同的CDN上,那么资源将因为同源策略而无法相互访问。所以,img、link、script等标签就是为了在不同源的情况下可以引用外部文件资源。

然而,引用第三方文件又会带了一些问题,如浏览器首页内容被恶意程序劫持等,其中最典型的就是XXS攻击了,为了解决这一问题,浏览器又引入内容安全策略,称为CSP(核心思想是通过服务器告知浏览器哪些资源可以加载、哪些脚本可以执行)。通过这些手段可以大大减少XXS攻击。

介绍完同源策略,来个小结,跨域仅出现在浏览器,服务器与服务器通信是不存在跨域的!所以你或许有个疑问,那跨域请求到底发没发出去?

跨域请求能发出去,并且服务端也能收到请求并正常返回结果,只是结果被浏览器拦截了

二、跨域解决方案

1.jsonp

jsonp的本质是通过script标签能够请求外部资源这一特性,让src属性链接外部资源实现跨域请求。这个跨域请求需要后端配合,让后端将数据以函数调用的形式返回,并告知浏览器以js文件执行。具体操作如下:

后端操作,以express为例:

const data = [{
    
    a:1},{
    
    b:2},{
    
    c:3}]

(req,res,next) => {
    
    
    const json = JSON.stringify(data);
    const script = `callback(${
      
      json})`
    res.header("content-type","application/javascript")   //告知浏览器这是js文件,浏览器接收到会当js文件执行
    res.send(script)   //发送数据
}

假如前端不进行处理发送请求到后端的这个接口,浏览器控制台会报错,报错原因是callback这个函数不存在。所以,前端需要想要接收后端返回的数据,就需要定义一个函数接收这些数据。
前端操作:

function callback(data){
    
    
    //在这个函数里就可以对data进行操作了
}

所以,jsonp的缺点也是显而易见:

1. 影响服务器的响应格式
2. 只能使用get请求,因为script标签只会发送get请求

2.cors

具体参考这里:跨域之CORS

3.node中间件代理

我们都知道服务器与服务器是不存在跨域的,所以我们可以通过一个服务器充当媒介进行跨域处理:

大致模式如下:

请求
请求
响应
响应
浏览器
node服务器
目标服务器

4.nginx反向代理

实现思路和Node服务器代理一样,需要搭建一个nginx服务器用于转发请求。通过 nginx 配置一个代理服务器(域名与 domain1 相同,端口不同)做跳板机,反向代理访问 domain2 接口,并且可以顺便修改 cookie 中 domain 信息,方便当前域 cookie 写入,实现跨域登录。
大致模式如下:

请求
请求
响应
响应
服务器
nginx服务器
浏览器
目标服务器

nginx配置大致如下:

// proxy服务器
server {
    
    
    listen   80;
    server_name  www.domain1.com;
    location / {
    
    
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;
    }
}

5.window.name+iframe

window.name 属性的独特之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)

设置window.name='aa'并在当前页打开新链接
在新页面输出window.name
浏览器当前页面-window.name默认为空
浏览器新页面-新页面可以不同源
aa

所以我们a和b两个页面想要跨域通信可以利用iframe+window.name,需要利用c页面作为媒介实现,c页面和a页面同源才能实现父子页面的iframe读取操作。

http://localhost:6060/a.html页面:

<iframe src="http://localhost:5050/b.html" frameborder="0" onload="load()" id="iframe"></iframe>
 <script>
    let first = true    //加锁
    function load() {
    
    
      if(first){
    
    
      // 第1次onload(跨域页)成功后,切换到同域代理页面
        let iframe = document.getElementById('iframe');
        iframe.src = 'http://localhost:6060/c.html';  //设置完毕,iframe再次加载,此时加载的是c页面,a和c同源
        first = false;
      }else{
    
    
      // 第2次onload后,此时是iframe是c页面,与a页面同源,a可以读取同域下iframe的window.name中数据
        console.log(iframe.contentWindow.name);
      }
    }
 </script>

http://localhost:5050/b.html页面:

<script>
    window.name = 'hello,I am b'
</script>

该处使用的url网络请求的数据。

6.location.hash+iframe

实现原理: a.html 想和c.html 跨域通信,可以通过在a.html里嵌套iframe链接到 c.html,而c.html里又有一个iframe,链接到b.html,a和b是同域的 。 三个页面,不同域之间利用 iframe 的 location.hash 传值,相同域之间直接 js 访问来通信。

嵌套
localtion.hash为hello-创建iframe链接到b.html#hi
将当前localtion.hash传给parent的parent的localtion.hash
a.html#hello
c.html
b.html#hi

7.document.domain+iframe

实现原理:两个页面都通过 js 强制设置 document.domain 为基础主域,就实现了同域。需要注意的是该方式只能用于二级域名相同的情况

比如xueshu.baidu.com/a.html里嵌套一个iframe(链接到tieba.baidu.com/b.html),这种情况可以将两个页面设置同一个domain以实现跨域。

嵌套
设置document.domain=baidu.com
设置document.domain=baidu.com
xueshu.baidu.com/a.html
tieba.baidu.com/b.html
baidu.com

8.postMessage

1.postMessage 是 HTML5 XMLHttpRequest Level 2 中的 API,且是为数不多可以跨域操作的 window 属性之一。

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

关于用法MDN上有详细的文档说明:window.postMessage

http://localhost:3000/a.html

 <iframe src="http://localhost:6060/b.html" frameborder="0" id="frame" onload="load()"></iframe>//iframe加载完毕触发onload事件
    <script>
      function load() {
    
    
        let frame = document.getElementById('frame')
        frame.contentWindow.postMessage('hello', 'http://localhost:6060') //发送数据
        window.onmessage = (e) => {
    
     //接受返回数据
          console.log(e.data) //打印出hi
        }
      }
    </script>

http://localhost:6060/b.html

  window.onmessage = (e) => {
    
    
    console.log(e.data) //打印出hello
    e.source.postMessage('hi', e.origin) //响应对方数据
 }

9.WebSocket

1.Websocket 是 HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。

3.WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。

2.WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。

示例如下:

客户端代码:

 let socket = new WebSocket('ws://localhost:6060');  //ws是WebSocket定义的协议
    socket.onopen = () =>  socket.send('Hello');//向服务器发送数据
    socket.onmessage = (e) =>console.log(e.data);//接收服务器返回的数据

服务器代码:

let express = require('express');
let app = express();
let WebSocket = require('ws');//ws 是一个第三方的 websocket 通信模块
let myWs = new WebSocket.Server({
    
    port:6060});
myWs.on('connection',(ws)=> {
    
    
  ws.on('message', (data) => {
    
    
    console.log(data);  //打印客户端发送过来的数据
    ws.send('我不爱你')
  });
})

总结

1. 跨域仅存在浏览器,脱离浏览器是不存在跨域的!
2.CORS 支持所有类型的 HTTP 请求,是跨域 HTTP 请求的根本解决方案!

猜你喜欢

转载自blog.csdn.net/Mr_RedStar/article/details/115384285