25.1 同源策略
25.1.1 同源
跨域本质其实就是指两个地址不同源,不同源的反面不就是同源,同源指的是:如果两个URL的协议、域名和端口号都相同,则就是两个同源的URL。
- // 非同源:协议不同
- http://www.baidu.com
- https://www.baidu.com
- // 同源:协议、域名、端口号都相同
- http://www.baidu.com
- http://www.baidu.com?query=1
25.1.2 同源策略
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的加载的脚本如何能与另一个源的资源进行交互。其主要是为了保护用户信息的安全,防止恶意的网站窃取数据,是浏览器在Web页面层面做的安全保护。
25.1.3 同源策略的表现
既然同源策略是浏览器在Web页面层面做的保护,那么该层面哪些位置需要进行保护呢?总结下来主要包含三个层面:DOM层面、数据层面、网络层面。
DOM层面
同源策略限制了来自不同源的JavaScript脚本对当前DOM对象读和写的操作。
数据层面
同源策略限制了不同源的站点读取当前站点的Cookie、IndexedDB、localStorage等数据。
网络层面
同源策略限制了通过XMHttpRequest等方式将站点的数据发送给不同源的站点。
25.2 跨域分类
同源策略保证了浏览器的安全,但是如果将这三个层面限制的死死的,则会让程序员的开发工作举步维艰,所以浏览器需要在最严格的同源策略限制下做一些让步,这些让步更多了是在安全性与便捷性的权衡。其实跨域的方式就可以认为是浏览器出让了一些安全性或在遵守浏览器同源策略前提下所采取的一种折中手段。
25.2.1 DOM层面和数据层面分类
根据同源策略,如果两个页面不同源,无法互相操作DOM、访问数据,但是两个不同源页面之间进行通信是比较常见的情形,典型的例子就是iframe窗口与父窗口之间的通信。随着历史的车轮,实现DOM层面间通信的方式有多种,如下所示:
片段标识符
片段标识符其核心原理就是通过监听url中hash的改变来实现数据的传递,想法真的很巧妙。
- // 父页面parentHtml.html
- <!DOCTYPE html>
- <html lang="zh">
- <head>
- <title></title>
- </head>
- <body>
- 我是父页面
- <button id='btn'>父传给子</button>
- <iframe src="./childHtml.html" id="childHtmlId"></iframe>
- </body>
- <script>
- window.onhashchange = function() {
- console.log(decodeURIComponent(window.location.hash));
- };
- document.getElementById('btn').addEventListener('click', () => {
- const iframeDom = document.getElementById('childHtmlId');
- iframeDom.src += '#父传给子';
- });
- </script>
- </html>
- // 子页面childHtml.html
- <!DOCTYPE html>
- <html lang="zh">
- <head>
- <title></title>
- </head>
- <body>
- 我是子页面
- <button id='btn'>子传给父</button>
- </body>
- <script>
- window.onhashchange = function() {
- console.log(decodeURIComponent(window.location.hash));
- };
- document.getElementById('btn').addEventListener('click', () => {
- parent.location.href += '#子传给父';
- });
- </script>
- </html>
window.name
浏览器窗口有window.name属性,这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。如果需要实现父页面和跨域的子页面之间的通信,需要一个和父页面同源的子页面作为中介,将跨域的子页面中的信息传递过来。(好麻烦呀,强烈不推荐使用,此处就不写对应的代码啦)
document.domain
document.domain是存放文档的服务器的主机名,可通过手动设置将其设置成当前域名或者上级的域名,当具有相同document.domain的页面就相当于处于同域名的服务器上,如果其域名和端口号相同就可以实现跨域访问数据了。
postMessage(强烈推荐)
window.postMessage是HTML5新增的跨文档通信API,该API,允许跨窗口通信,不论这两个窗口是否同源。
- // 父页面
- <!DOCTYPE html>
- <html lang="zh">
- <head>
- <title></title>
- </head>
- <body>
- 我是父页面
- <button id='btn'>父传给子</button>
- <iframe src="http://127.0.0.1:5500/024/childHtml.html" id="childHtmlId"></iframe>
- </body>
- <script>
- window.addEventListener('message', function(event) {
- console.log('父页面接收到信息', event.data);
- });
- document.getElementById('btn').addEventListener('click', () => {
- const iframeDom = document.getElementById('childHtmlId');
- iframeDom.contentWindow.postMessage('我是执鸢者1', 'http://127.0.0.1:5500/024/childHtml1.html');
- });
- </script>
- </html>
- // 子页面
- <!DOCTYPE html>
- <html lang="zh">
- <head>
- <title></title>
- </head>
- <body>
- 我是子页面
- <button id='btn'>子传给父</button>
- </body>
- <script>
- window.addEventListener('message', function(event) {
- console.log('子页面接收到信息', event.data);
- });
- document.getElementById('btn').addEventListener('click', () => {
- parent.postMessage('我是执鸢者2', 'http://127.0.0.1:5500/024/parentHtml1.html');
- });
- </script>
- </html>
25.2.2 网络层面
根据同源策略,浏览器默认是不允许XMLHttpRequest对象访问非同一站点的资源的,这会大大制约生产力,所以需要破解这种限制,实现跨域访问资源。目前广泛采用的主要有三种方式(注:该出不给出具体代码,后续会有专门的百题斩进行详细阐述):
通过代理实现
同源策略是浏览器为了安全制定的策略,所以服务端不会存在这样的限制,这样我们就可以将请求打到同源的服务器上,然后经由同源服务器代理至最终需要的服务器,从而实现跨域请求的目的。例如可以通过Nginx、Node中间件等。
JSONP的方式(具体实现见后续百题斩)
JSONP是一种借助script元素实现跨域的技术,它并没有使用XMLHttpRequest对象,其能够实现跨域主要得益于script有两个特点:
(1)src属性能够访问任何URL资源,并不会受到同源策略的限制;
(2)如果访问的资源包含JavaScript代码,其会在下载后自动执行。
CORS方式(具体实现见后续百题斩)
跨域资源共享(CORS),该机制可以进行跨域访问控制,从而使跨域数据传输得以安全进行。(实现一个跨域请求的方式,其中html访问网址为http://127.0.0.1:8009; 服务器监听端口为:8010)
(1)html页面内容
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>test CORS</title>
- </head>
- <body>
- CORS
- <script src="https://code.bdstatic.com/npm/axios@0.20.0/dist/axios.min.js"></script>
- <script>
- axios('http://127.0.0.1:8010', {
- method: 'get'
- }).then(console.log)
- </script>
- </body>
- </html>
(2)服务器端代码
- const express = require('express');
- const app = express();
- app.get('/', (req, res) => {
- console.log('get请求收到了!!!');
- res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8009');
- res.send('get请求已经被处理');
- })
- app.listen(8010, () => {
- console.log('8010 is listening')
- });
本文转载自微信公众号「执鸢者」,可以通过以下二维码关注。转载本文请联系执鸢者公众号。