关于同源策略与跨域问题

同源策略是现代浏览器安全里一个非常重要的机制,它通过限制客户端脚本读写不同源网站资源的权限来保护用户的信息安全。但是随着应用的复杂程度越来越高以及开发者的需求逐渐增多,很多时候又需要通过数据的跨域传输实现一些功能。

常用的跨域实现方式有 JSONP、CORS 或者服务器代理等。JSONP 依靠的是一些特殊 HTML 标签如 <img><script><link> 等可以加载非同源资源的特性,毕竟很多时候外码需要依靠单独的 CDN 服务器来减少对这些静态资源的请求时间,而 CORS 可以用于跨域的 AJAX 请求,是解决跨域问题非常重要和常用的技术,相比 JSONP 它支持的功能要多很多,不过代价就是需要服务端配置支持,当然这个配置也不麻烦。

写这篇文章是因为之前对同源策略和跨域问题刚了解的时候有一点误解,搞清楚之后决定用个简单的示例整理一下。关于它们的必要性、原理和实现之类的已经有很多讲得非常清楚的文章,比如 阮一峰的网络日志 里有两篇专门介绍它们的博客(具体地址在文末参考资料处),我这里就直接从实际测试开始瞎扯了。

准备

为了方便进行测试,这里的跨域就直接用“协议不同”这一点来实现了,服务端由本地的 PHP 处理进程充当,地址是 http://localhost,客户端就直接使用 file://file/location

首先在客户端的 HTML 文件里写入一段发送请求的 JS 代码,<script> 标签自己加吧:

var request = new XMLHttpRequest();  
request.open('GET', 'http://localhost/cors.php?param=mydata');  
request.onload = function() {  
    console.log(this.response);
};
request.send();  

然后在本地服务器对应的根目录下建个 cors.php,内容如下:

echo 'return data';

$file = fopen('./demo.txt', 'a');
fwrite($file, $_GET['param']);  
fclose($file);  

发送请求

在浏览器用 file:// 协议访问客户端的 HTML 文件(其实就是直接用浏览器打开这个文件),然后 JS 代码被执行的时候请求就会被发送出去,看看这个时候的控制台情况:

Fail

发现它报了个错误,console.log() 也没有执行到,然后再看看请求信息:

Request

比正常请求多了个 Origin 的 Header,响应状态码是 200 OKGET 请求的参数也正常发出了,响应信息里也有内容:

Response

实际上,响应信息并没有被客户端或者说页面脚本获取到,这里是控制台给开发者的调试信息。接下来我们打开那个和 PHP 文件同文件夹的 demo.txt,会发现它记录着传过来的参数值 mydata

解决问题

要想让客户端能获取到返回值,只需要在 PHP 文件开头加上一行设置 Access-Control-Allow-Origin 的代码即可:

header('Access-Control-Allow-Origin: *');  

这个时候客户端重新发送请求,控制台就有了对返回值的记录:

Success

请求和响应以及参数值被写入文件这些还是和之前一样的。

误解

在我一开始了解到同源策略的时候,我联系到了 XSS 攻击,大部分情况下要传送一些数据的话,目的地都是攻击者自己的服务器,而这肯定是和实际浏览的页面不同源的。那既然有了同源策略,是不是就不用考虑 Cookie 等信息被这样获取到了呢。

显然我还是 Naive,我以为浏览器实现同源策略就是直接拦截掉跨域请求不给发送,但是从上面的实验中我们可以看到请求是发送出去了的,并且服务端可以像处理其它正常请求一样获取到所有的参数值。

简单整理

通过 CORS 实现的跨域需要服务端的支持,浏览器在检测到跨域请求的时候,对于简单请求会直接在头信息里加上 Origin 字段表示请求的来源,然后被访问的服务器通过这个值判断是否许可本次请求,并在返回的头信息中设置 Access-Control-Allow-Origin 等配置参数,一般它的值就是收到的 Origin 的值,不过一些 API 提供方也会把它设置为通配符 *,表示允许任何来源的请求。浏览器收到响应信息后再根据 Access-Control-Allow-Origin 字段的值决定是否允许页面获取返回的数据。

对跨域的限制是浏览器的行为,但要实现跨域需要相应服务端的配合。它限制的是向别的站点读写资源的权限,但是对发送请求并没有做这样的限制,数据还是可以直接发送到其它站点。毕竟这个机制下,站点要收到一份请求才能判断和决定是否允许访问相应的内容。

封面图片来自 Medium

参考资料

顺带提下与此相关的另一个安全规范,可以看这一篇:Content Security Policy 介绍