同源策略#
定义#
什么是同源策略?
同源策略(Same Origin Policy)是一种约定,它是浏览器最核心也最基本的安全功能。
如果缺少了同源策略,则浏览器的正常功能可能会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

同源即是指协议、域名、与端口都相同,
不同源的客户端脚本(JavaScript、ActionScript)在没明确授权的情况下,不能读写对方的资源。
目的#
为了保证用户信息的安全,防止恶意的网站窃取数据
限制行为#
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM 无法获得
- AJAX 请求不能发送
跨域#
定义#
跨域,顾名思义,即是跨越、突破浏览器的同源限制,不同域之间相互请求资源
产生#
因为有同源策略的限制,一般情况下,不同域之间是无法通信。但是有时候我们不得不突破同源策略的限制,在不同的域之间进行资源请求。
一个简单的例子:
我的一个服务器(服务器 A)上空间容量有限且不能扩充,在上面存放了一些资源后,想要再存放一些图片已经放不下去了,没方法只好把图片放在另外一个服务器上(服务器 B)了。某个时候因为某个原因,服务器 A 的一个网页需要引用一张图片,但是这个图片在服务器 B 上,怎么办呢?因为两个服务器的协议,IP 地址都是不一样的,根据浏览器的同源策略限制,它们之间是无法访问的呀?
怎么办呢?这个时候跨域通信就产生了。
如果经常浏览网页,我们会发现大部分网站上页面和图片是分开放在不同的服务器上的,然后进行跨域请求资源。
大家还记得吗? HTML 中有个 src 属性就是支持跨域通信的,一些标签比如 img,script 都有这个属性, 可以跨域引用资源。
另外,link 标签的 href 属性也支持跨域。
<img src="xxx" />
<script src="xxx"></script>
<link rel="stylesheet" href="xxx" />跨域打破了浏览器的同源策略,可能会产生一些安全的问题。所以,所有的跨域请求都必须经过信息提供方允许才能进行,如果未经允许就可跨域获取资源,那么一定是同源策略出现了漏洞。
跨域通信#
理解了同源策略和跨域后,下面讲一下跨域通信的几种常用方法。
JSONP#
定义
JSONP 利用 script 标签的 src 属性可以跨域的特性,动态创建一个 script 标签,
标签引用跨域服务器的一段 js 代码,这段代码是一个回调函数,回调函数里面的参数放入所需的数据或者资源,
因为回调函数的名字和本地的函数名字一样,所以引用这段代码后就直接调用了本地的函数,输出内容。
代码示例
本地代码文件:local.html
<script>
var localHandler = function (data) {
alert(data);
};
</script>
<script scr="跨域服务器/remote.js"></script>跨域服务器代码文件:remote.js
localHandler(`哈哈,我是参数也是你需要的data!`);上面是一个最简单的 jsonp 代码示例了。也许有人会问:一般服务器的数据会有很多的跨域访问,
怎么让远程服务器知道你要调用的本地函数名称是什么呀?其实 JSONP 跨域请求一般是需要传一个 callback 函数给服务器的。
场景实例
实例场景:李明同学想要查询期末考试的成绩,他的学号是 030206。
本地代码文件:local.html
<script>
// 创建本地函数
var searchScore = function (data) {
alert(
data.class +
data.name +
"同学的成绩是:语文:" +
data.score.chinese +
"; 数学:" +
data.score.math +
"。"
);
};
// 需要提供服务的url,传入id和callback函数
var url = "跨域服务器/score.php?id=030206&callback=searchScore";
// 新建一个script标签
var script = document.createElement("script");
// 设置script标签的src值为url
script.setAttribute("src", url);
// 把新建的script标签添加到在head标签上
document.getElementsByTagName("head")[0].appendChild(script);
</script>上面代码相当于在 head 标签下面生成了一个 script 标签
<head>
<script src="跨域服务器/score.php?id=030205&callback=searchScore"></script>
</head>服务器会根据这个 url,就会返回一段 js 代码,代码如下:
searchScore({
class: "三年级二班",
name: "李明",
score: {
chinese: 80,
math: 85,
},
});然后这段代码就会执行上一个 script 代码中的searchScore()这个函数,页面弹出警告框:
三年级二班李明同学的成绩是:语文:80。数学:85。
至于服务器的输出是怎么实现的呢,这里说下原理。
服务器接受到 id 和 callback 后,会先通过 id 查询到这个同学的成绩,然后用把查询到的成绩和 callback 函数名(回调函数)拼接成一段 js 代码传出去。服务器实现的功能就是查询和拼接字符串代码,然后把代码return出去。
CORS#
CORS 全称是"跨域资源共享"(Cross-origin resource sharing),是一个 W3C 的标准。它允许XMLHttpRequest跨域请求,从而克服了 AJAX 只能同源使用的限制。
CORS 只需要在服务器端设置即可,客户端不需要设置。客户端只需要支持 CORS 这个功能就可以了,目前 IE10 以上的浏览器和现代主流浏览器都支持。
服务器代码如下, 不同后端语言写法可能不一样:
// 必需,值若为 * 而为支持所有的域名请求,不建议这样写
response.setHeader("Access-Control-Allow-Origin", "http://a.com, http://b.com");
// 如果浏览器有Access-Control-Request-Headers字段,则该字段必需
response.setHeader("Access-Control-Allow-Header", "X-Requested-With");
// 必需,支持请求的方法
response.setHeader(
"Access-Control-Allow-Methods",
"PUT,POST,GET,DELETE,OPTIONS"
);
// 可选,接受跨域的cookie
response.setHeader("Access-Control-Allow-Credentials", "true");
// ... 其他设置PS:django 可以下载一个第三方库django-cors-headers,然后配置下就可以轻松提供跨域服务了。
WebSocket#
WebSocket 是一种通信协议,该协议不实行同源策略,只要服务器支持就可以实现跨域通信。
浏览器发出的 WebSocket 请求头信息中,会带有一个 Origin 字段,表示该请求的请求源。
Origin: http://www.example.com正是因为有了这个字段,WebSocket 才不实行同源策略。服务器会根据这个字段,判断是否允许本次通信。如果服务器的白名单中有这个域名,就可以实现跨域通信。
postMessage#
HTML5 新增 postMessage 方法,postMessage 可以实现跨文档消息传输。
兼容性:IE8 以上浏览器和现在的主流浏览器都支持。
postMessage 方法是通过绑定 window 的 message 事件来监听发送跨文档消息传输内容。
代码示例:
// 窗口A(http://A.com)向跨域的窗口B(http://B.com)发送信息
BWindow.postMessage("data", "http://B.com");// 在窗口B中监听message事件
AWindow.addEventListener(
"message",
function (event) {
console.log(event.origin); // http://A.com 消息源的URI
console.log(event.source); // AWindow 消息源
console.log(event.data); // data 数据
},
false
);如果是窗口 B 向窗口 A 发送消息呢,反过来原理也是一样的。
总结#
跨域通信用得最多的就是 JSONP 和 CORS 了,JSONP 只支持 GET 请求,CORS 允许任何类型的请求。
不过 JSONP 的支持会更好一些,几乎所有的浏览器都支持。