同源策略
什么是同源
在了解什么是同源之前,我们得先知道什么是源?
我们在任意一个网站输入 window.origin 或 location.origin 就可以得到当前网站的源。
由上可知:源 = 协议 + 域名 + 端口号
现在我们已经知道了源,那么 同源 就如同字面意思一样:即当两个url的协议、域名、端口号完全一致,那么这两个url就是同源。
例如:https://www.juejin.cn 和 https://juejin.cn 就是不同源的,因为得完全一致才是同源。
什么是同源策略
同源的概念我们已经搞清楚了,那现在我们来了解一下什么是同源策略?
浏览器规定: 如果 JS 运行在源A里,那么就只能获取源A的数据。不能获取源B的数据,即不允许跨域。
例如:假设 https://baidu.com/index.html 引用了 https://cdn.com/1.js,那我们就说 1.js 运行在源https://baidu.com里。所以 1.js 就只能获取https://baidu.com上面的数据。(注意:这和 https://cdn.com 没有关系,虽然 1.js 是从它这里下载的。)
注意:这是浏览器的功能,浏览器故意这样设计的!
浏览器为什么要设置同源策略
这时不禁有人疑惑,为什么浏览器要这样设置呢?当然是为了保护用户隐私!
我们举一个例子:
- 假设没有同源策略,我们登录(cookie)并访问qq空间
https://user.qzone.qq.com/ - 然后AJAX请求
/friends.js获取好友列表 - 目前为止都是正常的。
- 此时有人给你分享了一个
https://qzone-qq.com/的网站,实际上这是一个钓鱼网站, - 当你点开时,这个网页也会请求你的好友列
https://user.qzone.qq.com/friends.js。 - 请问,这个时候你的好友列表是不是就被悄悄的偷走了?
问题的根源是什么?无法区分发送者
- qq空间页面里面的 JS 和 钓鱼网站里面的 js 发送的请求几乎没有区别。
- 唯一的区别是
Referer,如果后台开发者没有检查Referer,那么就几乎没有区别。 - 所以,如果没有同源策略,任何页面都能偷QQ空间的数据了。
那检查一下 Referer 不就好了?
- 安全原则: 安全链条的强度取决于最弱的一环。
- 万一后端开发忘记检查就会出现问题
- 所以浏览器应该主动预防这种偷数据的行为
- 所以,浏览器为了用户隐私,设置了严格的同源策略。
虽然同源策略限制了不同源的页面之间的直接交互,但在实际开发中,可以通过跨域技术实现不同源的页面之间的交互。常用的跨域技术有两种:JSONP和CORS。
CORS
因为有时候我们需要不同源之间的网站相互访问,所以浏览器给出一个方法,就是如果要共享数据,就要提前声明。
那怎么声明呢?
对于普通请求:我们可以在服务端响应头中添加Access-Control-Allow-Origin:跨域。
例如,假设a网站需要访问b网站的数据,那么就在b网站后台设置
if(path === '/friends.json'){
response.statusCode = 200
response.setHeader('Content-Type', 'text/json;charset=utf-8')
response.setHeader('Access-Control-Allow-Origin:http://a.com:9090')
// 如果要允许多个网站,我们可以通过referer获取,然后写入
// request.headers['referer']
}
对于复杂请求:得分两步实现
假设是PATCH请求,我们需要
- 先响应OPTIONS请求,在响应头中添加如下的响应头
Access-Control-Allow-Origin: https://
Access-Control-Allow-Methods: POST, GET, PATCH, OPTIONS
Access-Control-Allow-Headers: Content-Type
- 响应 PATCH 请求,在响应头中添加
Access-Control-Allow-Origin:头
如果要附带身份信息: JS中需要在AJAX里设置xhr.withCredentials = true
CORS的缺点
IE 6,7,8不支持,IE可以使用JSONP
JSONP
背景:
- 没有 CORS, 怎么跨域?
- 虽然我们不能访问.json,但是我们可以引用.js啊
- 然后我们让JS包含数据就好了。
那我们怎么把数据写到 JS 文件里面呢?假设a网站需要访问b网站的数据
// b网站 friends.js
// 写一个占位符,用来存在json的数据
{{data}}
// 但是这样写获取到的是字符串,我们需要JSON数据
// 所以我们要这样写
window.xxx ({{data}})
if(path === '/friends.js){
response.statusCode = 200
response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
// 读取js内容,然后放到字符串中,之后再将json数据放进去
const string = fs.readFileSync('./friends.js).toString();
const data = fs.readFileSync('./friends.json).toString();
const string2 = string.replace('{{data}}', data);
// 然后我们将获取到的数据写到响应里面
response.write(string2);
response.end();
}
// a网站 friends.js
const script = document.createElement('script')
scirpt.src = 'https://b.com/friends.js'
document.body.appendChild(script)
此时,我们就能在a网站通过window.xxx访问b中friends.json的数据了。
那我们怎么知道我们成功获取到了数据呢?我们可以提前先声明一下
// a网站 friends.js
window.xxx = (data) =>{
console.log(data)
}
const script = document.createElement('script')
scirpt.src = 'https://b.com/friends.js'
document.body.appendChild(script)
我们在 a 网站声明了window.xxx 但是没有调用,我们等b站点的一个脚本js文件来调用xxx,它调用的时候就会把它的第一个参数传给a,这就是一个跨网站的回调。
上面就是基本的JSONP实现方式。
- a站点利用script标签可以跨域的特性,向b站点发送get请求。
- b站点后端改造 JS 文件的内容,将数据传回回调函数。
- a站点通过回调函数拿到b站点的数据。
但是我们通过这种方式,所有人都可以访问了。为了防止 CSRF 攻击,乙站点可以在返回的数据中加入一些随机生成的 token,并在响应头中设置 Access-Control-Allow-Origin 和 Access-Control-Allow-Credentials 等 CORS 相关的头信息,限制只有带有正确 token 的请求才能访问数据。并且因为使用的是<script>标签,所以不支持POST,只能发get请求。
总结
- 同源策略就是浏览器故意设计的一个功能限制,就是不同源的页面之间,不准互相访问数据。
- 只要在浏览器里面打开页面,就默认遵守同源策略。
- 优点是保证用户的隐私安全和数据安全。
- 缺点是很多时候,前端需要访问另一个域名的后端接口,会被浏览器阻止其获取响应。 比如甲站点通过AJAX访问乙站点的/money 查询余额接口,请求会发出,但是响应会被浏览器屏蔽。 解决方法就是跨域。
- CORS通过在服务端设置响应头'Access-Control-Allow-Origin'来允许跨域访问
- 复杂请求需要设置options响应头和复杂请求头
- 缺点是不兼容ie
- JSONP是通过动态创建
<script>标签来实现跨域。 - 缺点是不支持POST,只能发get请求。