乐趣区

关于前端:逐点突破系列-深入跨域理论和实践都不能少

前言

从“跨域”这个词开始,去理清跨域这个知识点,路径同源策略,跨过 document.domain,window.postMessage,JSONP,CORS 等,先放若干个问题,心愿看完文章的你能够答上来。

  1. 能说说跨域吗?
  2. 能说说同源策略吗?
  3. 为什么要同源策略,它限度了什么?
  4. 你晓得哪些跨域计划呢?
  5. 无关 cookie 的跨域怎么实现?
  6. 能具体说说 JSONP 吗?返回什么数据呢,前端怎么解决呢?晓得什么原理吗?实现过吗?JSONP 服务器端实现过吗?
  7. postMessage 理解吗?怎么应用?须要留神什么?(平安方面)
  8. 代理理解过吗?用过哪些代理计划呢不?怎么在我的项目中用呢?
  9. cors 能够具体说一个简略申请和非简略申请吗?具体过程说一下?我的项目中怎么应用?

文章可能有些中央写的不当和不全的中央,欢送评论区给我倡议,感激~~ 🤞🤞🤞

也心愿外面的知识点有哪里不分明的,你能够本人能够花工夫去整明确更好,加油呀😊

这次就不放导图啦,左边目录很分明~~

1、讲一下跨域是什么?

一个源加载的文档或者脚本和来自另一个源的文档和脚本等资源进行交互(也就是不满足同源策略的两个源之间进行一些交互),就是跨域。

所以你须要分明的是同源策略是什么?它为什么呈现?它又限度了什么?往下看吧:

2、同源策略

2.1、同源策略是什么?

所谓 ” 同源 ” 指的是 ” 三个雷同 ”。

  • 协定雷同
  • 域名雷同
  • 端口雷同

举个栗子:

http://www.jingda.com/dir/page.html这个网址,协定是http://,域名是www.jingda.com,端口是 80(默认端口能够省略)。来看看上面改编的哪些是同源哪些是不同源:

  • http://www.jingda.com/dir2/other.html:同源
  • http://jingda1.com/dir/other.html:不同源(域名不同)
  • http://v2.www.jingda.com/dir/other.html:不同源(域名不同)
  • http://www.jingda.com:81/dir/other.html:不同源(端口不同)
  • https://www.jingda.com/dir/page.html:不同源(协定不同)

2.2、为什么须要同源策略?

同源政策的目标,是为了保障用户信息的平安,避免歹意的网站窃取数据。它能帮忙阻隔歹意文档,缩小可能被攻打的媒介。假如小明同学在 A 银行的官网进行了登录,之后他又去浏览了其余网站,如果其余网站能够读取 A 银行官网的 cookie,那么小明在 A 银行的登录信息和其余贷款记录等都会被泄露,将是一件十分危险的状况。

而 cookie 的拜访限度只是同源策略限度的一种状况,上面咱们介绍一下其余的。

2.3、同源策略带来了什么拜访限度?

  • 跨源数据存储拜访:拜访存储在浏览器中的数据,如 localStorage 和 IndexedDB,是以源进行宰割;Cookies 应用不同的源定义形式。每个源都领有本人独自的存储空间,一个源中的 JavaScript 脚本不能对属于其它源的数据进行读写操作。
  • 跨源脚本 API 拜访:JavaScript 的 API 中,如 iframe.contentWindow、window.parent、window.open 和 window.opener 容许文档间间接互相援用。当两个文档的源不同时,这些援用形式将对 Window 和 Location 对象的拜访增加限度,
  • 跨源网络拜访:同源策略管制不同源之间的交互,例如在应用 XMLHttpRequest 或 [图片上传中 …(image-d026b6-1618640180825-0)]

     标签时则会受到同源策略的束缚。

3、解决跨域的几种办法?

将下面三种拜访限度简化成上面的三种表白:

(1)Cookie、LocalStorage 和 IndexDB 无奈读取。

(2)JavaScript 的 API 中的一些援用,无奈取得。(详见上)

(3)AJAX 申请不能发送。(也就是无奈应用 XMLHttpRequest)

(因为在网上无关跨域的解决方案,可能是比拟多,但这里我是依据下面三种限度顺次介绍一下可能行得通的解决方案)

