资讯专栏INFORMATION COLUMN

同源策略及跨域访问

tomato / 3105人阅读

摘要:同源策略及跨域访问同源策略同源策略约束了两个域之间资源的加载方式,是一个很重要的安全机制用来隔离那些有潜在安全隐患的文档。

同源策略及跨域访问 同源策略

同源策略(Same-origin policy)约束了两个域之间资源的加载方式,是一个很重要的安全机制用来隔离那些有潜在安全隐患的文档。

何为源(orgin)

一个源由一个URL的协议(protocol)、主机(host)和端口(port)进行定义。如果两个页面拥有相同的协议、主机和端口一致的话,我们就可以称它们为同源。下面的表格比较了不同的URL跟http://store.company.com/dir/page.html这个URL的同源情况:

URL 是否同源 理由
http://store.company.com/dir2...
http://store.company.com/dir/...
https://store.company.com/sec... 协议不同
http://store.company.com:81/d... 端口不同
http://news.company.com/dir/o... 主机不同
跨域访问

同源策略控制了两个源之间的交互,例如你使用XMLHttpRequest发起一个请求,或者使用元素加载一张图片,就会产生两个源之间的交互。而当两个源不相同时,有些交互会被允许,而有些则不被允许。而不允许的情况,就是我们常说的跨域访问问题。那什么情况下不同源的交互会被允许,什么情况下又不被允许呢?大致可以分为如下的情况:

链接、跳转和表单提交这些在跨域的情况下都是被允许的。例如调用支付宝接口进行支付就是典型的跨域表单提交的场景,这种跨域的调用是被允许的。但是这个很容易被利用来进行CSRF攻击,所以我们的表单提交需要做好这方面的防护。

跨域的资源内嵌是被允许的。下面是一些资源内容的例子:

使用加载Javascript。只有同源的脚本在语法错误时会显示错误信息。

使用加载CSS。跨源的CSS文件要求使用正确的Content-Type 响应头。

使用加载图片。

使用加载媒体文件。

使用 加载插件。

使用 @font-face加载字体。有些浏览器允许加载跨域的字体,有些则不允许。

使用

然后在http://sub.example.com/iframe.html页面对父页面的背景色进行修改:




    ......


    

由于两个页面不同源,所以子页面对父页面的操作被禁止,例如在Firefox上你会看到以下的报错:

Error: Permission denied to access property "document"

不同源之间的XMLHttpRequest调用(也就是我们常说ajax调用)是不被允许的,这个也是我们最常遇到的跨域访问场景。例如我们在http://example.com/index.html页面进行如下的ajax调用:

var xhr = new XMLHttpRequest();
var url = "http://otherexample.com/api/get-data";
   
xhr.open("GET", url, true);
xhr.onreadystatechange = handler;
xhr.send();

在Firefox下会报如下错误:

已拦截跨源请求:同源策略禁止读取位于 http://otherexample.com/api/get-data 的远程资源。(原因:CORS 头缺少 "Access-Control-Allow-Origin")。
跨域访问的解决方案

修改源

一个页面的源是可以修改的,修改的方法很简单,就是通过Javascript脚本设置document.domain的值。举个例子,我们在页面http://store.company.com/dir/other.html执行下面的代码:

document.domain = "company.com";

那么这个页面的域将会由store.company.com变成company.com,后面在判断是否同源的时候,主机将会使用company.com这个值,而不是store.company.com

那是不是修改域后就能跟同域的页面进行交互呢。答案是否定的。例如,如果你在页面中通过iframe嵌入http://company.com/dir/page.html这个页面,然后通过javascript去跟这个页面交互,你会发现浏览器会报错,就跟我们之前那个例子一样。按照上面的源的定义,这时候两个页面应该是同源的,为什么呢?因为修改document.domain会导致端口号被设为null。所以另外一个页面也需要把document.domain修改为相同的值,这样两个页面的主机和端口就一致了,可以进行互相的访问了。

既然源可以修改,那么是不是就解决了我们的跨域问题呢?明显没这么简单。首先,这种方法是有很大限制条件的,document.domain这个值只能修改为这个页面的当前域或者当前域的超级域。所以,这个方法只能解决同一超级域下的页面跨域问题。其次,它的使用场景也很有限,因为它需要页面执行Javascript脚本,所以也就是说一般只能应用于页面跟页面的交互,例如访问ifame页面或者window.open打开的页面等等。所以如果你想用来解决ajax之类的跨域调用,这个方法就无能为力了。

使用代理

使用代理也是解决跨域访问的一个方法。上面修改document.domain的方法只能用来访问子域名的页面,无法访问不同域的页面,而使用代理则没有这个问题。

例如我们有一个页面http://example.com/,需要访问http://otherexample.com/这个页面,我们不直接对这个页面进行访问,而是通过请求另外一个同源的页面,这个页面在后端通过代理服务器把请求转发到http://otherexample.com/,获取数据并返回给客户端。

另外,这个方法同样可以用于解决ajax的跨域访问问题。

JSONP

JSONP也被经常用来解决ajax的跨域调用问题。JSONP请求并不是通过XMLHttpRequest发起,而是使用

然后接口获取到callback函数名后,把原来返回的数据作为函数的参数,最终返回如下的Javascript:

myFunction({"id": "123", "name": "Captain Jack Sparrow"});

然后myFunction就会执行,达到了调用的目的。

这个方法在大多数情况下都很有用,但是它也有它的局限。一是它需要后端的配合,因为后端的接口需要根据约定的参数获取回调函数名,然后跟返回数据进行拼接,最后进行响应。二是它只能进行异步的调用,因为它的原理是通过动态生成

阅读需要支付1元查看
<