在 Web 开发中,我们经常会遇到跨域请求的问题。跨域的问题,解决方案有很多:代理请求、domain 设置、flash方式、jsonp方式、Access-Control-Allow-Origin。其中,jsonp 的方式最为通用,使用起来也比较简单:通过 JavaScript 回调的方式进行数据跨域传送。
jsonp 方式解决跨域问题XHTML
<script src="http://www.other-domain.cn/api?callback=callback"></script>
在外部接口中(本例 http://www.other-domain.com/api )中,动态输出 Javascript 调用代码,以 PHP 代码为例:
数据接口代码PHP
代码如下 | 复制代码 |
<?php $data = 'data'; $callback = isset($_GET['callback']) ? $_GET['callback'] : 'callback'; echo "$callback('$data');"; ?> |
这样,通过在本地页面中实现 callback 函数即可读取到数据。
JSONP方式获取跨域数据XHTML
代码如下 | 复制代码 |
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Demo</title> </head> <body> <script type="text/javascript"> function callback(data){ alert('获取的数据为: ' + data); } </script> <script type="text/javascript" src="http://www.other-domain.com/api?callback=callback"></script> </body> </html> |
如果传输的数据比较敏感,不想被未经授权的网站读取,通常的做法是在 api 中做请求的 Referer 校验。如果 Referer 不为空且不是被授权的域名,则拒绝请求。此时,api 的代码类似于:
拒绝来自不受信任网站的请求PHP
代码如下 | 复制代码 |
<?php if (isset($_SERVER['HTTP_REFERER'])) { $url_info = parse_url($_SERVER['HTTP_REFERER']); if($url_info['host'] !== 'www.allowed-domain.com') { header("HTTP/1.1 404 Not Found"); exit; } } $data = 'data'; $callback = isset($_GET['callback']) ? $_GET['callback'] : 'callback'; echo "$callback('$data');"; ?> |
如果没有被授权,这时候调用 api 就会被拒绝。在上面的例子中,相应就是 404。
但是,对于被调用方,这样真的就安全了吗?api 的数据真的不能被未授权网站读取了吗?
答案是:否。
分析上面的 api 接口代码可知,当请求数据时,不发送 HTTP Referer 信息,则可以正常读取数据。
那么,如何加载 JS 而不发送 HTTP Referer 呢?
通常情况下,我们通过 script 标签加载 JS 文件,浏览器都将向 JS 所在服务器发送 Referer 信息:
XHTML
代码如下 | 复制代码 |
GET /api.php?callback=callback HTTP/1.1 Host: www.other-domain.com Connection: keep-alive Cache-Control: max-age=0 Accept: */* Referer: http://www.fising.cn/demo/test.html Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4,zh-TW;q=0.2 |
但是,通过某种手段,可以使得浏览器加载 JS 文件时,不发送 HTTP Referer。
方法一:HTTPS 页面请求 HTTP JS
当请求页面与被请求资源的 URL Scheme 不同时,不会发送 HTTP Referer。例如,打开淘宝网的登录页面(https://login.taobao.com/member/login.jhtml), 发现其会加载一个来自HTTP服务器的图片资源,这时候就不会发送Referer信息:
HTTP请求头XHTML
代码如下 | 复制代码 |
GET /imgextra/i2/10039155/TB2UQNJapXXXXaXXpXXXXXXXXXX_!!10039155-0-matrixgapp.jpg HTTP/1.1 Host: img02.taobaocdn.com Connection: keep-alive User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36 Accept: */* Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4,zh-TW;q=0.2 If-Modified-Since: Thu, 04 Sep 2014 06:33:17 GMT |
而这个页面加载的其他来自于 HTTPS 服务器的图片时,则会发送 Referer 信息。
方法二:利用 RFC2397
rfc 2397 定义了一种 URL 格式: data:[<mediatype>][;base64],<data>
有点眼熟不是吗?我们利用base64编码的方式加载图片,正是利用了这点。同样,我们可以利用这个来加载 JS 文件,而不发送 Referer 信息:
利用RFC 2397定义的URL格式发送请求XHTML
代码如下 | 复制代码 |
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Demo</title> </head> <body> <iframe src="data:text/html;charset=utf-8,<script type='text/javascript'>function callback(data){alert('获取的数据为: ' + data);}</script><script src='http://www.other-domain.com/api?callback=callback'></script>"> </body> </html> |
可以抓包看看,这个时加载 api 文件,并没有发送 Referer 信息。注意,需要指定 charset ,否则可能会出现乱码问题。
方法三:iframe 标签 src 属性 Hack 法
iframe src hack法XHTML
代码如下 | 复制代码 |
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Demo</title> </head> <body> <iframe src="javascript:'<script>function callback(data){alert('获取的数据为: ' + data);}</script><script src='http://www.other-domain.cn/api?callback=callback'></script>'"></iframe> </body> </html> |
@