3.1、cookie — document.domain

当咱们尝试解决因同源策略下,无法访问 cookie 这种状况时,咱们能够借助:

  • 1、浏览器容许通过设置 document.domain 共享 Cookie。来达成成果。然而,两个网页一级域名雷同,只是二级域名不同 才能够设置,那什么是一级域名,什么是二级域名呢?

举个栗子:A 网页:http://w1.jingda.com/a.html 在这个网页地址中,w1.jingda.com这部分统称为域名,

  • 一级域名是由一个非法的字符串 + 域名后缀组成,所以,jingda.com 这种模式的域名才是一级域名,jingda 是域名主体,.com、.net 也是域名后缀。
  • 二级域名理论就是一级域名上面的主机名,顾名思义,它是在一级域名后面加上一个字符串,比方 w1.jingda.com,

解释完怎么的状况能够设置 document.domain 共享 Cookie,让咱们看看一个如何操作:

假如有两个网页地址,咱们能够看到,他们的一级域名是雷同的,二级域名的不同的:

A 网页:http://w1.jingda.com/a.html

B 网页:http://w2.jingda.com/b.html

那么只有设置雷同的 document.domain,两个网页就能够共享 Cookie。

document.domain = 'example.com';
复制代码

A 网页通过脚本设置一个 Cookie。

document.cookie = "test1=hello";
复制代码

B 网页就能够读到这个 Cookie。

var allCookie = document.cookie;
复制代码

2、服务器也能够在设置 Cookie 的时候,指定 Cookie 的所属域名为一级域名,比方.example.com。

Set-Cookie: key=value; domain=.example.com; path=/
复制代码

这样的话,二级域名和三级域名不必做任何设置,都能够读取这个 Cookie。

这里的话,补充一下设置 cookie 的时候,一些其余的设置来限定其可拜访性:

  1. Domain 和 Path 标识定义了 Cookie 的作用域:即容许 Cookie 应该发送给哪些 URL。
  2. Secure:Secure 属性是说如果一个 cookie 被设置了 Secure=true,那么这个 cookie 只能用 https 协定发送给服务器,用 http 协定是不发送的。
  3. HttpOnly:应用 HttpOnly 属性可避免通过 JavaScript 拜访 cookie 值
  4. SameSite Cookie 容许服务器要求某个 cookie 在跨站申请时不会被发送,从而能够阻止跨站申请伪造攻打(CSRF)。

你应该留神到,这里咱们只是单单解决了在有一些限度条件下的 拜访 cookie的限度。然而下面还提到的 LocalStorage 和 IndexDB 临时还没有解决。(等下再说)

3.2、API 拜访 — window.postMessage

postMessage 是 html5 新增的一个解决跨域的一个办法,为了能让不同源中文档进行交换,能够应用 window.postMessage 平安地实现跨源通信。(平安是指在正确的应用状况下),这

3.2.1、window.postMessage 的应用场景?

这个我本人也是没有用过的。应用办法大家能够参考这篇 window.postMessage 用法一个比拟小的案例

因为是两个窗口页面之间的通信,因而咱们这边假如我两个页面,A,B,目标是在 B 窗口中点击 postMessage 按钮,可能在 A 页面收到发来的音讯。A 页面:

<script>
  function test() {let op = window.open('b.html', '_blank'); 
    function receiveMessage(event) {console.log('event', event);
    }
    op.addEventListener("message", receiveMessage, false); 
  }
</script>
<body>
  <div>
    <button onClick="test()">open</button>
  </div>
</body>
复制代码

B 页面:

<script>
  function post() {window.postMessage("hi there!", location.origin);
  }
  function receiveMessage(event) {console.log('event', event)
  }
  window.addEventListener("message", receiveMessage, false);
</script>
<body>
  <div>
    <button onClick="post()">postMessage</button>
  </div>
</body>
复制代码

我就间接说一下大略的思路了:首先看看 B 页面:

  1. 在 B 页面有一个按钮,点击这个按钮会触发一个办法,post()
  2. 在 post()办法中,window.postMessage(“hi there!”, location.origin),发送到所有同源的窗口,留神,以后窗口也会收到
  3. 之后通过 window.addEventListener(“message”, receiveMessage, false)去监听,如果有数据,就执行 receiveMessage(), 把数据打印进去

