同源跨域的概念与实现跨域的几种方案

news2025/5/19 14:12:45

本文将结合周老师的讲义对同源与跨域这一前端经典问题进行系统的总结、整理。一起来坐牢,快!

1. 同源限制

1.1 历史背景 - 含义的转变

1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。

最初,它的含义是指,A 网页设置的 Cookie,B 网页不能打开,除非这两个网页“同源”。所谓“同源”指的是“三个相同”。

  • 协议 - http://、https://、ftp://
  • 域名 - hostname
  • 端口号 - portnumber

举例来说,http://www.example.com/dir/page.html这个网址,协议是http://,域名是www.example.com,端口是80(默认端口可以省略),它的同源情况如下。

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

注意,标准规定端口不同的网址不是同源(比如8000端口和8001端口不是同源),但是浏览器没有遵守这条规定。实际上,同一个网域的不同端口,是可以互相读取 Cookie 的。

1.2 目的

同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。

设想这样一种情况:A 网站是一家银行,用户登录以后,A 网站在用户的机器上设置了一个 Cookie,包含了一些隐私信息。用户离开 A 网站以后,又去访问 B 网站,如果没有同源限制,B 网站可以读取 A 网站的 Cookie,那么隐私就泄漏了。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。

由此可见,同源政策是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。

同源是为了维护网站存储在客户端的临时信息的安全性,是客户端存储的网站信息的安全屏障。

1.3 限制范围

随着互联网的发展,同源政策越来越严格。目前,如果非同源,共有三种行为受到限制。

(1) 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB。
(2) 无法接触非同源网页的 DOM。
(3) 无法向非同源地址发送 AJAX 请求(可以发送,但浏览器会拒绝接受响应)。

另外,通过 JavaScript 脚本可以拿到其他窗口的window对象。如果是非同源的网页,目前允许一个窗口可以接触其他网页的window对象的九个属性和四个方法。

九个对象window.closed、window.frames、window.length、window.location、window.opener、window.parent、window.self、window.top、window.window
四个方法window.blur()、window.close()、window.focus()、window.postMessage()

上面的九个属性之中,只有window.location是可读写的,其他八个全部都是只读。而且,即使是location对象,非同源的情况下,也只允许调用location.replace()方法和写入location.href属性。(只能进行简单的页面跳转)

虽然这些限制是必要的,但是有时很不方便,合理的用途也受到影响。故我们要使用一些技术去实现跨域。

一些小概念:

Q1:什么是网页的DOM?

A1:文档对象模型 (DOM) 是 HTML 和 XML 文档的编程接口。它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容。DOM 将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。简言之,它会将 web 页面和脚本或程序语言连接起来。

2.跨域的实现

下面就为大家介绍一些常用的跨域方案。

2.1 降域实现部分跨域通信

Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。如果两个网页一级域名相同,只是次级域名不同,浏览器允许通过设置document.domain共享 Cookie。

举例来说,A 网页的网址是http://w1.example.com/a.html,B 网页的网址是http://w2.example.com/b.html,那么只要设置相同的document.domain,两个网页就可以共享 Cookie。因为浏览器通过document.domain属性来检查是否同源。

也就是说如果两个页面均设置了这一js属性那么就可以实现一级域名相同的网页间的跨域请求。

下面我们呢通过一个具体的例子来实现这一操作。

2.1.1 环境搭建

我们利用apache的基于域名的虚拟主机技术来实现本次实验的环境搭建,这里大家可以配置一下。

1.配置网页目录
在这里插入图片描述
这些目录都需要创建,并在其内部写入首页文件index.html

2.apache配置文件
将下面的内容复制到D:\phpstudy_pro\Extensions\Apache2.4.39\conf\vhosts\default-default.conf文件内容去

<VirtualHost *:80>
	DocumentRoot "D:/phpstudy_pro/WWW/secbasic"
	ServerName www.security.com
    <Directory  "D:/phpstudy_pro/WWW/secbasic">
	        Options FollowSymLinks ExecCGI
			AllowOverride All
			Order allow,deny
			Allow from all
			Require all granted
			DirectoryIndex index.php index.html
	</Directory>
