web 开发中经常会遇到 ajax 请求无法发送,cookie 无法读取,资源无法加载等问题,这背后很可能是受到了浏览器同源策略的限制。在此梳理一下浏览器的同源安全策略,为解决问题提供便利。
同源的定义
一个完整的 url 地址,例如 http://www.site.com:8080/home/index.html?q=123#home
由以下几部分组成:
- 协议(protocol):
http:
- 域名(domain):
www.site.com
- 端口(port):
8080
- 路径(pathname):
/home/index.html
- 查询(search):
?q=123
- 锚点(hash):
#home
这里有几点要注意:
- 域名分为一级域名,二级域名,三级域名等,例如
www.site.com
是一个二级域名,它对应的一级域名是site.com
,也说site.com
是www.site.com
的父域。同理web.www.site.com
是一个三级域名,www.site.com
是它的父域,site.com
也是它的父域名。 - 端口号大部分时候会省略,这时使用协议默认的端口号,例如
http
是 80,https
是 443。 - 路劲也有父子级关系,例如
/
是/home
的父路径,若省略路径则表示访问根路径/
。
协议 + 域名 + 端口 就组成了这个 url 的源(origin),例如上面的 url 的源是 http://www.site.com:8080
,同源就是指两个 url 的源相同。下面是一些比较的例子:
http://www.site.com
https://www.site.com #不同源,协议不同
https://site.com #不同源 协议不同,域名不同
http://59.60.61.62 #不同源 域名不同
http://www.site.com:8080 #不同源 端口不同
http://www.site.com/home/index.html?a=123#title #同源
同源策略对 iframe 的影响
iframe 为 web 提供了很多方便和灵活,但也最可能带来安全问题。因此在 iframe 的访问上浏览器严格执行同源检查,即只能访问与自己同源的 iframe 的内容。
这样会给 iframe 之间通信带来麻烦,但也有一些方法可以实现 iframe 之间的通信。在同父域名的情况下可以通过用 js 设置 domain 为父域名来允许相互访问访问。
<!-- http://www.site.com/index.html -->
<html>
<head>
<script>
document.domain = 'site.com';
</script>
</head>
<body>
<iframe src="http://sub.site.com/iframe.html"></iframe>
</body>
</html>
<!-- http://sub.site.com/iframe.html -->
<html>
<head>
<script>
document.domain = 'site.com';
</script>
</head>
<body></body>
</html>
父域名也不相同的情况下只能通过 window.postMessage 通信,信息收发时最好验证一下源,不然可能被钓鱼网站利用。
<!-- http://www.site.com/index2.html -->
<html>
<head>
</head>
<body>
<iframe id="iframe" src="http://sub.site.com/iframe.html"></iframe>
<script>
var iframe = document.getElementById('iframe').contentWindow;
setTimeout(function() {
// 给不同源窗口发送消息,第二个参数为要验证的源,只有发给窗口的源是设置的值时才会发送
// 不想验证时可以设置为 * 但有很大的安全风险
iframe.postMessage({data: 'some data'}, 'http://sub.site.com');
}, 1000);
</script>
</body>
</html>
<!-- http://sub.site.com/iframe2.html -->
<html>
<head>
</head>
<body>
<script>
// 接收消息,注意读取消息时最好验证一下消息发送者的源以保证消息可信
window.addEventListener('message',function(event) {
if (event.origin == 'http://www.site.com') { // 验证消息发送者的源来保证消息可信
console.log(event.data);
}
})
</script>
</body>
</html>
同源策略对 ajax 的影响
ajax 请求同样进行严格的同源检查,即 ajax 只能请求与当前页面同源的地址。这里要注意一下 js 文件引入到页面之后就相当于写在页面上了,即引入的 js 文件中的所有 ajax 请求都按照当前页的源来进行同源检测,而不是 js 文件的 src 地址。
当然也有方法让页面请求非同源的地址,一种方式是在拥有服务端权限时通过服务端设置 http 头来允许非同源的访问。
<!-- http://www.site.com/ajax.html -->
<html>
<head>
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
</head>
<body>
<script>
$.get('http://sub.site.com/ajax.php', function(data) {
console.log(data);
})
</script>
</body>
</html>
<?php
# http://sub.site.com/ajax.php
// 可接受的 ajax 非同源请求,可配多个用空格分开,也可以使用 * 表示接受所有,当使用 * 时 ajax 请求无法带上 cookie
header('Access-Control-Allow-Origin:http://www.site.com');
echo 'ajax data';
>
注意之前提到的通过 js 设置 document.domain
的方法只对 iframe 有效,对于 ajax 请求只会按照 url 来进行检测。即无法通过设置 document.domain='site.com'
来让 http://sub.site.com
ajax 访问 http://site.com
。
同源策略对 cookie 的影响
cookie 访问同样也有限制,但遵循的是另一套规则。在设置 cookie 时可以置顶 cookie 的域名(domain)和路径(pathname),域名只能设置为当前域或者当前域的父域,路径也只能设置为当前路径或者当前路径的父路径。读取时只能读取域名与当前域名相同或为当前域名父域,并且路径与当前路径相同或为当前路径父路径的 cookie。
<!-- http://www.site.com/cookie.html -->
<html>
<head>
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.js"></script>
</head>
<body>
<script>
// jq 设置 cookie,默认设置为当前域名和当前路径
$.cookie("example", "foo");
// 将 cookie 设置为一级域名和根路径以得到最大可访问性
$.cookie("example2", "foo", 'site.com', {path: '/', domain: 'site.com'});
// 只能读取域名与当前域名相同或为当前域名父域,并且路径与当前路径相同或为当前路径父路径的 cookie
$.cookie("example2");
</script>
</body>
</html>
注意同源的 ajax 请求会带上 cookie,但通过之前提到的服务端设置 http 头来允许非同源 ajax 请求时,请求默认是不带 cookie 的,要带 cookie 还需要一些配置:
<!-- http://www.site.com/ajax.html -->
<html>
<head>
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
</head>
<body>
<script>
// jquery 设置 ajax 跨域请求带上 cookie
$.ajaxSetup({crossDomain: true, xhrFields: {withCredentials: true}});
$.get('http://sub.site.com/ajax.php', function(data) {
console.log(data);
})
</script>
</body>
</html>
<?php
# http://sub.site.com/ajax.php
// 可接受的 ajax 非同源请求,可配多个用空格分开,也可以使用 * 表示接受所有,当使用 * 时 ajax 请求不可以带上 cookie
header('Access-Control-Allow-Origin:http://www.site.com');
// 接受 ajax 非同源请求带 cookie
// 前端对应要配置 xmlhttp.withCredentials = true; 才可实现 ajax 带cookie
// cookie 的携带规则与页面访问 cookie 规则相同
header('Access-Control-Allow-Credentials:true');
echo 'ajax data';
>
同源策略的其他影响
-
通过 src 属性引入资源例如图片,js,css 等不受同源策略的限制。但有一点,https 协议的页面不能加载 src 为 http 的资源,为了兼容可以在写 src 地址时不写具体协议而用
//
来匹配当前页协议。例如<script src="//www.site.com/index.js"></script>
。 -
引入不同源的 js 时,使用 window.onerror 无法捕获到其产生错误的具体信息,只会得到
script error
。