再来看 A 页面:

  1. 在 A 页面也有一个按钮,当点击这个按钮时触发 test()
  2. 关上新窗口,并建设窗口的援用变量 op = window.open(‘B.html’, ‘_blank’);
  3. op.addEventListener(“message”, receiveMessage, false); 监听新开窗口发来的音讯, 通过 receiveMessage() 把数据打印进去

3.2.2、如何正确的应用,以保障安全性?

  1. 始终应用 origin 和 source 属性验证发件人的身份, 没有验证 origin 和 source 属性会导致跨站点脚本攻打。
  2. 当应用 postMessage 将数据发送到其余窗口时,指定准确的指标 origin,而不是 *

3.3、JSONP

JSONP(JSON with Padding)是 JSON 的一种“应用模式”,可用于解决支流浏览器的跨域数据拜访的问题。

3.3.1、JSONP 的介绍

JSONP 是通过在 <script></srcipt> 标签里,通过 src,img,href 属性的跨域形式向一个不同源的网站地址发送 http 申请,并且使得 json 数据能够在 javascript 代码中可能应用。

它躲避了 javascript 代码中的跨源网络拜访,也就是无奈应用 XMLHttpRequest,fetch 被同源机制管到了(如果不同源的话)。

提前准备一个接口:https://photo.sina.cn/aj/index?page=1&cate=recommend 间接网页中关上,咱们是能够看到有很多数据的,如下图:

让咱们尝试在本地申请一下这个地址,看看能不能拿到数据:因为单方地址并不是同源的,因而这样申请会报跨域的错:

<body>
   <script>
        fetch('https://photo.sina.cn/aj/index?page=1&cate=recommend')
            .then(data=>{console.log(data);

            })
    </script>
</body>
复制代码

通过 live-server 关上浏览器,在控制台能够看到报错了,因为这个是一个跨域的申请:

接下来咱们来看看 JSONP 如何解决这个问题:

3.3.2、jsonp 如何应用?原理是什么?返回数据格式?前端怎么解决?

还是申请下面的这个网站地址,咱们把代码改成上面这样:

<body>
  <script>
    function callback(data){console.log(data);
    }
</script>
<script src="https://photo.sina.cn/aj/index?page=1&cate=recommend&callback=callback"></script>  
</body>
复制代码

再来看看页面控制台输入:

data 胜利取到了。然而咱们的数据达到之后是 json 数据,不能间接应用,script 标签是一个加载资源的标签,它并不能间接运行这个代码。

事实上咱们是在拜访的时候,在申请的地址前面加上一个,&callback=callback,告诉服务器,本地想进行一个跨资源拜访(以 JSOP 的模式进行跨域)。等号前面的 callback 是一个你本人定义的函数,名字可自取,这个函数就是,告诉我须要申请的地址,这边页面上我有一个函数,它会期待调用,用来执行你发过来的数据(也就是能够去执行把数据申请下来的操作)。

因而在数据达到之后,还包了一层函数 callback({data}),当数据通过 script 标签申请下来之后,再通过 callback 实现了一个调用本地资源的能力。

最初再理一下这部分的内容:

  • JSONP 的原理

script 标签申请数据,在申请的地址前面加上一个,&callback=callback,申请的服务器就在 json 数据外面包一层 callback 函数,当这个带有数据的 callback 函数能够在 script 失去之后能够运行的函数:

  • 返回的数据格式

JSON

  • 以及前端如何解决的

JSON with padding — callback({data})

3.3.3、本人封装一个 jsonp?

  1. 筹备工作
 <script>
    let jsonp = () => {}
    jsonp('https://photo.sina.cn/aj/index', {
      page: 1,
      cate: 'recommend'
    })
    .then(response => {console.log(response,'调用胜利啦');
    })
  </script>
复制代码
  1. 具体实现流程
  • 确定传递参数:url、携带的参数、callback;
  • 解决 url 上的参数(?前面的);
  • 筹备好 url(携带 callback 函数);
  • 构建 script 标签;
  • 把这个标签挂到 window 上
