http: //zt.jd.com:80/cgi-bin/popuser_menu?tag=4#eduTop
这是一个普通的URL,格式:protocol :// hostname[:port] / path / [?query]#fragment
关系对应如下图:
在浏览器里有一个策略--同源策略--即只有同源的文件之间才能互相通信,否则就会被浏览器拒绝。如下图,在www.jd.com下面的页面想请求zt.jd.com页面时是被拒绝的。
同源策略
那什么是同源,同源就是相同的来源,只有这些资源都来自相同的地方,它们之间才能通信,否则就可能存在安全和隐私问题。通常情况下,同源也叫同域,因为判断同源的方式就是比对“协议+域名+端口“是否一致。
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上有这样的描述,基本可以很清晰的说明什么是同源了:
Ajax跨域
对于XMLHttpRequest对象,是不支持跨域操作的,你没办法在www.jd.com下通过ajax去调用zt.jd.com下的接口,浏览器会把请求给拒绝掉。
那浏览器到底是拒绝发请求呢?还是拒绝接受响应呢?我们看一下network:
很明显请求是发出去的,而且响应了200 ok。只是响应内容被浏览器屏蔽了,js也无法读取到。fiddler里面可以看到响应的内容:
那为什么浏览器不直接把请求丢掉,而是在响应的时候屏蔽掉呢?因为原则上来讲如果服务器允许,客户端没理由阻止跨域,因此客户端必须确认服务器并没有允许当前域的跨域访问时才能丢掉这个请求,这就涉及到CORS。
在XMLHttpRequest2里,可以通过服务器设置Access-control-allow-origin响应头部字段来告诉浏览器允许ajax跨域。如下图,在zt.jd.com下用ajax调用xoa.pp.jd.com下的接口,成功跨域调用。
但毕竟是HTML5的新特性,浏览器端的兼容性并不好,因此,目前很少使用这种方式。更多的使用JSONP。
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
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也不行。也就是说,要么都不设置,要么都设置。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。