Erlo

「深入浅出」前端开发中常用的几种跨域解决方案

2020-09-17 14:00:20 发布   395 浏览  
页面报错/反馈
收藏 点赞

感谢来自微医大前端技术 公众号提供的Theme模板

编者荐语

本文将为大家介绍,前端开发中,最常用的几种跨域解决方案;

看完本文可以系统地掌握,不同种跨域解决方案间的巧妙,以及它们的用法、原理、局限性和适用的场景

包括以下几个方面:

  • 跨域的现象,和几种常见的跨域表现
  • 跨域的解决方案(原理分析)
    • 修改本地HOST
    • JSONP
    • CORS
    • Proxy
    • Nginx反向代理
    • Post Message(利用 iframe标签,实现不同域的关联)

同源是什么?

如果两个URL的协议protocol、主机名host和端口号port都相同的话,则这两个URL是同源。

同源策略

同源策略是一个重要的安全策略。它能够阻断恶意文档,减少被攻击的媒介。

真实项目中,很少有同源策略,大部分都是非同源策略

跨域是什么?

当协议、域名与端口号中任意一个不相同时,都算作不同域,不同域之间相互请求资源的表现(非同源策略请求),称作”跨域“

跨域现象

那么我们就下面的网址分析一下,哪一块是协议,哪一块是域名及端口号

http://kbs.sports.qq.com/index.html

协议:http(还有以一种https协议)
域名:kbs.sports.qq.com
端口号:80

https://127.0.0.1:3000

协议:https
域名:127.0.0.1
端口号:3000

假如我们的真实项目开发中的Web服务器地址为 ”http://kbs.sports.qq.com/index.html“,而需要请求的数据接口地址为 "http://api.sports.qq.com/list"。

当Web服务器的地址向数据接口的地址发送请求时,便会造成了跨域现象

造成跨域的几种常见表现

  • 服务器分开部署(Web服务器 + 数据请求服务器)
  • 本地开发(本地预览项目 调取 测试服务器的数据)
  • 调取第三方平台的接口

Web服务器:主要用来静态资源文件的处理

解决方案

  • 修改本地HOST(不作介绍)
  • JSONP
  • CORS
  • Proxy
  • Nginx反向代理
  • Post Message(利用 iframe标签,实现不同域的关联)

在后面会详细分析这四种解决方案的原理和用法配置,以及它们的优点和局限性

注意: 基于ajaxfetch发送请求时,如果是跨域的,则浏览器默认的安全策略会禁止该跨域请求

补充说明:以下所有的测试用例,均由Web:http://127.0.0.1:5500/index.htmlAPI:http://127.0.0.1:1001/list发起请求

API接口的服务器端是自己通过express建立的,下文在服务器端以app.use中间件的形式接受来自客户端的请求并做处理。

即 在“http://127.0.0.1:1001/list”from origin“http://127.0.0.1:55”上对XMLHttpRequest的访问已被CORS策略阻止:被请求的资源上没有“Access- control - allow-origin”头

在后端开启了一个端口号为1001的服务器之后,我们来实践一下

let xhr = new XMLHttpRequest;
xhr.open('get''http://127.0.0.1:1001/list');
xhr.onreadystatechange = () => {
  if (xhr.status === 200 && xhr.readyState === 4) {
    console.log(xhr.responseText);
  }
};
xhr.send(); 
跨域的常见报错提示

这就是由于浏览器默认的安全策略禁止导致的。

下面介绍一下几种常见的解决方案。

JSONP

原理:JSONP利用script标签不存在域的限制,且定义一个全局执行上下文中的函数func

(用来接收服务器端返回的数据信息)来接收数据,从而实现跨域请求。

弊端

  • 只允许 GET请求
  • 不安全:只要浏览器支持,且存在浏览器的全局变量里,则谁都可以调用

图解JSONP的原理

手动封装JSONP

callback必须是一个全局上下文中的函数

(防止不是全局的函数,我们需要把这个函数放在全局上,并且从服务器端接收回信息时,要浏览器执行该函数)

注意:

  • uniqueName变量存储全局的回调函数(确保每次的 callback都具有唯一性)
  • 检验 url中是否含有"?",有的话直接拼接 callback,没有的话补”?“