</VirtualHost>

#domain aaa
<VirtualHost *:80>
	DocumentRoot "D:/phpstudy_pro/WWW/aaa"
	ServerName www.aaa.com
    <Directory  "D:/phpstudy_pro/WWW/aaa">
	        Options FollowSymLinks ExecCGI
			AllowOverride All
			Order allow,deny
			Allow from all
			Require all granted
			DirectoryIndex index.php index.html
	</Directory>
</VirtualHost>

#domain bbb
<VirtualHost *:80>
	DocumentRoot "D:/phpstudy_pro/WWW/bbb"
	ServerName www.bbb.com
    <Directory  "D:/phpstudy_pro/WWW/bbb">
	        Options FollowSymLinks ExecCGI
			AllowOverride All
			Order allow,deny
			Allow from all
			Require all granted
			DirectoryIndex index.php index.html
	</Directory>
</VirtualHost>

#domain master.security.com
<VirtualHost *:80>
	DocumentRoot "D:/phpstudy_pro/WWW/master"
	ServerName master.security.com
    <Directory  "D:/phpstudy_pro/WWW/master">
	        Options FollowSymLinks ExecCGI
			AllowOverride All
			Order allow,deny
			Allow from all
			Require all granted
			DirectoryIndex index.php index.html
	</Directory>
</VirtualHost>

#domain slave.security.com
<VirtualHost *:80>
	DocumentRoot "D:/phpstudy_pro/WWW/slave"
	ServerName slave.security.com
    <Directory  "D:/phpstudy_pro/WWW/slave">
	        Options FollowSymLinks ExecCGI
			AllowOverride All
			Order allow,deny
			Allow from all
			Require all granted
			DirectoryIndex index.php index.html
	</Directory>
</VirtualHost>

3.配置本地host文件将域名解析到本地服务器上
C:\Windows\System32\drivers\etc\hosts
文件内容添加

127.0.0.1 slave.security.com
127.0.0.1 master.security.com 
127.0.0.1 www.security.com 
127.0.0.1 www.bbb.com 
127.0.0.1 www.aaa.com

4.小皮面板上重启apache进行测试
在这里插入图片描述
在这里插入图片描述

2.1.2 页面编写

master页面:index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>master</title>
</head>
<body>
    <h1>this is master.security.com</h1>
    <iframe id="001" src="http://slave.security.com"></iframe>
</body>

<script>
    //设置当前页面的域为本身的二级域名用于进行跨域通信
    document.domain = 'security.com';
    //抓取页面内部的ifram标签连接
    var ifr = document.getElementById("001");

    //等待加载完毕,将连接的窗口传递给win,取出内部的传输数据
    ifr.onload = function() {
        let win = ifr.contentWindow;
        alert(win.data);
    }

</script>
</html>

slave页面:index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>slave</title>
</head>
<body>
    <h1>this is slave.security.com</h1>
</body>
<script>
     document.domain = 'security.com';
     window.data = 'hello,i am slave';
</script>
</html>

2.1.3 测试

先关闭master页面的降域设置:
在这里插入图片描述
浏览器不允许进行跨域的数据交换,此时我们打开降域设置在此测试:
在这里插入图片描述
可以看到,master页面成功的获取了子页面的window.data全局变量。可以对其进行读取。

注意,这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexedDB 无法通过这种方法规避同源政策,而要使用下文介绍 PostMessage API。

到此,降域进行跨域可以解决一部分问题。但很明显,实际环境中还会遇到很多域名不一致的跨域场景。那么就需要新的方法来解决了。

2.2 片段识别符实现跨域

片段标识符(fragment identifier)指的是,URL 的#号后面的部分,比如http://example.com/x.html#fragment#fragment。如果只是改变片段标识符,页面不会重新刷新。

也就是说我们可以利用这一特性,将片段识别符作为信息载体实现跨域。下面我们来尝试实现。

2.2.1 环境搭建

