跨域的解决办法

一、什么是跨域?

在了解跨域之前首先要了解一下浏览器的同源策略。同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS(跨站脚本攻击)、CSRF(跨站请求伪造)等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

在这里插入图片描述
同源策略限制内容有:

  • Cookie、LocalStorage、IndexedDB 等存储性内容
  • DOM 节点
  • AJAX 请求发送后,结果被浏览器拦截了

常见跨域场景

当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作跨域。常见跨域场景如下图所示:

在这里插入图片描述
这里就有个疑问:请求跨域了,那么请求到底发出去没有?

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。

二、跨域解决方案

1.JSONP

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

1.2 JSONP优缺点
JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击,而且需要服务端配合返回回调函数。

1.3JSONP跨域实现方式

  • 声明一个回调函数,其函数名(如func)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)
  • 创建一个<script>标签,把那个跨域的API数据接口地址,赋值给script的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=func)
  • 服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串
  • 最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(func),对返回的数据进行操作

下面是用node写的一个服务器的代码:

var express = require('express');

var app = express();

app.listen(3000, function () {
  console.log('app is running');
});

app.get('/text', (request, response) => {
  let {callback=Function.prototype}=request.query;
  let data='lwf'

  console.log(111,callback)
  
  //将函数名和将要返回的数据拼接成字符串然后反馈给浏览器
  response.send(`${callback}(${JSON.stringify(data)})`)
});

然后是请求服务器的前端代码:

 <script>
  function func(data) {
      //输出JSONP跨域请求到的数据
      console.log(123, data);
    }
  </script>
<script src="http://localhost:3000/text?callback=func"></script>

然后在控制台可以接受到服务器返回的数据
在这里插入图片描述
2.CORS

CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。

浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。
服务端设置Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。在跨域请求失败的时候浏览器反馈的错误就是请求头错误,如下图所示:
在这里插入图片描述
在node服务端设置Access-Control-Allow-Origin的代码如下所示:

var express = require('express');

var app = express();

app.listen(3000, function () {
  console.log('app is running');
});

app.get('/student', (request, response) => {
  console.log(123);
  response.append('Access-Control-Allow-Origin', "*");
  response.send(JSON.stringify('lwf'))
});

然后在前端页面中就可以正常请求到数据了:
在这里插入图片描述
3.postMessage

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

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的iframe消息传递

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
接下来是有个需求: http://localhost:3000/a.html页面向http://localhost:4000/b.html传递“b页面,你收到消息了没”,然后后者传回"嗯嗯,a页面,我收到你的消息了"。

// a.html
  <iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe> //等它加载完触发一个事件
  //内嵌在http://localhost:3000/a.html
    <script>
      function load() {
        let frame = document.getElementById('frame')
        frame.contentWindow.postMessage('b页面,你收到消息了没', 'http://localhost:4000') //发送数据
        window.onmessage = function(e) { //监听接受返回数据
          console.log(e.data) //
        }
      }
    </script>
// b.html
  window.onmessage = function(e) {
    console.log(e.data) //我爱你
    e.source.postMessage('嗯嗯,a页面,我收到你的消息了', e.origin)
 }

最后结果如图所示:
在这里插入图片描述

4.websocket

Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

下面是示例:

//server.js
let WebSocket = require('ws');//记得安装ws

let wss = new WebSocket.Server({port:3000});
wss.on('connection',function(ws) {
  ws.on('message', function (data) {
    console.log(data);
    ws.send('cll')
  });
})
<script>
  let socket = new WebSocket('ws://localhost:3000');
  socket.onopen = function () {
    socket.send('lwf');//向服务器发送数据
  }
  socket.onmessage = function (e) {
    console.log(e.data);//接收服务器返回的数据
  }
</script>

这个业务场景是前端发送lwf,后台服务器接收到请求后返回cll:
在这里插入图片描述
在这里插入图片描述
5. Node中间件代理(两次跨域)

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

1.接受客户端请求
2.将请求转发给服务器
3.拿到服务器响应的数据
4.将响应转发给客户端
下面是我的服务器上真实在运行的node实现中间件的代理服务器的代码:

var express = require('express');

var app = express();

var cors = require('cors');

const {default: Axios} = require('axios');

function getHoneyedWords(language) {
  var url = '';

  language = language.substr(1);

  if (language === 'pyq') {
    url = 'https://pyq.shadiao.app/api.php';
  } else if (language === 'nmsl') {
    url = 'https://nmsl.shadiao.app/api.php?level=min&lang=zh_cn';
  } else if (language === 'chp') {
    url = 'https://chp.shadiao.app/api.php';
  } else {
    url = 'https://du.shadiao.app/api.php ';
  }

  return Axios.get(url);
}

app.use(cors());

app.listen(3000, function () {
  console.log('app is running');
});

app.get('/pyq', (request, response) => {
  console.log(111, request.url);
  getHoneyedWords(request.url.toString()).then((res) => {
    console.log(222, res.data);
    response.status(200).json(res.data);
  });
});

app.get('/nmsl', (request, response) => {
  console.log(111, request.url);
  getHoneyedWords(request.url.toString()).then((res) => {
    console.log(222, res.data);
    response.status(200).json(res.data);
  });
});

app.get('/chp', (request, response) => {
  console.log(111, request.url);
  getHoneyedWords(request.url.toString()).then((res) => {
    console.log(222, res.data);
    response.status(200).json(res.data);
  });
});

app.get('/du', (request, response) => {
  console.log(111, request.url);
  getHoneyedWords(request.url.toString()).then((res) => {
    response.status(200).json(res.data);
  });
})

然后请求我的服务器的地址,就可以接收到转发过来的数据了:
在这里插入图片描述
6.nginx反向代理

实现原理类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。
使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

剩下还有三种在工作中很少遇到,分别为·window.name + iframelocation.hash + iframe、和document.domain + iframe。前两者中心思想是通过设置一个中间代理b.html来实现a.html和c.html的跨域请求,document.domain + iframe通过js强制设置document.domain为基础主域,就实现了同域,且该方式只能用在二级域名相同的情况下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值