同源策略和跨域

145 阅读6分钟

同源策略

什么是同源

在了解什么是同源之前,我们得先知道什么是

我们在任意一个网站输入 window.originlocation.origin 就可以得到当前网站的源。

image.png

由上可知:源 = 协议 + 域名 + 端口号


现在我们已经知道了源,那么 同源 就如同字面意思一样:即当两个url的协议、域名、端口号完全一致,那么这两个url就是同源。

例如:https://www.juejin.cnhttps://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请求,我们需要

  1. 先响应OPTIONS请求,在响应头中添加如下的响应头
Access-Control-Allow-Origin: https://
Access-Control-Allow-Methods: POST, GET, PATCH, OPTIONS
Access-Control-Allow-Headers: Content-Type
  1. 响应 PATCH 请求,在响应头中添加Access-Control-Allow-Origin:

如果要附带身份信息: JS中需要在AJAX里设置xhr.withCredentials = true

什么是简单请求? 跨源资源共享(CORS) - HTTP | MDN (mozilla.org)

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请求。