参照2.1.1的环境进行搭建,其实已经配置完毕了。我们的测试页面放在aaa文件夹和bbb文件夹内部,实现www.aaa.comwww.bbb.com两个域之间的跨域通信。

2.2.2 页面编写

aaa目录下的页面:
index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>aaa</title>
</head>
<body>
    <h1>this is www.aaa.com</h1>
</body>
<script>
    //创建ifram标签包含跨域通信目标网页
    let ifr = document.createElement('iframe');
    ifr.style.display = 'none';
    ifr.src = "http://www.bbb.com/index.html#data";
    document.body.appendChild(ifr);
    
    //输出数据
    function checkHash() {
        try {
        //获取数据去掉'#'
        let data = location.hash ? location.hash.substring(1) : ' ';
        console.log('获得到的数据是:', data);
        }catch(e) {
            console.log('咦,发生甚么事了?');
        }
    }
    window.addEventListener('hashchange', function(e) {
        checkHash();
        });
    </script>
</html>

01.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>this is 01.html,page for foward the message</h1>
</body>
<script>
	//将数据传递给父页面的父页面
    parent.parent.location.hash = self.location.hash.substring(1)
</script>
</html>

bbb目录下的页面:
index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>bbb</title>
</head>
<body>
    <h1>this is www.bbb.com</h1>
</body>
<script>
    //检查父页面的hash标识符,确认是否为请求数据的
    switch(location.hash) {
        case "#data":
        callback();
        break;
    }

    function callback() {
    const data = "this is a message from bbb.com";
    // parent.location.hash = data;
    try {
        //尝试直接向父页面的hash内写入数据
        parent.location.hash = data;
    }catch(e) {
        // 当前主流浏览器下的安全机制无法修改 parent.location.hash
        // 所以要利用一个中间的代理文件 iframe 
        var ifrproxy = document.createElement('iframe');
        ifrproxy.style.display = 'none';
        ifrproxy.src = 'http://www.aaa.com/01.html#' + data;     // 该文件在请求域名的域下
        document.body.appendChild(ifrproxy);
        }
       }
    </script>
</html>

2.2.3 测试效果

我们先在bbb域里面尝试直接修改该父页面:
在这里插入图片描述

在这里插入图片描述
这是因为在现在的安全内环境下,这种子页面修改父页面的hash标识符的行为在跨域情况下是被禁止的。我们的解决方案就是在下面添加进入一个中转页面,利用中转页面和父页面同源的特性,让中转页面实现数据的传递。

大概原理如下图:
在这里插入图片描述
说白了,bbb找了中间人帮忙给修改他爷爷的hash片段识别符。

修改代码之后在此进行测试:
在这里插入图片描述
跨域成功,但是这样操作的缺点显而易见。

缺点:

  • 数据直接暴露在了url中
  • 数据容量和类型都有限
  • 需要第三方中转实现麻烦

2.3 window.name

window.name(一般在js代码里出现)的值不是一个普通的全局变量,而是当前窗口的名字,要注意的是每个iframe都有包裹它的window,而这个window是top window的子窗口,而它自然也有window.name的属性,window.name属性的神奇之处在于name值在不同的页面(甚至不同域名)加载后依旧存在(如果没有修改则值不会变化),并且可以支持非常长的name值(2MB)

比如你在某个页面的控制台输入:

window.name = "hello world"
window.location = "http://www.baidu.com"

可以看到,跳转后也被保存了下来

在这里插入图片描述

示例:window.name实现跨域

aaa域文件:

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>aaa</title>
</head>

<body>
    <h1>this is www.aaa.com</h1>
</body>
<script>
    let data = '';
    const ifr = document.createElement('iframe');
    ifr.src = "http://www.bbb.com";
    ifr.style.display = 'none';
    document.body.appendChild(ifr);

    //中转获取window.name
    ifr.onload = function() {
        //更换请求地址
        ifr.src = "http://www.aaa.com/01.html";
        //尝试取出window.name内的数据
        ifr.onload = function() {
            data = ifr.contentWindow.name;
            console.log('收到数据:', data);
        }
    }

    //此处为测试不进行中转
    // ifr.onload = function () {
    //     data = ifr.contentWindow.name;
    //     console.log('收到数据:', data);
    // }
