源由下面三个部分组成:javascript
两个 URL ,只有这三个都相同的状况下,才能够称为同源。php
下来就以 "http://www.example.com/page.html" 这个连接来比较说明:html
对比URL | 结果 | 缘由 |
---|---|---|
http://m.example.com/page.html | 不一样源 | 域名不一样 |
https://www.example.com/page.html | 不一样源 | 协议不一样 |
http://www.example.com:8080/page.html | 不一样源 | 端口不一样 |
http://www.example.com/page3.html | 同源 | 同域名,同端口,同协议 |
浏览器的同源策略是一种安全功能,同源策略限制了从同一个源加载的文档或脚本如何与来自另外一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。因此a.com下的js脚本采用ajax读取b.com里面的文件数据是会报错的。前端
对于浏览器来讲,除了DOM、Cookie、XMLHttpRequest 会受到同源策略的限制外,浏览器加载的一些第三方插件也有各自的同源策略。最多见的一些插件如 Flash ,有本身的控制策略。html5
因此,想要体验下,同源策略限制,你就能够写一个ajax 请求,好比127.0.0.1:80 要请求127.0.0.1:8080 的 a.js ;
127.0.0.1:80 里的index.htmljava
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <h1>另外一个页面</h1> </body> <script> var xhr = new XMLHttpRequest(); xhr.open('get','http://127.0.0.1:8080/index.js'); xhr.send(null); xhr.onreadystatechange = function(){ if(xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){ alert(xhr.responseText); } } </script> </html>
而后就会报错了,出现了同源策略限制了。
web
说的跨域,其实呢就是跨源。而跨域是一个统称,经过上面的咱们知道了,由于同源策略,不一样源之间,不能进行交互。那么跨域就是解决不一样源之间发起请求、请求数据、发送数据、通讯等交互问题解决方法的统称。ajax
在浏览器中,<script>
、<img>
、<iframe>
、<link>
、<video>
等标签均可以跨域加载资源,而不受同源策略的限制,经过 src 属性加载的资源,浏览器都会发起一个 GET 请求,可是浏览器限制了 JavaScript 的权限,使用js不能读、写加载的内容。json
这句话什么意思呢,其实就是,你能够经过这几个标签来跨域加载资源,可是,发起的GET请求 返回的数据,经过 js 获取不到。后端
注意:经过 <script>
标签获取 js 文件里的全局属性,方法等,能够经过 js 读取到。是由于这些都是挂载在 window对象上的,看下面:
127.0.0.1 index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="app"></div> <script type="text/javascript" src="http://127.0.0.1:8080/index.js"></script> <script type="text/javascript"> window.onload = function(){ say(); } </script> </body> </html>
127.0.0.1:8080 index.js
function say(){ var app = document.getElementById('app'); app.innerHTML = "我是被挂载到window对象上的方法,因此能够获取到我!"; }
到底什么是jsonp 跨域呢?其实,jsonp 跟 json 二者没有什么关系,也没有什么类似的地方,你们都知道json 是一种数据格式,而jsonp 之因此被称为jsonp,我认为跟它发出请求后,通常获得的,都是json格式数据有关吧。
上面说过了,<script>
、<img>
、<iframe>
、<link>
、<video>
这些标签均可以发起跨域请求,其中的 <script>
标签都熟悉吧,常常用来加载 js 文件。jsonp就是利用了这个标签。
不知道你们有没有疑问啊,既然这些标签都能发起跨域请求,那么为啥只用 <script>
标签能够请求到数据呢?其实呢,关键就在于,<script>
再请求获得数据后,遇到js代码,就会解析执行。理解这个也不难,你在js文件里写的代码,确定是要被执行的。
好比127.0.0.1
里的index.html
页面加载了一个 <script src="index.js"></script>
:
function say(){ console.log("666"); } say();
当打开127.0.0.1/index.html
页面时,<script>
标签发起了一个对index.js 的 GET 请求,获得数据后,js引擎开始解析执行,而后say
方法就被执行了,这时,控制台就会输出 "666";
那么jsonp就是利用了这点了。先来写一个jsonp实例吧。
127.0.0.1 jsonp.html
:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <h1>JSONP</h1> </body> <script > function say(data){ alert(data); } </script> <script src="http://127.0.0.1:8080/index.php?callback=say"></script> </html>
而后是 127.0.0.1:8080 index.php
文件:
<?php $data = array( 'name' => 'zdx', 'sex' => 'man', 'age' => 18 ); $callback = $_GET['callback']; echo $callback . '(' . json_encode($data) . ')'; ?>
当访问jsonp.html
时,其中的<script>
发起一个请求,并发送了一个名为callback
参数,值为字符串"say"
。而后index.php
把传进来的 say
和要发送的 data
进行字符串拼接,json_encode
函数就是把 数据转成json 格式的。而后这个请求就返回了:say({"name":"zdx","sex":"man","age":18})
;而后 <script>
获得这个数据后,就会解析执行 say
函数了。
因此明白了吧,jsonp 是须要后端 支持的,须要配套使用,而后关于jsonp 是存在安全风险的,传过来的数据直接执行,那么只要改掉同名的函数,那么想怎么操做数据均可以了。还能够修改参数值,对传到服务器的数据进行修改,从而攻击服务器。
注意:此方法只能发起GET请求,经过jsonp发送的请求,会随带 cookie 一块儿发送。
CORS(Cross-Origin Resource Sharing,跨源资源共享)定义了在必须访问跨源资源时,浏览器与服务器应该如何沟通。CORS 背后的基本思想,就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,仍是应该失败。
注意:此方法IE8如下彻底不支持,IE8-10部分支持。
这须要服务器 和前端配合, 或者 后端 和 前端配合。
能够看看阮老师的:跨域资源共享 CORS 详解
这里以 php 为例,只需在须要被请求的 php 文件里加上一个响应头部 header('Access-Control-Allow-Origin:http://127.0.0.1')
,后面的域名就是容许请求的域名。这里就是表示容许来自http://127.0.0.1
全部的请求。
127.0.0.1:8080 index.php
:
<?php header('Access-Control-Allow-Origin:http://127.0.0.1'); echo "我是CORS跨域过来的!"; ?>
而后就是前端了。IE10及以上、Firefox 3.5+、Safari 4+、Chrome、iOS版 Safari和 Android平台中的 WebKit都经过 XMLHttpRequest 对象实现了对 CORS 的原生支持。
127.0.0.1:80 index.html
:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <h1>另外一个页面</h1> </body> <script> var xhr = new XMLHttpRequest(); xhr.open('get','http://127.0.0.1:8080/index.php'); xhr.send(null); xhr.onreadystatechange = function(){ if(xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300 || xhr.status == 304){ alert(xhr.responseText); } } </script> </html>
而IE8 - IE9是经过XDR对象实现 CORS 的。
基于XDR的 index.html
代码以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>XDR对象实现CORS</title> </head> <body> <h1>XDR对象实现CORS</h1> <script> var xdr = new XDomainRequest(); xdr.onload = function(){ console.log(xdr.responseText); }; xdr.open("get","http:127.0.0.1:8080/index.php"); xdr.send(null); </script> </body> </html>
注意:CORS能够发起 GET、POST请求,可是发送的请求,默认不会随带 cookie 一块儿发送, 也不会接受后端发过来的 cookie;
要想随带cookie 一块儿发送。
须要在127.0.0.1:8080 index.php
添加 header('Access-Control-Allow-Credentials:true');
头部,而后在127.0.0.1:80 index.html
中var xhr = new XMLHttpRequest();
后面添加xhr.withCredentials = true;
同源策略认为域和子域属于不一样的域,如:
child1.a.com 与 a.com,
child1.a.com 与 child2.a.com,
xxx.child1.a.com 与 child1.a.com
两两不一样源,能够经过设置 document.domain='a.com',浏览器就会认为它们都是同一个源。想要实现以上任意两个页面之间的通讯,两个页面必须都设置documen.damain='a.com'。
此方式的特色:
下面来模拟一下,在a.com 与 child1.a.com 之间通讯。若是要在本机测试,请自行更改host 等,访问的都是本机80端口,这里就不在累述了。
a.com index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <h1>主页面</h1> <script> document.domain = 'a.com'; </script> <iframe src="http://child1.a.com/index1.html" frameborder="0"></iframe> </body> </html>
child1.a.com index.php
<?php echo "我是document.domain 降域过来的!"; ?>
child1.a.com index1.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <h1>child</h1> <script> document.domain = 'a.com'; var xhr = new XMLHttpRequest(); xhr.open('get','http://child1.a.com/index.php'); xhr.send(null); xhr.onreadystatechange = function(){ if(xhr.readyState == 4 && xhr.status >= 200 && xhr.status <= 300 || xhr.status == 304){ alert(xhr.responseText); } } </script> </body> </html>
注意:此方法能够发起 GET、POST 请求,发起的请求不会随带 cookie 一块儿发送,也不能接受后端发过来的 cookie
这是html5 新加的方法。
这个方法容许一个页面的脚本发送数据到另外一个页面的脚本中,无论脚本是否跨域。在一个window对象上调用postMessage()会异步的触发window上的onmessage事件,而后触发定义好的事件处理方法。一个页面上的脚本仍然不能直接访问另一个页面上的方法或者变量,可是他们能够安全的经过消息传递技术交流。
好比说父页面为127.0.0.1:80 的页面,传送数据给 127.0.0.1:8080 的子页面:
127.0.0.1:80 index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <h1>父页面</h1> <iframe id="iframe" src="http://127.0.0.1:8080/ty/index6.html" frameborder="0"></iframe> </body> <script> window.onload = function(){ var wd = document.getElementById('iframe').contentWindow; wd.postMessage('我是经过postMessage方法过来的!','http://127.0.0.1:8080'); } </script> </html>
127.0.0.1:8080 index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <h1>子页面</h1> </body> <script> window.addEventListener("message", receiveMessage, false); function receiveMessage(event) { alert(event.data) } </script> </html>
而后访问:127.0.0.1:80/index.html
,就获得想要的结果了,这方法一般用来进行两个窗口通讯。
现代浏览器容许脚本直连一个WebSocket地址而无论同源策略。然而,使用WebSocket URI的时候,在请求中插入Origin头就能够标识脚本请求的源。为了确保跨站安全,WebSocket服务器必须根据容许接受请求的白名单中的源列表比较头数据。
这个由于须要后端的支持,并且比较复杂,这里就不举例子了,感兴趣的能够去查阅资料。
这里贴一个阮老师的websocket教程吧:WebSocket 教程
window对象有一个name属性,该属性有一个特征:即在一个窗口的生命周期内,窗口载入的全部的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久的存在于一个窗口载入的全部页面中的,并不会由于新的页面的载入而被重置。
所以,就能够利用此特性,进行跨域通讯。
127.0.0.1:80 index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body id="data"> <h1>window.name</h1> </body> <script type="text/javascript"> window.name = "我是document.name过来的数据。" location.href = "http://127.0.0.1:8080/ty/index8.html"; </script> </html>
127.0.0.1:8080 index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script type="text/javascript"> alert(window.name) </script> </body> </html>
这时,访问127.0.0.1:80/index.html
,跳转到的127.0.0.1:8080/index.html
就能接受传过来的数据了。
原理是利用location.hash来进行传值。在url: http://a.com#helloword中的‘#helloworld’就是location.hash。
127.0.0.1:80 index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body id="data"> <h1>window.name</h1> </body> <script type="text/javascript"> location.hash = "我是document.name过来的数据。" location.href = "http://127.0.0.1:8080/index.html" + location.hash; </script> </html>
127.0.0.1:8080 index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script type="text/javascript"> alert(decodeURIComponent(location.hash.slice(1))); </script> </body> </html>
这时,访问127.0.0.1:80/index.html
,跳转到的127.0.0.1:8080/index.html
就能接受传过来的数据了。
这个彻底是后端的实现,我就不说了,我也搞不懂,也没意义。哈哈。
这里说的仍是皮毛,这些跨域方案只是工具,怎么利用,就看你了。