前端跨域
本文总结前端常用的跨域方案和例子,以及周边的应用知识
(1)跨域原理
(2)域名概念
(3)本地简单模拟跨域
(4)常见跨域方案
一、跨域原理
我们常说的跨域,是由浏览器同源政策的限制引起的。
同源政策:
同源策略/SOP(Same origin policy)是一种约定,是浏览器最核心也最基本的安全功能。它的核心就在于它认为自任何站点装载的信赖内容是不安全的。
所谓同源是指"协议+域名+端口"三者相同。
有三个方面限制:
1.DOM 同源策略:禁止对不同源页面 DOM 进行操作。主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。
2.XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。
3.cookie,localstorage和IndexDB无法读取
二、域名概念
顶级域名
互联网DNS等级之中的最高级的域,它保存于DNS根域的名字空间中。顶级域名是域名的最后一个部分,即是域名最后一点之后的字母。如 .cn、.com、.net
主、子域名
baidu.com是主域名,www.baidu.com和wap.baidu.com以及new.baidu.com都是他的子域名
父、子域名
一个相对概念。如 a.test.com是 test.com的子域名,q.a.test.com是 a.test.com的子域名
window.location 属性
例子:https://www.test.com:8080/first/second/index.html?user=123&name=panda#home
protocol(协议) => ‘https:’
hostname(主机名) => ‘www.test.com’
port(端口号) => ‘8080’
host(主机名) => ‘www.test.com:8080’
origin(来源) => ‘https://www.test.com:8080’
pathname(路径) => ‘/first/second/index.html’
search(url参数) => ‘?user=123&name=panda’
hash(hash值) => ‘#home’
href(地址) => ‘https://www.test.com:8080/first/second/index.html?user=123&name=panda#home’
三、简单模拟跨域
安装 node.js
在相应文件夹下打开cmd,输入: http-server 开启本地服务
在不同文件下开启本地服务,通过一样的 ip 地址但是不同的端口号,可用于简单本地模拟跨域
四、常见跨域方案
(1) jsonp 跨域
(2) location.hash
(3) window.name
(4) window.domain
(5) postMessage
(6) 跨域资源共享(CORS)
(7) 代理
(1) (6) (7) 可用于后台接口跨域
(1)jsonp 跨域
原理:在html页面中通过标签从不同域名下加载资源,而被浏览器允许,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。(常用作跨域服务器的请求)
原生代码:
<script type="text/javascript">
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'http://www.test.com:8080/login?user=admin&callback=onBack';
//document.head.appendChild(script);
document.getElementsByTagName('head')[0].appendChild(script);
// 回调执行函数
function onBack(res) {
alert(JSON.stringify(res));
}
</script>
一般请求带到了服务器,解析参数然后拼成字符串或者 Json 当成返回数据
如返回: onBack({“status”: 200, “user”: “admin”}),就是执行上面的 onBack 函数
可以通过之前的模拟跨域,通过一样的 IP 地址但是请求不同端口的 js 文件在本地模拟服务器跨域的返回。(这里www.test.com就是我们的本地模拟的ip地址)
jQ 代码:
$.ajax({
url: 'http://www.test.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "onBack", // 自定义回调函数名
data: {}
});
jsonp 跨域缺点:只能实现get一种请求
(2)location.hash
原理:
1、改变hash值不会刷新页面,因此可以利用 hash 值来传递数据。
2、无论跨域与否,iframe 内可以获取自己的 location.hash
3、只要域名相同就能通信,即使ABC三层嵌套
流程:
三个页面 A B C,A 通过第三方页面 C 达到与 B 的通讯
-
A 页面设置 iframe 指向 B 页面地址,待 iframe 加载完后修改该 hash 值从而传参给 B,
并监听本页面 hash 的变化 -
B 页面在页面内监听 hash 变化从而获取参数,设置 iframe 指向 C 页面并传参给 C
-
C 页面监听 hash 变化,只要 A C 页面域名相同,可根据 B 的 hash 参数,修改父父窗口(即 A页面)的 hash 从而触发 A 页面的监听,达到参数回传给 A 的效果
实例:
www.test.com:8080/A.html
var ifr = document.createElement('iframe');
createIfr('admin');
window.onhashchange = function(){ //监听hash变化
if(location.hash == '#status')
callBack();
}
function callBack(){
alert('A: data from B.html ---> ' + location.hash);
}
function createIfr(hash){
ifr.style.display = 'none';
ifr.src = 'www.test.com:8081/B.html';
document.body.appendChild(ifr);
ifr.onload = function(){ ifr.src = 'www.test.com:8081/B.html#' + hash; }
}
www.test.com:8081/B.html
var ifr = document.createElement('iframe');
window.onhashchange = function(){
if(location.hash == '#admin')
callBack();
createIfr('status');
}
function callBack(){
alert('B: data from A.html ---> ' + location.hash);
}
function createIfr(hash){
ifr.style.display = 'none';
ifr.src = 'www.test.com:8080/C.html';
document.body.appendChild(ifr);
ifr.onload = function(){ ifr.src = 'www.test.com:8080/C.html#' + hash; }
}
www.test.com:8080/C.html
window.onhashchange = function(){
parent.parent.location.hash = location.hash;
}
缺点:因为是修改 url 的 hash ,数据直接暴露在了url中,而且数据容量和类型都有限
(3)window.name
原理:在一个窗口(window)的生命周期内,窗口载入的所有的页面(不管是相同域的页面还是不同域的页面)都是共享一个 window.name 的(可存2M),每个页面对 window.name 都有读写的权限,window.name 是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
流程:
-
A 页面设置 iframe 指向 B 页面地址
-
B 在当前页面设置 window.name 并传参
-
C 页面不需处理
-
待 B 加载完成,A 页面 iframe 地址从 B 改成 C,从而可以访问在 B 页面时设置的 name
例子:
www.test.com:8080/A.html
var iframe = document.createElement('iframe');
var status = 0;
iframe.onload = function(){
if(status == 1){
onCallback(iframe.contentWindow.name);
}else if(status == 0){
status = 1;
iframe.contentWindow.location = "www.test.com:8080/C.html";
}
}
iframe.src = 'www.test.com:8080/B.html';
document.body.appendChild(iframe);
function onCallback(res) {
alert('A: data from B.html ---> ' + res);
}
www.test.com:8081/B.html
window.name = 'name2.html设置的数据';
www.test.com:8080/C.html 不做操作
(4)window.domain
原理:当两个页面域名不同,但主域名相同时,可以把两个页面的 document.domain 都设成相同的域名,此时浏览器会认为他们在同一域名内,这样就可以通讯了。
www.test.com:8080/first/A.html
<iframe id="iframe" src="www.test.com:8080/test/B.html"></iframe>
<script type="text/javascript">
document.domain = 'www.test.com';
var user = 'admin';
</script>
www.test.com:8080/second/B.html
document.domain = 'www.test.com';
// 获取父窗口中变量
alert('data from parent ---> ' + window.parent.user);
缺点:只适用于主域名相同,而子域名不同的情况。
(5)postMessage
HTML5 中的 API,专门用来跨域操作的 Windows 属性。
window.postMessage(data,origin)
data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
www.test.com:8080/A.html
<iframe id="ifr" src="www.test.com:8081/B.html"></iframe>
<script type="text/javascript">
var iframe = document.getElementById('ifr');
iframe.onload = function() {
var data = {
name: 'panda'
};
iframe.contentWindow.postMessage(JSON.stringify(data), 'www.test.com:8081/B.html');
};
// 监听 B 返回数据
window.addEventListener('message', function(e) {
alert('A: data from B ---> ' + e.data);
}, false);
</script>
www.test.com:8081/B.html
window.addEventListener('message', function(e) {
alert('B: data from A ---> ' + e.data);
var data = {
pwd: '123'
};
window.parent.postMessage(JSON.stringify(data), 'www.test.com:8080/A.html');
}, false);
(6)跨域资源共享(CORS)
CORS需要浏览器和服务器同时支持。只要服务器实现了CORS接口,浏览器发现跨域就会自动添加一些附加的头信息,就可以跨源通信。
CORS分为简单请求和非简单请求。
1.简单请求
范围
(1)请求方法是以下三种方法之一
HEAD/GET/POST
(2)HTTP的头信息不超出以下几种字段
-
Accept
-
Accept-Language
-
Content-Language
-
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
流程
对于简单请求,浏览器直接发出CORS请求。就是在头信息之中,增加一个Origin字段(浏览器会自动添加,标识请求来自哪个源)。
(1) 如果Origin指定的域名在许可范围外,服务器会返回一个正常的HTTP回应。但是会报一个onerror错误。
(2)如果Origin指定的域名在许可范围内,服务器返回的响应,响应报文会多出几个头信息字段。
Access-Control-Allow-Origin: 可跨域域名
Access-Control-Allow-Credentials: 是否允许发送Cookie(请求报文要设置withCredentials)
Access-Control-Expose-Headers: 头部字段能否暴露
2.非简单请求
不满足简单请求条件的就是非简单请求。
非简单请求在通信前,会发出一个预检请求判断能否跨域。
预检请求:
请求方法:Options
Origin: 请求来自哪个源
Access-Control-Request-Method: 非简单请求方法
Access-Control-Request-Headers:: 非简单请求头部
(1)预检请求失败,正常返回http,但是报错。
(2)预检请求成功,返回和简单请求一样的三个头部。
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Expose-Headers
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样
(6)node 中间件代理
原理:通过启一个代理服务器,实现数据的转发