</script>

</html>

01.html - 这个文件也可以为空,旨在提供一个可供bbb切换src的页面,用于制造同源访问。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>this is 01.html,page for foward the message</h1>
</body>
<script>
</script>
</html>

bbb域文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>bbb</title>
</head>
<body>
    <h1>this is www.bbb.com</h1>
</body>
<script>
    window.name = "传输的数据";
</script>
</html>

先尝试不设置中转页面是否可以获取子页面的data:
在这里插入图片描述
不允许,因为很显然aaa包含进了bbb,进行了跨域,并且window.name属性并未在限制范围之外。故一定会被同源策略禁止掉,我们可以采用二次修改src的方式,将bbb的window.name数值转移到本地同源的中转网页上,再传输进来。使用修改好的代码再次测试:

在这里插入图片描述
跨域成功。

2.4 window.postMessage()

上面的这种方法属于破解,HTML5 为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。

这个 API 为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。举例来说,父窗口aaa.com向子窗口bbb.com发消息,调用postMessage方法就可以了。

postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即“协议 + 域名 + 端口”。也可以设为*,表示不限制域名,向所有窗口发送。

示例1:新开页面之间的跨域通信

aaa域首页:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>aaa</title>
</head>

<body>
    <h1>this is www.aaa.com</h1>
</body>
<script>
    // 父窗口打开一个子窗口,取名title
    var popup = window.open('http://www.bbb.com', 'title');
    // 父窗口向子窗口发消息
    popup.postMessage('Hello, i am aaa page,who are you ?', 'http://www.bbb.com');

   // 监听 message 消息
   window.addEventListener('message', function (e) {console.log(e.data);}, false);

</script>

</html>

bbb域首页

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>bbb</title>
</head>
<body>
    <h1>this is www.bbb.com</h1>
</body>
<script>
        //子窗口向父窗口发送消息
        window.opener.postMessage('hello nice to meet you , my name is bbb', 'http://www.aaa.com');
        // 监听 message 消息
        window.addEventListener('message', function (e) {console.log(e.data);}, false);
</script>
</html>

测试效果:
在这里插入图片描述
在这里插入图片描述
可以看到其在新的窗口内进行了跨域通信。但是正常使用的时候弹出一个新页面并不常见。更多的还是使用iframe对页面进行嵌套处理,在使用postmessage实现平滑的跨域通信。

一些概念:

message事件的参数是事件对象event,提供以下三个属性。

  • event.source:发送消息的窗口
  • event.origin: 发送消息的网址(发送者地址)
  • event.data: 消息内容

示例2:结合iframe的跨域

aaa域页面:index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>aaa</title>
</head>

<body>
    <h1>this is aaa page</h1>
    <iframe src="http://www.bbb.com" ></iframe>
</body>
<script>

    window.onload = function () {
        let targetOrigin = 'http://www.bbb.com';
        //想要操作当前iframe的时候,就像该ifranme中postMessage()一个东西。
        window.frames[0].postMessage('来自aaa大哥的轮船~~~', targetOrigin );
        //*表示任何域都可以监听。
    }
    //当我监听到message事件的时候,我就知道有人向我发送数据了,我获得了数据就可以做对应的事情。内部对消息做处理
    window.addEventListener('message', function (e) {
        console.log('aaa 接收到的消息:', e.data);
    });
</script>

</html>

bbb域页面:index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>bbb</title>
</head>

<body>
    <h1>this is www.bbb.com</h1>
</body>
<script>
    //创建事件监听器,监听message,第二个参数内写上处理函数
    window.addEventListener('message', function (e) {
        //判断消息来源为自己的父页面,防止恶意信息传输
        if (e.origin !== 'http://www.aaa.com') {
            return;
        }
        console.log('message.source: ', e.source);
        console.log('message.origin: ', e.origin);
        console.log('bbb 接收到的消息:', e.data);
        parent.postMessage('来自bbb大哥的火箭^|^', e.origin);
    })