<script>
    // 1、确定好参数
    let jsonp = (url,data = {},callback = 'callback') => {
    // 2、解决好 url 外面的参数
      let dataStr = url.indexOf('?') === -1 ? '?':'&'
    // 3、把参数和 & 拼接下来
      for (let key in data) {dataStr += `${key}=${data[key]}&`;
      }
    // 4、把 callback 拼接上
      dataStr += 'callback=' + callback;
    // 5、创立一个 script 标签
      let oScript = document.createElement('script');
      oScript.src = url + dataStr;
      document.body.appendChild(oScript);
     // 6、把 script 标签挂载到 window 下来

    // 计划一、// window[callback] = (data) => {//   console.log(data);
      // }
    // 计划二、return new Promise((reslove,reject) => {window[callback] = (data) => {
          try {reslove(data)
          } catch(e) {reject(e)
          } finally {oScript.parentNode.removeChild(oScript);
            // 删除这个 script 节点
          }
        }
      })
    }
    // 调用 jsonp 办法
    jsonp('https://photo.sina.cn/aj/index?a=1', {
      page: 1,
      cate: 'recommend'
    })
    .then(response => {console.log(response,'调用胜利啦');
    })
  </script>
复制代码

3.3.4、实现一个 jsonp 服务器端?(node 版本,express 版本)

node 版本

创立一个构造如下的服务器端文件夹,咱们将在 index.js 中实现咱们的 JSONP:

var http = require('http');
http.createServer(function(req, res){
// req url  callback=?
console.log(req.url);
let data = {a: 1};
res.writeHead(200, {'Content-type' : 'text/json'})
  const reg = /callback=([\w]+)/
  if (reg.test(req.url)) {
    let padding = RegExp.$1
    res.end(`${padding}(${JSON.stringify(data)})`)
  } else {res.end(JSON.stringify(data));
  }
//  res.end('<p>Hello World</p>');
 res.end(JSON.stringify(data));
}).listen(3000);
复制代码

express 版本

var express = require('express');
var cors = require('cors');// 后端 cors 中间件
const app = express();
app.use(cors());
app.get('/product',(req,res)=>{
    res.json({
        a:1,
        b:2
    })
})
app.listen(8000,()=>{console.log('server is ok')
})
复制代码

3.4 cors

3.4.1、介绍一下 cors?

CORS 是一个 W3C 规范,全称是 ” 跨域资源共享 ”(Cross-origin resource sharing)。它容许浏览器向跨源服务器,收回 XMLHttpRequest 申请,从而克服了 AJAX 只能同源应用的限度。

3.4.2、简略申请和非简略申请?

浏览器将 CORS 申请分成两类:简略申请(simple request)和非简略申请(not-so-simple request)。

除了简略申请其余的都是非简略申请,因而只有记住哪些是简略申请就能够啦:

简略申请:(须要同时满足上面两种条件)

  1. 申请办法是以下三种办法之一:
  • HEAD
  • GET
  • POST
  1. HTTP 的头信息不超出以下几种字段:
  • Accept:设置承受的内容类型(申请头)
  • Accept-Language:设置承受的语言(申请头)
  • Content-Language:为关闭内容设置自然语言或者指标用户语言(响应头)
  • Content-Type:(设置申请体的 MIME 类型(实用 POST 和 PUT 申请))只限于三个值

application/x-www-form-urlencoded

中默认的 encType,form 表单数据被编码为 key/value 格局发送到服务器(表单默认的提交数据的格局)

multipart/form-data:将表单的数据处理为一条音讯,以标签为单元,用分隔符离开。既能够上传键值对,也能够上传文件。

text/plain:text/plain:纯文本格式

3.4.3、我的项目中怎么应用?

  • 服务器端:
const express = require('express');
const app= express();

app.get('/', (req, res)=>{console.log('server is OK');
  res.end('jingjing')
});

// app.use((req, res, next) => {//     res.header("Access-Control-Allow-Origin",'http://localhost:5500');
//     res.header("Access-Control-Allow-Credentials", true);
//     res.header("Access-Control-Allow-Headers", 'Content-Type,Content-Length,Authorization, Accept,X-Requested-With');
//     res.header("Access-Control-Allow-Methods", 'PUT,POST,GET,DELETE,OPTIONS,HEAD');
//     req.method === 'OPTIONS' ? res.send('CURRENT SERVICES SUPPORT CROSS DOMAIN REQUESTS!') : next();
// });

