大家好,又见面了,我是你们的朋友全栈君。
目录
1 域名基础
1.1 基本定义
1.2 主域名附加域名子域名
2 跨域访问原理和防盗链
2.1 防盗链
2.2 跨域访问原理
2.2.1 JSONP跨域原理
2.2.2 CORS跨域原理
3 四种跨域方法
3.1 JSONP
3.2 document.domain
3.3 window.name
3.4 [HTML5]postMessage
1 域名基础
1.1 基本定义
一个完整的域名由二个或二个以上部分组成,各部分之间用英文的句号”.”来分隔,最后一个”.”的右边部分称为顶级域名(TLD,也称为一级域名),最后一个”.”的左边部分称为二级域名(SLD),二级域名的左边部分称为三级域名,以此类推,每一级的域名控制它下一级域名的分配。定义是这样的,在域名中包含两个点的,就叫二级域名,只包含一个点的,就是一级域名。
域名级数是指一个域名由多少级组成,域名的各个级别被“.”分开,简而言之,有多少个点就是几级域名。
顶级域名在开头有一个点,一级域名就是在“com net org”前加一级:http://www.cdxwcx.com/(前面是www的通用网址)
二级域名就是在一级域名前再加一级:http://domain.cdxwcx.com/(www替换成了别的)
二级域名及其以上级别的域名,统称为子域名,不在“注册域名”的范畴中。
中间由点号分隔开,最右边的那个词称为顶级域名。(!补充: 也就是说www只是一个主机名,真正的一级域名是由一个合法字符串+域名后缀组成。)
1.2 主域名附加域名子域名
主域名maindomain,也就是虚拟主机的帐号。主域名对应的ftp路径是 /public_html/ 比如www.maindomain.com是主域名,你把一个名为aaa.php的网页上传到public_html下面,就可以用http://www.maindomain.com/aaa.php来访问了。
附加域,也就是国内说的“绑定域名”。除了主域名以外,还可以自由绑定无数个域名。附加域可以在 /public_html/**/ 下面自定义目录。 比如www.a.com是一个附加域,可以绑定到public_html/a.com下面,可以是public_html/a/ ,可以是public_html/mysite/a/ ,还可以public_html/本身,或者 /public_html/*/ 其中的*,可以自定义*
按照使用习惯,一般建议如下目录,这样看起来比较直观。只是建议,可以自定义。 www.maindomain.com 对应 /public_html/ www.a.com 对应 /public_html/a.com/ www.b.com 对应 /public_html/b.com/ www.c.com 对应 /public_html/c.com/
子域名,就是subdomain, 子域名,也可以自定义路径。 比如 bbs.maindomain.com 可以绑定到 /public_html/bbs.maindomain.com/ 目录,或者/public_html/bbs/
2 跨域访问原理和防盗链
2.1 防盗链
网站资源都有域的概念,浏览器加载一个站点时,首先加载这个站点的首页,一般是index.html或者index.php等。页面加载,如果仅仅是加载一个index.html页面,那么该页面里面只有文本,最终浏览器只能呈现一个文本页面。丰富的多媒体信息无法在站点上面展现。
那么我们看到的各类元素丰富的网页是如何在浏览器端生成并呈现的?其实,index.html在被解析时,浏览器会识别页面源码中的img,script等标签,标签内部一般会有src属性,src属性一般是一个绝对的URL地址或者相对本域的地址。浏览器会识别各种情况,并最终得到该资源的唯一地址,加载该资源。具体的加载过程就是对该资源的URL发起一个获取数据的请求,也就是GET请求。各种丰富的资源组成整个页面,浏览器按照html语法指定的格式排列获取到各类资源,最终呈现一个完整的页面。因此一个网页是由很多次请求,获取众多资源形成的,整个浏览器在一次网页呈现中会有很多次GET请求获取各个标签下的src资源。
上图是一个网页呈现过程中的抓包截图。可以看到,大量的加载css、js和图片类资源的get请求。
观察其中的请求目的地址,可以发现有两类,一个是本站的43.242段的IP地址,这是本站的空间地址,即向本站自身请求资源,一般来说这个是必须的,访问资源由自身托管。另外一类是访问182的网段拉取数据。这类数据不是托管站内的,是在其他站点的。浏览器在页面呈现的过程,拉取非本站的资源,这就称“盗链”
准确的说,只有某些时候,这种跨站访问资源,才被称为盗链。假设B站点作为一个商业网站,有很多自主版权的图片,自身展示用于商业目的。而A站点,希望在自己的网站上面也展示这些图片,直接使用:<img src=”http://b.com/photo.jpg”/>
这样,大量的客户端在访问A站点时,实际上消耗了B站点的流量,而A站点却从中达成商业目的。从而不劳而获。这样的A站点着实令B站点不快的。那么如何禁止此类问题呢?
HTTP协议和标准的浏览器对于解决这个问题提供便利,浏览器在加载非本站的资源时,会增加一个头域,头域名字固定为:Referer 而在直接粘贴地址到浏览器地址栏访问时,请求的是本站的该url的页面,是不会有这个referer这个http头域的。使用Chrome浏览器的调试台,打开network标签可以看到每一个资源的加载过程,下面两个图分别是主页面和一个页面内资源的加载请求截图:
这个referer标签正是为了告诉请求响应者(被拉取资源的服务端),本次请求的引用页是谁,资源提供端可以分析这个引用者是否“友好”,是否允许其“引用”,对于不允许访问的引用者,可以不提供图片,这样访问者在页面上就只能看到一个图片无法加载的浏览器默认占位的警告图片,甚至服务端可以返回一个默认的提醒勿盗链的提示图片。
一般的站点或者静态资源托管站点都提供防盗链的设置,也就是让服务端识别指定的Referer,在服务端接收到请求时,通过匹配referer头域与配置,对于指定放行,对于其他referer视为盗链。
2.2 跨域访问原理
维基上面给出了跨站访问的危害性。从这里可以整理出跨站访问的定义:JS脚本在浏览器端发起的请求其他域(名)下的网站数据的HTTP请求。
这里要与referer区分开,referer是浏览器的行为,所有浏览器发出的请求都不会存在安全风险。而由网页加载的脚本发起请求则会不可控,甚至可以截获用户数据传输到其他站点。referer方式拉取其他网站的数据也是跨域,但是这个是由浏览器请求整个资源,资源请求到后,客户端的脚本并不能操纵这份数据,只能用来呈现。但是很多时候,我们都需要发起请求到其他站点动态获取数据,并将获取到底数据进行进一步的处理,这也就是跨域访问的需求。
2.2.1 JSONP跨域原理
利用浏览器的Referer方式加载脚本到客户端的方式。以:
<script type="text/javascript" src="http://api.com/jsexample.js"></script>
这种方式获取并加载其他站点的JS脚本是被允许的,加载过来的脚本中如果有定义的函数或者接口,可以在本地使用,这也是我们用得最多的脚本加载方式。但是这个加载到本地脚本是不能被修改和处理的,只能是引用。而跨域访问需要正是访问远端抓取到的数据。那么能否反过来,本地写好一个数据处理函数,让请求服务端帮助完成调用过程?JS脚本允许这样。
<script type="text/javascript">
var localHandler = function(data){
alert('我是本地函数,可以被跨域的remote.js文件调用,远程js带来的数据是:' + data.result);
};
</script>
<script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
端的服务器上面定义的remote.js是这样的:
localHandler({"result":"我是远程js带来的数据"});
面首先在本地定义了一个函数localHandler,然后远端返回的JS的内容是调用这个函数,返回到浏览器端执行。同时在JS内容中将客户端需要的数据返回,这样数据就被传输到了浏览器端,浏览器端只需要修改处理方法即可。
这里有一些限制:
- 客户端脚本和服务端需要一些配合;
- 调用的数据必须是json格式的,否则客户端脚本无法处理;
- 只能给被引用的服务端网址发送get请求。
<script type="text/javascript">
var localHandler = function(data)
{
alert('我是本地函数,可以被跨域的remote.js文件调用,远程js带来的数据是:' + data.result);
};
</script>
<script type="text/javascript" src="http://remoteserver.com/remote.php?callBack=localHandler"></script>
务端的PHP函数可能是这样的:
<?php
$data = ".......";
callback = _GET['callback'];
echo callback.'('.json_encode(data).')';
exit;
?>
这样即可根据客户端指定的回调拼装调用过程。
2.2.2 CORS跨域原理
上述的JSONP由于有诸多限制,已经无法满足各种灵活的跨域访问请求。现在浏览器支持一种新的跨域访问机制,基于服务端控制访问权限的方式。简而言之,浏览器不再一味禁止跨域访问,而是需要检查目的站点返回的消息的头域,要检查该响应是否允许当前站点访问。通过HTTP头域的方式来通知浏览器:
Response headers[edit]
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Expose-Headers
Access-Control-Max-Age
Access-Control-Allow-Methods
Access-Control-Allow-Headers
服务端利用这几个HTTP头域通知浏览器该资源的访问权限信息。在访问资源前,浏览器会先发出OPTIONS请求,获取这些权限信息,并比对当前站点的脚本是否有权限,然后再将实际的脚本的数据请求发出。发现权限不允许,则不会发出请求。逻辑流程图为:
浏览器也可以直接将GET请求发出,数据和权限同时到达浏览器端,但是数据是否交给脚本处理需要浏览器检查权限对比后作出决定。
一次具体的跨域访问的流程为:
因此权限控制交给了服务端,服务端一般也会提供对资源的CORS的配置。
跨域访问还有其他几种方式:本站服务端代理、跨子域时使用修改域标识等方法,但是应用场景的限制更多。目前绝大多数的跨域访问都由JSONP和CORS这两类方式组成。
3 四种跨域方法
3.1 JSONP
首先要介绍的跨域方法必然是 JSONP。
现在你想要获取其他网站上的 JavaScript 脚本,你非常高兴的使用 XMLHttpRequest 对象来获取。但是浏览器一点儿也不配合你,无情的弹出了下面的错误信息:
XMLHttpRequest cannot load http://x.com/main.dat. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://y.com' is therefore not allowed access.
为了避免这种蛋疼的事情发生,JSONP 就派上用场了。你心里肯定会想,我难道要用后台做个爬虫来获取这个数据吗?!
<script> 标签是不受同源策略的限制的,它可以载入任意地方的 JavaScript 文件,而并不要求同源。
所以 JSONP 的理念就是,我和服务端约定好一个函数名,当我请求文件的时候,服务端返回一段 JavaScript。这段 JavaScript 调用了我们约定好的函数,并且将数据当做参数传入。
非常巧合的一点(其实并不是),JSON 的数据格式和 JavaScript 语言里对象的格式正好相同。所以在我们约定的函数里面可以直接使用这个对象。
你需要获取数据的页面 index.html:
<script>
function getWeather(data) { console.log(data); }
</script>
<script src="http://x.y.com/xx.js">
http://x.y.com/xx.js 文件内容:
JavaScript
getWeather({
"城市": "北京", "天气": "大雾"
});
我们可以看到,在我们定义了 getWeather(data) 这个函数后,直接载入了 xx.js。
在这个脚本中,执行了 getWeather 函数,并传入了一个对象。然后我们在这个函数中将这个对象输出到 console 中。
这就是整个 JSONP 的流程。
3.2 document.domain
使用条件:
- 有其他页面 window 对象的引用。
- 二级域名相同。
- 协议相同。
- 端口相同。
document.domain 默认的值是整个域名,所以即使两个域名的二级域名一样,那么他们的document.domain 也不一样。
使用方法就是将符合上述条件页面的 document.domain 设置为同样的二级域名。这样我们就可以使用其他页面的 window 对象引用做我们想做的任何事情了。
补充知识:
- x.one.example.com 和 y.one.example.com 可以将 document.domain 设置为 one.example.com,也可以设置为 example.com。
- document.domain 只能设置为当前域名的一个后缀,并且包括二级域名或以上(.edu.cn 这种整个算顶级域名)。
我们直接操刀演示,用两个网站 http://wenku.baidu.com/ 和 百度知道 – 全球领先中文互动问答平台。
这两个网站都是 http 协议,端口都是 80, 且二级域名都是 baidu.com。
打开 http://wenku.baidu.com/,在 console 中输入代码:
JavaScript
document.domain = 'baidu.com';
var otherWindow = window.open('http://zhidao.baidu.com/');
我们现在已经发现百度知道的网页已经打开了,在百度知道网页的 console 中输入以下代码:
JavaScript
document.domain = 'baidu.com';
现在回到百度文库的网页,我们就可以使用百度知道网页的 window 对象来操作百度知道的网页了。例如:
JavaScript
var divs = otherWindow.document.getElementsByTagName('div');
上面这个例子的使用方法并不常见,但是非常详细的说明了这种方法的原理。
这种方法主要用在控制 <iframe> 的情况中。
比如我的页面(http://one.example.com/index.html)中内嵌了一个 <iframe> :
<iframe id="iframe" src="http://two.example.com/iframe.html"></iframe>
我们在 iframe.html 中使用 JavaScript 将 document.domain 设置好,也就是 example.com。
在 index.html 执行以下脚本:
JavaScript
var iframe = document.getElementById('iframe');
document.domain = 'example.com';
iframe.contentDocument; // 框架的 document 对象
iframe.contentWindow; // 框架的 window 对象
这样,我们就可以获得对框架的完全控制权了。
补充知识(绝对干货):
当两个页面不做任何处理,但是使用了框架或者 window.open() 得到了某个页面的 window 对象的引用,我们可以直接访问的属性有哪些?
方法 |
---|
window.blur |
window.close |
window.focus |
window.postMessage |
window.location.replace |
属性 | 权限 |
---|---|
window.closed | 只读 |
window.frames | 只读 |
window.length | 只读 |
window.location.href | 只写 |
window.opener | 只读 |
window.parent | 只读 |
window.self | 只读 |
window.top | 只读 |
window.window | 只读 |
3.3 window.name
我们来看以下一个场景:
随意打开一个页面,输入以下代码:
JavaScript
dow.name = "My window's name";
location.href = "http://www.qq.com/";
再检测 window.name :
JavaScript
window.name; // My window's name
可以看到,如果在一个标签里面跳转网页的话,我们的 window.name 是不会改变的。
基于这个思想,我们可以在某个页面设置好 window.name 的值,然后跳转到另外一个页面。在这个页面中就可以获取到我们刚刚设置的 了。
由于安全原因,浏览器始终会保持 window.name 是 string 类型。
这个方法也可以应用到与 <iframe> 的交互上来。
我的页面(http://one.example.com/index.html)中内嵌了一个 <iframe> :
<iframe id="iframe" src="http://omg.com/iframe.html"></iframe>
在 iframe.html 中设置好了 window.name 为我们要传递的字符串。
我们在 index.html 中写了下面的代码:
JavaScript
var iframe = document.getElementById('iframe');
var data = '';
iframe.onload = function() {
data = iframe.contentWindow.name;
};
定睛一看,为毛线报错?
细心的读者们肯定已经发现了,两个页面完全不同源啊!
由于 window.name 不随着 URL 的跳转而改变,所以我们使用一个暗黑技术来解决这个问题:
JavaScript
var iframe = document.getElementById('iframe');
var data = '';
iframe.onload = function() {
iframe.onload = function(){ data = iframe.contentWindow.name; } iframe.src = 'about:blank';
};
或者将里面的 about:blank 替换成某个同源页面(最好是空页面,减少加载时间)。
补充知识:
about:blank , javascript: 和 data: 中的内容,继承了载入他们的页面的源。
这种方法与 document.domain 方法相比,放宽了域名后缀要相同的限制,可以从任意页面获取 string 类型的数据。
3.4 [HTML5]postMessage
在 HTML5 中, window 对象增加了一个非常有用的方法:
JavaScript
windowObj.postMessage(message, targetOrigin);
- windowObj : 接受消息的 Window 对象。
- message : 在最新的浏览器中可以是对象。
- targetOrigin : 目标的源,* 表示任意。
这个方法非常强大,无视协议,端口,域名的不同。下面是烤熟的栗子:
JavaScript
var windowObj = window; // 可以是其他的 Window 对象的引用
var data = null;
addEventListener('message', function(e){if(e.origin == 'http://jasonkid.github.io/fezone') { data = e.data; e.source.postMessage('Got it!', '*'); }
});
message 事件就是用来接收 postMessage 发送过来的请求的。函数参数的属性有以下几个:
- origin : 发送消息的 window 的源。
- data : 数据。
- source : 发送消息的 Window 对象。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。