</script>

</html>

效果测试:
在这里插入图片描述

示例3:结合LocalStorage实现跨域

通过window.postMessage,读写其他窗口的 LocalStorage 也成为了可能。

大概的思路就是让子页面通过postmessage给父页面提供操作子页面LocalStorage小小数据库的接口。从而在子页面LocalStorage内实现和子页面的共享数据以进行跨域通信。

aaa域首页:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>aaa</title>
</head>

<body>
    <h1>this is aaa page</h1>
    <iframe src="http://www.bbb.com"></iframe>
</body>
<script>
    window.onload = function () {
        //抓取连接
        var win = document.getElementsByTagName('iframe')[0].contentWindow;
        //创建对象
        var obj = {
            id: 001,
            name: 'jack',
            age: '25',
            work: 'engineer'
        };

        // 存入对象
        win.postMessage(
            JSON.stringify({ key: obj.id, method: 'set', data: obj }),
            'http://www.bbb.com'
        );
        // 读取对象
        win.postMessage(
            JSON.stringify({ key: obj.id, method: "get" }),
            'http://www.bbb.com'
        );

        // //删除对象
        // win.postMessage(
        //     JSON.stringify({ key: obj.id, method: "remove" ,data: obj}),
        //     'http://www.bbb.com'
        // );

        //监听消息窗口,处理回显的数据
        window.onmessage = function (e) {
            if (e.origin != 'http://www.bbb.com') return;
            console.log('返回的JSON数据:',e.data);
            console.log('还原回对象模式:',JSON.parse(e.data));
        };
    }
</script>

</html>

bbb域首页:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>bbb</title>
</head>

<body>
    <h1>this is www.bbb.com</h1>
</body>
<script>
    //开启message事件监听
    window.addEventListener('message',receivemessage);
    function receivemessage(e){
        if (e.origin !== 'http://www.aaa.com') return;
        //接受的数据进行反JSON化,用于后续的localstorge处理
        var payload = JSON.parse(e.data);
        switch (payload.method) {
            case 'set':
                localStorage.setItem(payload.key,JSON.stringify(payload.data));
                break;
            case 'get':
                var parent = window.parent;
                var data = localStorage.getItem(payload.key);
                parent.postMessage(data,'http://www.aaa.com');
                break;
            case 'remove':
                console.log('已删除',e.data);
                localStorage.removeItem(payload.key);
                break;
        }
    }
</script>

</html>

测试增加查询操作:

在这里插入图片描述
测试删除操作:
在这里插入图片描述
可以看到,删除成功。

2.5 JSONP跨域

知道AJAX通信机制的同学肯定了解过,由于AXJX对于同源限制的十分之死。在十年前,大家为了解决跨域问题提出了JSONP这一hakcer的写法,虽然已经被淘汰,但是还是可以作为经典回味一下。

JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单易用,没有兼容性问题,老式浏览器全部支持,服务端改造非常小。

它的做法如下。

第一步,网页添加一个<script>元素,向服务器请求一个脚本,这不受同源政策限制,可以跨域请求。

<script src="http://api.foo.com?callback=bar"></script>

注意,请求的脚本网址有一个callback参数(?callback=bar),用来告诉服务器,客户端的回调函数名称(bar)。

第二步,服务器收到请求后,拼接一个字符串,将 JSON 数据放在函数名里面,作为字符串返回(bar({...}))。

第三步,客户端会将服务器返回的字符串,作为代码解析,因为浏览器认为,这是<script>标签请求的脚本内容。这时,客户端只要定义了bar()函数,就能在该函数体内,拿到服务器返回的 JSON 数据。

为了更好的演示这个例子借鉴了另一位博主的示例:使用JSONP解决跨域

我们需要在aaa域内创建页面发出请求:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JSONP简单实现</title>
</head>

<body>
    <button id="btn">点击发送请求</button>
