同源策略与跨域

同源策略

同源策略为JavaScript文件只能访问与其同源的资源和数据。同源策略浏览器最核心也是最基本的安全功能,其目的是为了防止恶意网站窃取数据。所谓同源是指协议、域名、端口都相同。IE关于同源不要求端口相同。

同源禁止的行为

考虑同源,要考虑三个部分:不同源的服务器、不同源的窗口和文档、不同源的存储数据。一般情况下,同源策略禁止以下行为:

  • 禁止JavaScript访问不同源的Cookie、LocalStorage以及IndexDB等存储数据。
  • 禁止读取和操作与JavaScript脚本不同源的窗口和文档的大多数属性。
  • 禁止XMLHttpRequest生成的HTTP请求与不同源的服务器通信。(如果允许,就可以在控制台写个ajax提交到任意的服务器了)

同源策略允许的行为

同源策略控制了不同源之间的交互,但是并非限制了所有不同源的交互。通常允许以下行为:

  • 通常允许跨域写操作,例如链接、重定向以及表单提交。特定少数的HTTP请求需要添加preflight。
  • 通常允许跨域资源嵌入。例如利用script、link、img、iframe、video、audio等标签嵌入不同域的资源(可以设置资源是否可以被其他域访问)。这种内嵌资源的获取和访问是由浏览器来完成的,所以是安全的。此外,通过< script>标签等引入的JavaScript、CSS、图像等静态资源也被认为是与包含它们的界面所在的域同源。
  • 通常不允许跨域读操作。但常可以通过内嵌资源进行读取访问,如调用内嵌脚本的方法(JSONP)。

防盗链

严格来说,同源策略用于JavaScript脚本,所以使用script、img等标签进行跨域资源嵌入是与同源策略无关的。那么别人也就可以在他们的页面上引用我们的服务器的图片了,但是如果我们不想给他们用呢?在请求头中,有个字段为referer,采用url的格式表示发送请求的网页或文件,我们可以在服务器代码中检查referer的值,来决定是否让这些网站使用。这就是防盗链的原理。

跨域

跨域利用一定的方法和技术,使JavaScript脚步可以访问获取原本不同源的服务器、窗口和文档、存储数据的内容。常用的方法有设置相同的域、跨域资源共享、JSONP、跨文档信息、WebSockets等。

变更源

同源限制的是不同源内容之间的访问,那么我们只需要将两个页面的源设成一致就可以相互访问了。
实现方法:通过JavaScript,将A页面和B页面的document.domain设置为相同的域名。
限制及适用范围:

  • 同个网页的一级域名需要相同。如http://store.company.com/dir/other.html的域名可以设置为`document.domain = “company.com”`,但是不能设置为othercompany.com。另外赋值时,会以null覆盖原来的端口号,所以赋值时要带上端口号
  • 此方法只适用于Cookie和窗口,LocalStorage和IndexDB无法通过此方法规避同源,而要使用PostMessage。
  • 也可以在服务器设置Cookie时,指定Cookie的所属域名为一级域名,这样只有一级域名相同,就可以访问该Cookie。

CORS跨域资源共享

跨域资源共享是一种网络浏览器的技术规范,它为Web服务器定义了一种方式,允许网页从不同域访问其资源。而这种访问是同源策略所禁止的。它定义了一种浏览器和服务器交互的方式来确定是否允许跨域请求。跨域资源共享的基本思想,是通过自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是否应该成功。
默认情况下,XHR对象只能访问与包含它的页面位于同一域中的资源,所以通过xhr.open()打开的是相对路径的url,如果浏览器支持CORS,则可以将url设置为指向其他域资源的绝对路径的url。将请求发送给服务器后,服务器会根据这个头部信息(Origin,说明请求的源)决定是否给予响应,可以接受的话,就在Access-Control-Allow-Origin头部中回发相同的源信息(如果是公共资源,可以回*),如果没有这个头部或者有这个头部但源信息不匹配,浏览器就会驳回请求。
与JSONP相比,CORS无疑更加先进:

  1. JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
  2. 使用CORS,开发者可以使用普通的xhr对象发起请求和获得数据,比起JSONP有更好的错误处理。