// 客户端
function jsonp(url, callback{
  // 把传递的回调函数挂载到全局上
 let uniqueName = `jsonp${new Date().getTime()}`;
  // 套了一层 anonymous function
  // 目的让 返回的callback执行且删除创建的标签
  window[uniqueName] = data => {
  // 从服务器获取结果并让浏览器执行callback
    document.body.removeChild(script);
    delete window[uniqueName];
    callback && callback(data);
  }
  
  // 处理URL
  url += `${url.includes('?')} ? '&' : '?}callback=${uniqueName}'`;
  
  // 发送请求
  let script = document.createElement('script');
  script.src = url;
  document.body.appendChild(script);
}

// 执行第二个参数 callback,获取数据
jsonp('http://127.0.0.1:1001/list?userName="lsh"', (result) => {
 console.log(result);
})
// 服务器端
// Api请求数据
app.get('/list', (req, res) => {
  
  // req.query 问号后面传递的参数信息
  // 此时的callback 为传递过来的函数名字 (uniqueName)
 let { callback } = req.query;
  
  // 准备返回的数据(字符串)
  let res = { code0data: [10,20] };
  let str = `${callback}($(JSON.stringify(res)))`;
  
  // 返回给客户端数据
  res.send(str);
})

测试用例展示:

  • 客户端请求的 url
  • 服务端返回的数据
    • 返回的 callback
    • 返回的数据信息 result
// 服务器请求的 url
Request URL:
 http://127.0.0.1:1001/list?userName="lsh"&callback=jsonp159876002

// 服务器返回的函数callback
 jsonp159876002({"code":0"data": [10,20]});

// 客户端接收的数据信息
code0dataArray(2) }

当浏览器发现返回的是jsonp159876002({"code":0, "data": [10,20]});这个函数,会自动帮我们执行的。

JSONP弊端

在上文中说到只要服务器端那里设置了允许通过jsonp的形式跨域请求,我们就可以取回数据。

下面是在我们封装完jsonp方法之后,向一个允许任何源向该服务器发送请求的网址xxx

jsonp('https://matchweb.sports.qq.com/matchUnion/cateColumns?from=pc', result => {
  console.log(result);
});

CORS

上文提到,不允许跨域的根本原因是因为Access-Control-Allow-Origin已被禁止

那么只要让服务器端设置允许源就可以了

原理:解决掉浏览器的默认安全策略,在服务器端设置允许哪些源请求就可以了

先来看一下下面的设置有哪些问题

// 服务器端
app.use((req, res, next) => {
 // * 允许所有源(不安全/不能携带资源凭证)
 res.header("Access-Control-Allow-Origin""*");
 res.header("Access-Control-Allow-Credentials"true);

 /* res.header("Access-Control-Allow-Headers", "Content-Type,....");
 res.header("Access-Control-Allow-Methods", "GET,..."); */


 // 试探请求:在CORS跨域请求中,首先浏览器会自己发送一个试探请求,验证是否可以和服务器跨域通信,服务器返回200,则浏览器继续发送真实的请求
 req.method === 'OPTIONS' ? res.send('CURRENT SERVICES SUPPORT CROSS DOMAIN REQUESTS!') : next();
});

// 客户端
let xhr = new XMLHttpRequest;
xhr.open('get''http://127.0.0.1:1001/list');
xhr.setRequestHeader('Cookie''name=jason');
xhr.withCredentials = true;
xhr.onreadystatechange = () => {
  if (xhr.status === 200 && xhr.readyState === 4) {
    console.log(xhr.responseText);
  }
};
xhr.send();

当我们一旦在服务器端设置了允许任何源可以请求之后,其实请求是不安全的,并且要求客户端不能携带资源凭证(比如上文中的Cookie字段),浏览器端会报错。

告诉我们Cookie字段是不安全的也不能被设置的,如果允许源为'*'的话也是不允许的。

假如在我们的真实项目开发中

正确写法✅

  • 设置单一源(安全/也可以携带资源凭证/只能是单一一个源)
  • 也可以动态设置多个源:每一次请求都会走这个中间件,我们首先设置一个白名单,如果当前客户端请求的源在白名单中,我们把 Allow-Origin动态设置为当前这个源
app.use((req, res, next) => {
  
  // 也可自定义白名单,检验请求的源是否在白名单里,动态设置
  /* let safeList = [
  "http://127.0.0.1:5500",
  xxx,
  xxxxx,
 ]; */

 res.header("Access-Control-Allow-Origin""http://127.0.0.1:5500");
 res.header("Access-Control-Allow-Credentials"true); // 设置是否可携带资源凭证

 /* res.header("Access-Control-Allow-Headers", "Content-Type,....");
 res.header("Access-Control-Allow-Methods", "GET,..."); */


 // 试探请求:在CORS跨域请求中,首先浏览器会自己发送一个试探请求,验证是否可以和服务器跨域通信,服务器返回200,则浏览器继续发送真实的请求
 req.method === 'OPTIONS' ? res.send('CURRENT SERVICES SUPPORT CROSS DOMAIN REQUESTS!') : next();
});

CORS的好处

  • 原理简单,容易配置,允许携带资源凭证
  • 仍可以用 ajax作为资源请求的方式
  • 可以动态设置多个源,通过判断,将 Allow-Origin设置为当前源

登录查看全部

参与评论

评论留言

还没有评论留言,赶紧来抢楼吧~~

手机查看

返回顶部

给这篇文章打个标签吧~

棒极了 糟糕透顶 好文章 PHP JAVA JS 小程序 Python SEO MySql 确认