</body>
<script>

    function getJsonpData(data) {
        console.log("获取数据成功")
        console.log(data) //{name:'tom'}
    }

    var btn = document.getElementById("btn");

    btn.onclick = function () {
        
        //创建script标签
        var script = document.createElement("script");
        script.src = 'http://localhost:3000/user?callback=getJsonpData';
        document.body.appendChild(script);
        script.onload = function () {
            document.body.removeChild(script)
        }
    }
</script>

</html>

之后在本地启动nodejs服务器:

const express = require('express')
const app = express()
const port = 3000

//路由配置
app.get("/user",(req,res)=>{
   //1.获取客户端发送过来的回调函数的名字
   let fnName = req.query.callback;
   //2.得到要通过JSONP形式发送给客户端的数据
   const data = {name:'tom'}
   //3.根据前两步得到的数据,拼接出个函数调用的字符串
   let result = `${fnName}({name:"tom"})`
   //4.把上步拼接得到的字符串,响应给客户端的<script> 标签进行解析执行
   res.send(result);
})

app.listen(port, () => {
   console.log(`Example app listening on port ${port}`)
})

测试:

在这里插入图片描述
整个过程作为发出跨域请求的页面,新建了一个script标签将目标服务器上的页面脚本包含进来。此时在发出的URL上有自己的回调函数参数。那么服务器收到此信息后对其做出处理,将本地的数据和回调函数拼接到一起予以返回。于是请求方就接收到了数据。完成跨域。

其缺点也很明显了:

1.传输数据少
2.仅支持get传输

2.6 websocket完成跨域

2.6.1 websocket简介

WebSocket 是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

说白了在websocket通信里只要支持这种通信方式无所谓跨不跨域。

下面是一个websocket通信的请求头

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。

正是因为有了Origin这个字段,所以 WebSocket 才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

2.6.2 简单示例

客户端首页:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>01.html-aaa</title>
</head>

<body>
    <h1>test for websocket</h1>
</body>
<script>
    let socket = new WebSocket('ws://127.0.0.1:3000');
    socket.onopen = function () {
        //向服务器发送肉麻消息
        socket.send('I LOVE YOU');
    } 
    socket.onmessage = function (e) {
        //接收返回的数据
        console.log(e.data);
    }
</script>
</html>

服务端NODEJS文件:在对应目录下的cmd使用node 文件名.js即可(需要安装nodejs环境)

let express = require('express');
let app = express();
let websocket = require('ws');
let wss = new websocket.Server({ port: 3000 });

wss.on('connection',
    function (ws) {
        ws.on('message',
            function (data) {
                console.log(data);
                ws.send('I am here,and listen to you.')
            }
        );
    }
)

测试结果:
在这里插入图片描述
转换查看服务端信息:

D:\phpstudy_pro\WWW\bbb>node socket.js
<Buffer 49 20 4c 4f 56 45 20 59 4f 55>

在这里插入图片描述

2.7 CORS

CORS 是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。相比 JSONP 只能发GET请求,CORS 允许任何类型的请求。

限于篇幅,这一块的内容请移步后续的文章。

下面就来总结一下,首先为了客户端的安全性,浏览器支持了同源政策。在不同的网站之间严禁互相访问本地存储文件(cookie、localstorge、indexeddb)并且在很多的属性方法上也做出了很大的限制。我们在开发中有时会有跨域的需求,故在漫长的岁月中演变出了很多的解决方案。其中有降域解决同二级域名网站间的跨域方案。也有利用片段标识符、windows.name等中转的汉克方案。这其中最方便的就是postmessage方法的使用。不仅可以结合iframe使用更是可以使用localstorge进行更加系统的跨域共享。

但是以上的方法都适用于静态html页面,涉及到动态页面时我们就有三个可选的解决方案,首先是古老的JSONP方案由于传输量小、方法支持单一不推荐、那么其次就是websocket通信,方便好用。最后还有最推荐的官方正统CROS跨域机制可谓是官方出品实属精品了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/153941.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【无标题】javaEE初阶---多线程(面试常用)

这篇文章 , 我将主要介绍多线程进阶部分的内容 . 主要涉及到一些在面试中常考的内容。 一:常见的锁策略 1.1乐观锁和悲观锁 乐观锁 : 预测接下来发生锁冲突的可能性不大 , 而进行的一类操作; 悲观锁 : 预测接下来发生锁冲突的可能性很大 , 而进行的一类操作.乐观锁 : 假设数…