app.listen(8081, ()=>{console.log('Server is running at http://localhost:8081')
})

复制代码
  • 前端申请:
  <body>
    <button onclick="sendAjax()">sendAjax</button>
  <script type="text/javascript">
    var sendAjax = () => {var xhr = new XMLHttpRequest();
        xhr.open('GET', 'http://127.0.0.1:5500', true);
        xhr.send();
        xhr.onreadystatechange = function (e) {if (xhr.readyState == 4 && xhr.status == 200) {console.log(xhr.responseText);
            console.log('胜利了')
          }
        };
    }
  </script>
  </body>
复制代码

跨域报错:

把两头正文的局部放开再执行:没有下面的报错了,也返回了

console.log(xhr.responseText);
console.log('胜利了')

剖析一下:

  • “Access-Control-Allow-Origin”,http://localhost:5500

如果服务端仅容许来自 http://localhost:5500的拜访,如果服务端返回的 Access-Control-Allow-Origin: * 表明,该资源能够被任意外域拜访。

  • “Access-Control-Allow-Credentials”, true):

Access-Control-Allow-Credentials 头指定了当浏览器的 credentials 设置为 true 时是否容许浏览器读取 response 的内容。

  • “Access-Control-Allow-Headers”, ‘Content-Type,Content-Length,Authorization, Accept,X-Requested-With’):

首部字段 Access-Control-Allow-Headers 表明服务器容许申请中携带字段 X-PINGOTHER 与 Content-Type。

  • “Access-Control-Allow-Methods”, ‘PUT,POST,GET,DELETE,OPTIONS,HEAD’:

首部字段 Access-Control-Allow-Methods 表明服务器容许客户端应用 POST, GET 和 OPTIONS 等办法发动申请

  • req.method === ‘OPTIONS’ ? res.send(‘CURRENT SERVICES SUPPORT CROSS DOMAIN REQUESTS!’) : next():

“需预检的申请”要求必须首先应用 OPTIONS 办法发动一个预检申请到服务器,以获知服务器是否容许该理论申请(除简略申请以外的,比方 POST 办法就须要用到预检)

3.5、代理(nginx)

3.5.1 原理

A 网站向 B 网站申请 1.js 文件时,向 B 网站发送一个获取的申请,nginx 依据配置文件接管这个申请,代替 A 网站向 B 网站来申请这个资源,nginx 拿到这个资源后再返回给 a 网站,以此来解决了跨域问题。

3.5.2 应用

应用 Nginx,无关下载和配置 Nginx,我就不再这里说了,感兴趣的小伙伴能够参考一下这篇文章,外面配置相干的讲的比较清楚。正确的 Nginx 跨域配置

(本人平时也没怎么用就是,唉,大多知识点也是一边写一边理)

然而的然而,学习还是要学滴。回到最开始咱们提到的一些问题,来看看你能答复多少 👇👇👇

总结

最初再来一次拷问:

  1. 能说说跨域吗?
  2. 能说说同源策略吗?
  3. 为什么要同源策略,它限度了什么?
  4. 你晓得哪些跨域计划呢?
  5. 无关 cookie 的跨域怎么实现?
  6. 能具体说说 JSONP 吗?返回什么数据呢,前端怎么解决呢?晓得什么原理吗?实现过吗?JSONP 服务器端实现过吗?
  7. postMessage 理解吗?怎么应用?须要留神什么?(平安方面)
  8. 代理理解过吗?用过哪些代理计划呢不?怎么在我的项目中用呢?
  9. cors 能够具体说一个简略申请和非简略申请吗?具体过程说一下?我的项目中怎么应用?

🙈

最初

因为有拷问题了,拷问题大家能答复上也是把握啦,这次的面试题小编就以 PDF 模式展现给大家,不然篇幅被限度了,面试题出不来啦,须要前端面试题完整版 PDF 的,点击这里间接支付:

逐点冲破,冲破自我!咱们下期见!

退出移动版