跨域解决方法
浏览器跨域解决方法
目录
- 跨域概念解析
- JSONP解决方案
- document.domain + iframe方案
- location.hash + iframe方案
- window.name + iframe方案
- postMessage API方案
- CORS跨域方案
- WebSocket协议方案
一、跨域概念解析
当前浏览器的内容访问要求同源,所谓同源即是url的方案(协议)
,主机(域名)
,和端定义
,
只有当方案,主机和端口都匹配时,两个对象具有相同的起源
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介
二、JSONP解决方案
在html标签中很多带src属性的标签都可以跨域请求内容,比如我们熟悉的img图片标签。同理,script标签也可以,
通过<script>
标签来实现跨域数据请求的方法。其工作原理主要依赖于浏览器对<script>
标签的src属性没有同源策略限制这一特性。下面详细解释其实现跨域的步骤:
创建回调函数:在你的网页中定义一个全局函数,这个函数将被用来处理从其他域返回的数据。
构造
<script>
标签:动态地创建一个<script>
元素,并设置它的src属性为目标服务器的URL。在这个URL中,你需要指定一个参数来告诉服务器你的回调函数名称。服务器响应:当目标服务器收到请求后,它会构建一段JavaScript代码作为响应。这段代码调用你在第一步中定义的回调函数,并将需要的数据作为参数传递给这个函数。
执行脚本:浏览器接收到服务器的响应后,会执行这段JavaScript代码,从而触发你之前定义的回调函数,完成数据的获取和处理。
例如,假设你想从example.com
获取数据,并且已经定义了一个名为handleResponse
的回调函数,那么你可以创建一个<script>
标签,其src属性为http://example.com/data?callback=handleResponse
。服务器应该返回类似handleResponse({...})
的JavaScript代码,其中{...}
是实际的数据。
需要注意的是,由于JSONP的实现方式,它只能支持GET请求,而且如果服务器不正确地处理回调参数,可能会引发安全问题,如XSS攻击。因此,在使用JSONP时需要格外小心。随着CORS(跨源资源共享)机制的普及,JSONP逐渐被更为安全的CORS替代。
三、document.domain + iframe方案
使用 document.domain
和 <iframe>
实现跨域通信是一种传统的方法,主要用于主域名相同但子域名不同的情况下。例如,父页面位于 a.example.com
,而子页面位于 b.example.com
。通过设置 document.domain
,可以让这两个不同子域名的页面认为它们属于同一个源(即相同的主域名),从而允许它们之间进行直接的JavaScript交互。
实现步骤
设置
document.domain
:- 在父页面和子页面中都显式地设置
document.domain
为相同的主域名。比如,如果两个页面分别位于a.example.com
和b.example.com
,则在每个页面中添加以下代码:这样做之后,两个页面就会被视为来自同一源。1
document.domain = "example.com";
- 在父页面和子页面中都显式地设置
建立 iframe 嵌套:
- 在父页面中嵌入一个指向子页面的
<iframe>
标签:1
<iframe id="myIframe" src="http://b.example.com/page.html"></iframe>
- 在父页面中嵌入一个指向子页面的
实现通信:
- 在父页面中,可以通过获取 iframe 的 contentWindow 对象来访问子页面中的 JavaScript 变量或调用其函数。
1
2
3
4var iframe = document.getElementById('myIframe');
var iframeWindow = iframe.contentWindow;
// 调用子页面中的函数
iframeWindow.someFunction(); - 同样,在子页面中也可以访问父页面的 JavaScript 变量或调用其函数:
1
window.parent.someParentFunction();
- 在父页面中,可以通过获取 iframe 的 contentWindow 对象来访问子页面中的 JavaScript 变量或调用其函数。
注意事项
- 这种方法仅适用于主域名相同的情况。如果主域名不同,则无法通过此方法解决跨域问题。
- 设置
document.domain
可能会影响页面的安全性,因此应谨慎使用,并确保只在受信任的上下文中操作。
四、location.hash + iframe方案
使用 location.hash
和 <iframe>
实现跨域通信是一种巧妙的方法,特别适用于需要在不同源的页面之间传递少量数据的情况。这种方法利用了 iframe 的 src 属性和 location 对象的 hash 属性(即 URL 中 “#” 后面的部分),允许父页面和嵌入的 iframe 页面之间进行单向或双向的数据交换。
实现原理
- 父页面通过修改 iframe 的 src 属性中的 hash 部分来向 iframe 发送消息。
- **子页面(iframe)**可以通过监听自身的
hashchange
事件来接收来自父页面的消息。 - 类似地,子页面也可以通过修改父页面的
location.hash
来发送消息给父页面,父页面则监听自身的hashchange
事件来接收这些消息。
实现步骤
父页面向 iframe 发送消息
在父页面中创建一个指向目标域名的 iframe:
1
<iframe id="myIframe" src="http://otherdomain.com/page.html"></iframe>
通过 JavaScript 修改 iframe 的 src 属性中的 hash 部分来发送消息:
1
2var iframe = document.getElementById('myIframe');
iframe.src = iframe.src.split('#')[0] + '#' + encodeURIComponent('Hello from parent!');
iframe 接收消息并处理
- 在 iframe 内部监听
hashchange
事件来捕获来自父页面的消息:1
2
3
4window.addEventListener('hashchange', function() {
var message = decodeURIComponent(location.hash.substr(1));
console.log('Received message:', message);
});
iframe 向父页面发送消息
在 iframe 内部通过修改父页面的
location.hash
来发送消息:1
2
3
4
5try {
parent.location.hash = 'Message from iframe';
} catch (e) {
// 跨域时会抛出异常,可以忽略或者做其他处理
}在父页面中监听
hashchange
事件以接收来自 iframe 的消息:1
2
3
4window.addEventListener('hashchange', function() {
var message = decodeURIComponent(location.hash.substr(1));
console.log('Received message from iframe:', message);
});
注意事项
- 使用
location.hash
进行跨域通信有一定的局限性,比如只能传输简单的字符串信息,并且长度受限于浏览器对 URL 总长度的限制。 - 由于涉及到直接操作 URL,频繁的更改可能会导致用户体验问题,如浏览器历史记录条目的增加。
五、window.name + iframe方案
使用 window.name
和 <iframe>
实现跨域通信是一种较为巧妙的方法,利用了浏览器的一个特性:window.name
属性在页面的整个生命周期内保持不变,即使页面的 URL 发生变化,只要没有重新加载或导航到其他页面,window.name
的值就不会改变。这使得它成为一种可行的跨域通信手段。
工作原理
初始设置:
- 在父页面中创建一个指向目标域名的 iframe。
- 目标页面(即 iframe 中加载的页面)设置其
window.name
为需要传递的数据。
数据传递:
- 父页面通过将 iframe 的 src 更改为与自身同源的一个空白页(例如,父页面所在域下的一个空 HTML 文件),然后访问 iframe 的
contentWindow.name
来读取之前设置的window.name
值。
- 父页面通过将 iframe 的 src 更改为与自身同源的一个空白页(例如,父页面所在域下的一个空 HTML 文件),然后访问 iframe 的
清理:
- 由于
window.name
可能会被后续操作覆盖或污染,通常会在读取完数据后立即将其重置为空字符串或其他默认值。
- 由于
实现步骤
目标页面(子页面)
- 在目标页面(即要从其获取数据的页面)中设置
window.name
:1
window.name = JSON.stringify({key: 'value'}); // 将需要传递的数据序列化为字符串
父页面
创建 iframe 并设置其 src 属性为目标页面的 URL:
1
<iframe id="myIframe" src="http://otherdomain.com/page.html" style="display:none;"></iframe>
使用 JavaScript 修改 iframe 的 src 属性为与父页面同源的一个空白页,并读取
window.name
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var iframe = document.getElementById('myIframe');
// 当 iframe 加载完成时
iframe.onload = function() {
// 修改 iframe 的 src 到同源的一个空白页
iframe.src = "about:blank"; // 或者是一个同源的空白HTML文件URL
// 访问 iframe 的 window.name 获取数据
var data = iframe.contentWindow.name;
// 处理接收到的数据
console.log(JSON.parse(data)); // 解析返回的数据
// 清理 window.name 防止泄露
iframe.contentWindow.name = '';
};
注意事项
- 这种方法适用于一次性的数据传递,因为一旦页面导航或重新加载,
window.name
的值可能会被覆盖或丢失。 window.name
的大小有限制(不同浏览器可能有所不同),因此不适合传输大量数据。
六、postMessage API方案
postMessage
是一种用于实现安全的跨文档消息传递的API,允许来自不同源的窗口或iframe之间进行通信。它不仅适用于父页面和子iframe之间的通信,也适用于任何两个窗口(如通过 window.open
打开的新窗口)之间的通信。这种方法比使用 location.hash
、document.domain
或 window.name
更加灵活和安全。
使用 postMessage
实现跨域通信
基本语法
发送消息的一方使用如下格式调用 postMessage
方法:
1 | otherWindow.postMessage(message, targetOrigin, [transfer]); |
message
: 需要传递的数据。可以是任意类型的对象,但如果需要支持旧版浏览器,建议使用基本数据类型或JSON可序列化的对象。targetOrigin
: 指定目标窗口的源(协议+域名+端口),只有当目标窗口的源与此参数匹配时,才会发送消息。使用"*"
表示不检查源,但这通常不推荐因为存在安全隐患。[transfer]
(可选): 一个数组,包含要转移所有权的对象(例如ArrayBuffer对象)。这些对象在发送后将不再属于发送者。
接收消息的一方需要监听 message
事件:
1 | window.addEventListener('message', function(event) { |
示例
父页面向 iframe 发送消息
父页面代码:
1 | <iframe id="myIframe" src="http://otherdomain.com/page.html"></iframe> |
iframe代码:
1 | // 监听来自父页面的消息 |
iframe 向父页面发送消息
只需调整上述示例中的角色,即在 iframe 中使用 parent.postMessage()
发送消息,在父页面中监听 message
事件即可。
iframe代码:
1 | // 向父页面发送消息 |
父页面代码:
1 | // 监听来自iframe的消息 |
注意事项
- 安全性: 始终验证
event.origin
来确保消息是从预期的来源接收的。不要信任未经验证的消息内容。 - 数据类型: 虽然可以发送复杂对象,但要注意兼容性问题,特别是在处理旧版浏览器时。
- 性能: 对于频繁的消息传递,考虑消息的大小和频率以避免影响性能。
七、CORS跨域方案
跨域资源共享(Cross-Origin Resource Sharing,简称 CORS)是一种基于HTTP的协议,它允许服务器声明哪些源可以访问其资源。CORS通过在HTTP响应中添加特定的头部信息来控制跨域请求的行为,从而增强了Web应用的安全性。
CORS的工作原理
CORS机制主要依赖于以下几个HTTP头部:
- Origin: 这个头部由浏览器自动添加到跨域请求中,表明发起请求的来源(协议+域名+端口)。
- Access-Control-Allow-Origin: 服务器使用这个头部来指定哪些源可以访问资源。它可以设置为具体的源(如
http://example.com
),也可以设置为通配符*
表示允许所有源访问。 - Access-Control-Allow-Methods: 指定允许的HTTP方法(如GET, POST, PUT等)。
- Access-Control-Allow-Headers: 指定除了默认的简单请求头外,还允许哪些自定义请求头。
- Access-Control-Allow-Credentials: 表示是否允许发送凭据(如Cookies、HTTP认证信息)。默认情况下,CORS请求不会发送这些凭据。
请求类型
CORS请求分为两种类型:简单请求和预检请求。
简单请求
满足以下条件的请求被认为是简单请求:
- 使用
GET
,HEAD
, 或POST
方法。 - HTTP 头部仅包含
Accept
,Accept-Language
,Content-Language
,Content-Type
(但限制为application/x-www-form-urlencoded
,multipart/form-data
, 或text/plain
)。
对于简单请求,浏览器直接发送请求,并检查响应中的Access-Control-Allow-Origin
头部以确定是否允许该请求。
预检请求
如果请求不符合简单请求的标准,浏览器会首先发送一个OPTIONS
请求(称为预检请求)来询问服务器是否允许实际请求。预检请求会携带如下头部:
- Access-Control-Request-Method: 实际请求将使用的HTTP方法。
- Access-Control-Request-Headers: 实际请求将携带的额外HTTP头部。
服务器需要响应一个200状态码,并且在响应头部中包含必要的CORS头部信息。
注意事项
- 安全性: 不要随意设置
Access-Control-Allow-Origin
为*
,尤其是在需要发送凭据的情况下。应该根据实际情况精确控制允许访问的源。
八、WebSocket协议方案
WebSocket 协议本身并不受限于同源策略(Same-Origin Policy),这意味着 WebSocket 连接可以在不同的源之间建立,而不需要像传统的 HTTP 请求那样处理跨域资源共享(CORS)。然而,在实际应用中,为了确保安全性,服务器通常会验证请求的来源,并决定是否接受连接。
WebSocket 跨域的基本原理
在 WebSocket 连接中,浏览器会在握手阶段发送一个标准的 HTTP 请求,该请求包含以下关键头部:
- Origin: 浏览器自动添加此头部,指示发起 WebSocket 连接的源(协议+域名+端口)。
- Sec-WebSocket-Key: 用于握手过程中的安全验证。
- Sec-WebSocket-Version: 指示使用的 WebSocket 协议版本。
服务器在收到连接请求后,可以通过检查 Origin
头部来决定是否允许该连接。如果服务器认为该来源是可信的,则可以继续握手并建立连接;否则,服务器可以选择拒绝连接。
服务器端配置 WebSocket 跨域
以下是js服务器环境下的 WebSocket 跨域配置示例。
Node.js 使用 ws
库
在 Node.js 中使用 ws
库时,可以通过中间件或直接在 WebSocket 服务器上设置来处理跨域请求。
1 | const WebSocket = require('ws'); |