ClassIn:如何打造更稳定的Zabbix监控系统

作者简介&#xff1a;罗呈祥。现就职于北京翼鸥教育科技有限公司&#xff0c;负责数据库相关的运维管理和技术支持工作&#xff0c;擅长故障处理和性能优化&#xff0c;对分布式数据库也有深入研究。 近期&#xff0c;OceanBase 社区发布了一篇关于我们公司选型分布式数据库的文…

bilateral_filter 双边滤波器详细用法

一、双边滤波&#xff08;Bilateral filter&#xff09;是一种可以保边去噪的滤波器。其输出像素的值依赖于邻域像素的值的加权组合。 从效果来说&#xff0c;双边滤波可产生类似美肤的效果。皮肤上的皱纹和斑&#xff0c;与正常皮肤的差异&#xff0c;远小于黑白眼珠之间的差异…

13种Shell逻辑与算术,能写出5种算你赢!

相较于最初的 Bourne shell&#xff0c;现代 bash 版本的最大改进之一体现在算术方面。早期的 shell 版本没有内建的算术功能&#xff0c;哪怕是给变量加1&#xff0c;也得调用单独的程序来完成。 1、算术方法一&#xff1a; $(( )) 只要都是整数运算&#xff0c;就可以在 $(…

DHT11温湿度传感器初识

目录 一、产品概述 1、接线方式 2、特点 3、数据传送逻辑 二、发送时序检测模块是否存在 1、C51单片机&#xff08;主机&#xff09;时序分析 2、编写代码检测模块是否存在 3、读取DHT11数据的时序分析 三、温湿度通过串口传到PC显示 四、温湿度检测小系统——使数据…

Discrete Opinion Tree Induction for Aspect-based Sentiment Analysis 论文阅读笔记

一、作者 Chenhua Chen、Zhiyang Teng、Zhongqing Wang、Yue Zhang School of Engineering, Westlake University, China Institute of Advanced Technology, Westlake Institute for Advanced Study Soochow University 二、背景 如何为每一个方面词定位相应的意见上下文…

反补码运算之 “1 - 1 = - 1 ” ?

我们在研究数据的二进制表示时遇到这样一个问题&#xff1a; 由于计算中的CPU只有加法器&#xff0c;没有减法器&#xff0c;所以在计算机采用原码做减法时对于&#xff1a;1 - 1 0 相当于1 &#xff08;-1&#xff09;&#xff0c;用二进制表示为&#xff1a;000110011001。…

儿童感染新冠病毒后发高烧如何应对?专家来支招

儿童感染新冠病毒后发热较多&#xff0c;但肺炎发展较少儿童感染新冠病毒后发高烧&#xff0c;是重症吗&#xff1f;徐红梅&#xff1a;从目前的接待情况来看&#xff0c;儿童感染新冠病毒后比成人发烧。超过一半的儿童在感染后有发烧症状&#xff0c;伴有咳嗽、鼻塞、流鼻涕、…

JavaWeb的一些学习总结

Web系统就是&#xff1a;前端负责貌美如花&#xff0c;后端负责坚如磐石 企业在做Web系统时&#xff0c;成本与收益的对比是不可忽视的&#xff0c;企业做项目一定是要盈利的&#xff01; 企业做项目最求的不是技术的新和潮&#xff0c;追求的是低成本和稳还有好用度&#xff…

函数的设计

一、默认参数 C允许在函数定义或声明时&#xff0c;为形参指定默认值&#xff0c;即默认参数&#xff08;default argument&#xff09;。 &#xff08;1&#xff09;函数定义与函数声明只能设置一次默认参数。 &#xff08;2&#xff09;可以设置多个默认参数&#xff0c;设…

【精华】搞定JVM调优学习