JSONP

JSONP是JSON with padding的简写,是应有JSON的一种方法。JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的JSON数据。JSONP是通过动态< script>元素来使用的,使用时可以为src属性指定一个跨域URL。
总的来说,JSONP是一种在客户端定义函数,在服务器端传入数据并传递到客户端调用该函数的一种方式。所以JSONP要求服务器和客户端的密切配合,并且只能实现GET请求。且数据格式要为可以通过script标签传递的JSONP格式。
实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//客户端
<body>
<div id="divCustomers"></div>
<script type="text/javascript">
function callbackFunction(result, methodName)
{

var html = '<ul>';
for(var i = 0; i < result.length; i++)
{
html += '<li>' + result[i] + '</li>';
}
html += '</ul>';
document.getElementById('divCustomers').innerHTML = html;
}
</script>

<script type="text/javascript" src="http://www.runoob.com/try/ajax/jsonp.php?jsoncallback=callbackFunction"></script>
</body>

//服务器端
<?php
header('Content-type: application/json');
//获取回调函数名
$jsoncallback = htmlspecialchars($_REQUEST ['jsoncallback']);
//json数据
$json_data = '["customername1","customername2"]';
//输出jsonp格式的数据
echo $jsoncallback . "(" . $json_data . ")";
?>

Web Sockets

Web Sockets的目标是在一个单独的持久连接上提供双工、双向通信。使用标准的HTTP服务器无法实现Web Sockets,只要支持这种协议的专门服务器才能正常工作。

1
2
3
4
5
6
var socket=new WebSocket("ws://www.example.com/server.php");
socket.send(JSON.stringify(message));
socket.onmessage=function(event){
var data = event.data;
//处理数据
}

window.postMessage

HTML5为解决文档间的通信问题,引入了一个全新的API,它为window对象新增了一个window.postMessage方法,允许跨域的窗口通信。
父窗口可以使用window.open()或iframe等打开一个不同源的子窗口,则父窗口和子窗口可以进行通信。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
//父窗口http://a.com脚本
var popup = window.open('http://b.com', 'title');
popup.postMessage('hello b', 'http://b.com');//向子窗体发送信息
window.addEventListener('message', function(e){ console.log(e); });

//子窗口http://b.com脚本
window.opener.postMessage('hello a', 'http://a.com');//向父窗体发送信息
window.addEventListener('message', function(event){
if(event.origin = "http://a.com"){
console.log(event.data);
}
});

总结

同源策略是限制JavaScript脚本访问不同源的资源的一种安全措施,是web安全的基石。所以script、img等标签不受同源策略的限制(因为其资源的请求是浏览器发起的,是安全的),但是可以通过防盗链技术(检查请求头的referer)来限制我们的资源被其他网页的使用的权限。
同源策略限制JavaScript脚本访问不同源的存储数据(Cookie、LocalStorage、IndexDB)、窗口和文档(其绝大部分属性)以及服务器资源(XMLHttpRequest发起的请求要同源),以防止恶意的网址获取用户的信息。但是,实现合理的跨域请求也是必须的。
为了使同一个一级域名下网站间进行相互访问,可以将其子网址的域名通过document.domain统一设为一级域名,子网站间就不再受同源限制了。为了使Ajax发起不同源的请求,可以使用跨域资源共享方法,设置Orign和Access-Control-Allow-Origin头部来指定该服务器信任的网站,也可以使用JSONP,但它有很大的限制。此外,也可使用WebSocket来实现客户端与服务器端的通信,它不实行同源策略。为了不同网页间进行信息传输,则可以使用postMessage方法来实现。