http: //zt.jd.com:80/cgi-bin/popuser_menu?tag=4#eduTop
这是一个普通的URL,格式:protocol :// hostname[:port] / path / [?query]#fragment
关系对应以下图:html
在浏览器里有一个策略--同源策略--即只有同源的文件之间才能互相通讯,不然就会被浏览器拒绝。以下图,在www.jd.com下面的页面想请求zt.jd.com页面时是被拒绝的。web
那什么是同源,同源就是相同的来源,只有这些资源都来自相同的地方,它们之间才能通讯,不然就可能存在安全和隐私问题。一般状况下,同源也叫同域,由于判断同源的方式就是比对“协议+域名+端口“是否一致。ajax
protocol: 常见的有http, https, ftp等协议跨域
host: 如zt.jd.com,,其中com是顶级域名,jd是主域名,zt是子域名。所以zt.jd.com和www.jd.com是不一样源的,m.zt.jd.com和zt.jd.com也是不一样源的。浏览器
port: 80, 8080等,url不加端口号时默认为80端口,所以对于http ://zt.jd.com和http ://zt.jd.com:80是同源的,而很明显http ://zt.jd.com和http ://zt.jd.com:8080则是不一样源。安全
对于http ://store.company.com/dir/page.html这个URL来讲,MDN上有这样的描述,基本能够很清晰的说明什么是同源了:服务器
对于XMLHttpRequest对象,是不支持跨域操做的,你没办法在www.jd.com下经过ajax去调用zt.jd.com下的接口,浏览器会把请求给拒绝掉。
那浏览器究竟是拒绝发请求呢?仍是拒绝接受响应呢?咱们看一下network:cookie
很明显请求是发出去的,并且响应了200 ok。只是响应内容被浏览器屏蔽了,js也没法读取到。fiddler里面能够看到响应的内容:框架
那为何浏览器不直接把请求丢掉,而是在响应的时候屏蔽掉呢?由于原则上来说若是服务器容许,客户端没理由阻止跨域,所以客户端必须确认服务器并无容许当前域的跨域访问时才能丢掉这个请求,这就涉及到CORS。
在XMLHttpRequest2里,能够经过服务器设置Access-control-allow-origin响应头部字段来告诉浏览器容许ajax跨域。以下图,在zt.jd.com下用ajax调用xoa.pp.jd.com下的接口,成功跨域调用。dom
但毕竟是HTML5的新特性,浏览器端的兼容性并很差,所以,目前不多使用这种方式。更多的使用JSONP。
咱们知道在HTML标签里,有一些特殊的标签是能够跨域的,好比script, link, img之类的,这些标签能够提供给咱们主动调用第三方资源的能力,方便web开发。因为调用资源的主动权在你本身手里,所以只要你调用本身可信任的资源,不会有什么安全性问题。JSONP就是利用script标签的跨域功能来实现跨域拉取数据的。 咱们来看script标签的特殊之处在哪里。 一般状况下,script标签是这样用的:
还有这样用的:
也就是说script既能够拉取数据,也能够执行代码。那假如,咱们用它来拉取一个跨域的接口,咱们怎样才能把接口返回的数据拿来用呢?很明显,那就是让接口在返回数据的同时,还要返回操做这些数据的js代码。好比:
可是这种方式存在两个弊端:依赖问题和阻塞问题。你必须确保那个拉取数据的script标签是先执行了,然后面的代码必须等它执行完才能执行。那么,就使用异步回调呗。以下:
先定义一个回调函数,而后把拉取数据的script标签改成异步拉取。这样拉取成功后会调用定义好的回调函数来进行数据处理。这样就不用理会依赖问题,script标签能够不考虑前后顺序,由于异步必须在同步代码完成后才执行。可是,这样还存在一个问题,那就是接口通用性受到了限制,由于必须返回一句写死的renderData(data)代码,拉取这个接口的页面老是必须定义一个叫作renderData的函数。因而,聪明的人又想到了解决办法,那就是用传参的方式告诉接口给我返回什么回调函数,这样我想定义一个renderData1也行,renderData2也行,接口的通用性就大大提升了。以下:
接口读出参数callback,并把它返回便可。这样我传给接口render1它就返回render1,render2就返回render2。
这就是JSONP,通常会封装成一个相似于ajax的函数,直接传参便可动态建立script标签实现跨域请求。可是,这里有必要说明一点,JSONP接口经过CSRF很容易被恶意拉取到敏感信息,你能够构造页面让目标用户打开,由于callback的存在,你能将拉取到的用户信息上传到本身的服务器,所以需作好防范。
iframe是一个特殊的标签,能够在页面里面加载别的页面。
既然有加载别的页面的能力,那在非同源下一定是要受到同源策略限制的,不然,咱们就能随便读取任意网站的cookie了。
非同源状况下,默认是阻止父框架与子框架之间的通讯的,但能够分为两种状况:
子框架加载的URL是一个第三方页面时:你能作的仅仅就是把它展示出来,而没有任何能力去访问它。
子框架加载的URL是你本身可控的页面,这时候能够分两种状况:
主域相同,子域不一样:能够经过在iframe和父页面里都设置document.domain=主域名来实现跨域通讯。
主域不一样:这种状况只能经过一些奇淫异巧来实现简单的通讯。有两种状况:
父框架向子框架通讯:父框架经过改变iframe的src的hash值,iframe监听自身location.hash便可获取到父框架设置的参数,从而实现信息的传递。但数据量收到url长度的限制。
子框架向父框架通讯:你们或许会想那直接逆向一下设置location.hash不就好了吗。事实上是不行的,在iframe里设置location.hash是没法更新到iframe的src值的。所以,还得再嵌入一个跟父框架同源的iframe到子框架里,而后经过“父框架向子框架通讯”的方式让子框架传递信息给“孙框架",而后父框架再去读”孙框架“就好了。
同源状况下,无需设置便可双向通讯。可是,假如父页面和子页面任何一方设置了document.domain,都会致使浏览器的跨域拦截,即便document.domain就是当前的domain也不行。也就是说,要么都不设置,要么都设置。