JVM 介绍 1. 什么是 JVM JVM 是 Java Virtual Machine&#xff08;Java 虚拟机&#xff09;的缩写。一台执行 Java 程序的机器。 2 .JAVA 语言的执行原理 计算机语言&#xff1a; 计算机能够直接执行的指令。这种指令和系统及硬件有关。 计算机高级语言&#xff1a; 在遵循…

「数据」驱动行业拐点,毫末智行冲刺自动驾驶3.0时代

“毫末预计&#xff0c;到2025年中国高阶辅助驾驶搭载率将达到70%。而在汽车新消费领域&#xff0c;中国汽车市场增换购消费比例将达到60%&#xff0c;智能驾驶功能成为必选因素&#xff0c;并迎来商业化的加速发展。”1月5日&#xff0c;第七届HAOMO AI DAY上&#xff0c;毫末…

黑马学ElasticSearch(五)

目录&#xff1a; &#xff08;1&#xff09;DSL查询语法-DSL查询分类和基本语法 &#xff08;2&#xff09;DSL查询语法-全文检索查询 &#xff08;3&#xff09;DSL查询语法-精确查询 &#xff08;4&#xff09;DSL查询语法-地理查询 &#xff08;5&#xff09;DSL查询语…

计算机网络(一)

计算机网络1 概述1.1 计算机网络的作用1.2 因特网概述1.2.1 网络的网络1.2.2 Internet和internet的区别1.2.3 因特网发展的三个阶段1.2.4 ISP介绍1.2.5 ISP分类1.2.5.1 主干ISP1.2.5.2 地区ISP1.2.5.3 本地ISP1.2.6 因特网交换点 IXP1.3 因特网的组成1.3.1 因特网的边缘部分1.3…

资产管理4大难点,如何破解?

随着企业业务扩大、人员增多&#xff0c;固定资产的数量和种类也会随着增加。此时&#xff0c;如何高效管理企业资产就成为很多企业亟待解决的一大难题。 传统资产管理4大难点 01.资产管理部门需要联系采购部门、使用部门、财务部门等收集数据&#xff0c;汇总难且工作量大&…

vacuum移除不必要的CLOG文件

迫切模式弥补了惰性模式的缺陷。它会扫描所有页面&#xff0c;检查表中的所有元组&#xff0c;更新相关的系统视图&#xff0c;并在可能时删除不必要的CLOG文件与页面。当满足以下条件时&#xff0c;会执行迫切模式。pg_database.datfrozenxid<(OldestXmin-vacuum_freeze_ta…

【多线程】【C++ 知识点】pthread_join学习

目录pthread_join进程id和线程idpthread_join pthread_join() 主线程会进入阻塞装题&#xff0c;pthread_join()之后的代码&#xff0c;只有等待子进程退出之后才能执行。 代码块A pthread_create(&id, NULL, Fn, NULL);pthread_create(&id_1, NULL, Fn, NULL);pthre…

jdk1.8 更替为 oepnJdk8遇到的坑

背景&#xff1a;客户服务器因为说jdk要收费&#xff0c;所以要求将jdk1.8替换为openJdk&#xff0c;本地测试ok&#xff0c;则将服务器的jdk替换为openJdk8&#xff0c;出现一个登录异常&#xff0c;调查发现是一个sso登录的问题&#xff08;单点登录&#xff09;&#xff0c;…

Rhce第一次作业

chrony服务部署&#xff1a;两台机器a: 第一台机器从阿里云同步时间&#xff0c;第二台机器从第一台机器同步时间1.查看防火墙是否关闭&#xff0c;若未关闭&#xff0c;关闭防火墙2.打开chrony配置文件3.向配置文件中写入阿里云时间服务器&#xff0c;并允许两台机器所在的网段…

从0到1完成一个Vue后台管理项目(十八、基础地图绘制)

往期 从0到1完成一个Vue后台管理项目&#xff08;一、创建项目&#xff09; 从0到1完成一个Vue后台管理项目&#xff08;二、使用element-ui&#xff09; 从0到1完成一个Vue后台管理项目&#xff08;三、使用SCSS/LESS&#xff0c;安装图标库&#xff09; 从0到1完成一个Vu…