这篇文章主要讨论了跨域的概念、同源策略及其限制,以及如何处理跨域问题。文章首先解释了什么是跨域,然后介绍了同源策略及其对Cookie、LocalStorage、IndexedDB等存储性内容、DOM节点和AJAX请求的限制。最后,文章详细介绍了CORS(跨源资源共享)的原理和优缺点,并提供了PHP代码示例,展示了如何在服务器端添加CORS头部和验证Referer以防止CSRF攻击。
一、什么是跨域
url的组成
JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象。那什么是跨域呢,简单地理解就是因为JavaScript同源策略的限制,a.com
域名下的js无法操作b.com
或是c.a.com
域名下的对象。
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。
例如:http://www.abc.com/index.html
请求 http://www.efg.com/service.php
。
有一点必须要注意:跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。
大家可以参照下图,有助于深入理解跨域。
特别说明两点:
第一:如果是协议和端口造成的跨域问题“前台”是无能为力的。
第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”。
二、什么是同源策略及其限制
同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。它的存在可以保护用户隐私信息,防止身份伪造等(读取Cookie)。
同源策略限制内容有:
Cookie、LocalStorage、IndexedDB 等存储性内容
DOM 节点
AJAX 请求不能发送
但是有三个标签是允许跨域加载资源:
1.<img src=XXX>
2.<link href=XXX>
3.<script src=XXX>
接下来我们讨论下有哪些处理跨域的方法。但所有的跨域都必须经过信息提供方的允许。如果未经允许即可获取,那是浏览器同源策略出现漏洞。
处理跨域方法——CORS
1.CORS原理
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
2.CORS优缺点
CORS要求浏览器(>IE10)和服务器的同时支持,是跨域的根本解决方法,由浏览器自动完成。 优点在于功能更加强大支持各种HTTP Method,缺点是兼容性不如JSONP。 只需要在服务器端做一些小小的改造即可:
/**
* 验证Refer,允许跨域访问
* @param mixed $whiteHostList 白名单域名或ip
*/
public static function addCrosHeader($whiteHostList = array())
{
if (!isset($_SERVER['HTTP_REFERER'])) {
return;
}
$origin = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST);
if (in_array($origin, $whiteHostList)) {
header("Access-Control-Allow-Origin: "."https://".$origin);
// header("Access-Control-Allow-Origin: "."http://".$origin.":8081");
header('Access-Control-Allow-Credentials: true'); // 跨域cookie生效必备
header('Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS');
header('Access-Control-Allow-Headers: X-Requested-With,X_Requested_With,Content-Type');
header('P3P: CP="CAO PSA OUR"');
}
return;
}
验证Refer防止Refer攻击
/**
* 验证Refer,防范csrf攻击
* @param mixed $whiteHostList 白名单域名或ip
* @return boolean true/false
*/
public static function checkReferer($whiteHostList = array())
{
$referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : false;
if (empty($referer)) {
return false;
}
// referer 必须以 http 或 https 开头
if (strpos($referer, 'http://') !== 0 && strpos($referer, 'https://') !== 0) {
return false;
}
// 设置默认域名
if (empty($whiteHostList)) {
$whiteHostList = array(
$_SERVER['HTTP_HOST']
);
} elseif (is_string($whiteHostList)) {
$whiteHostList = array(
$whiteHostList
);
}
// refer 主机地址判断
$refererHost = parse_url($referer, PHP_URL_HOST);
if (is_array($whiteHostList) && in_array($refererHost, $whiteHostList)) {
return true;
} else {
return false;
}
}