同源政策
"同源政策"是浏览器安全的基石。
含义
它最初的含义是指,A网页设置的 Cookie,B网页不能打开,除非这两个网页"同源"。
“同源”的概念
"同源"指的是"三个相同":协议相同 、域名相同、端口相同
http://www.example.com/dir/page.html这个网址,协议是http://,域名是www.example.com,端口是80(默认端口可以省略)。
它与http://example.com/dir/other.html不同源,其域名不同(只是一级域名相同)。
同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。
限制范围
如果非同源,共有三种行为受到限制。
- Cookie、LocalStorage 和 IndexDB 无法读取。
- DOM 无法获得。
- AJAX 请求不能发送。
同源政策的规避
Cookie
服务器在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。
Set-Cookie: key=value; domain=.example.com; path=/
这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。
提醒:
前端最好不要随意去设置cookie,因为cookie信息量太大会影响到请求和响应的速度,所以最好统一由后端去设置。
iframe
如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。
父窗口运行下面的命令,如果iframe窗口不是同源,就会报错。
document.getElementById("myIFrame").contentWindow.document`
// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.
反之亦然,子窗口获取主窗口的DOM也会报错。
window.parent.document.body
// 报错
对于完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题。
- 片段识别符(fragment identifier)
- window.name
- 跨文档通信API(Cross-document messaging)
AJAX
同源政策规定,AJAX请求只能发给同源的网址,否则就报错。
除了架设服务器代理,有三种方法规避这个限制。
- JSONP
- WebSocket
- CORS
JSONP
JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
它的基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
首先,网页动态插入<script>元素,由它向跨源网址发出请求。
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
addScriptTag('http://example.com/ip?callback=foo');
}
function foo(data) {
console.log('Your public IP address is: ' + data.ip);
};
上面代码通过动态添加<script>元素,向服务器example.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的。
服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。
foo({
"ip": "8.8.8.8"
});
由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。
WebSocket
WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
下面是一个例子,浏览器发出的WebSocket请求的头信息
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
上面代码中,有一个字段是Origin
,表示该请求的请求源(origin),即发自哪个域名。服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出回应。
CORS
什么是 CORS:Cross-Origin Resource Sharing(跨域资源共享)
比如,a.com的前端向b.com的服务器发请求
request.open('GET', 'http://b.com:8002/ada')
会报错了,因为不是同源,响应完成却得不到任何内容
解决方法,在Node代码中加入一句
//允许 http://ada.com:8001 访问我
response.setHeader('Access-Control-Allow-Origin','http://ada.com:8001')
这就是用CORS实现AJAX跨域的过程