前言
浏览器限制一个源的文档(document)或它加载的脚本(script)如何与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。但是这样也造成问题:跨域。在前后端分离的项目中,前端发送请求给后台服务器,后台服务器接收到请求,处理完响应给前端。但是由于浏览器的同源策略(Same-origin policy),该响应被拦截了。
什么是同源
要了解同源,先来看看 URL 是怎么组成的:
protocol://[host]:[port][path][?query][anchor]
e.g.
http://www.zsxcool.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument
其中:
protocol 为协议,这里为 http
host 为域名,这里为 www.zsxcool.com。其中 zsxcool.com 为主域名,www.zsxcool.com 为子域名。
port 为端口,这里为 80
path 为资源路径,这里为 /path/to/myfile.html
query 为查询字符串,这里为 ?key1=value1&key2=value2
anchor 为描点,这里为 #SomewhereInTheDocument
在 URL 中,只要 protocol, host 和 path 相同,则认定为同源。
例如,一个 URL 为 http://store.zsxcool.com/dir/page.html 下面列出对该源的比较:
URL | 结果 | 原因 |
---|---|---|
http://store.zsxcool.com/dir2/other.html | 同源 | 只有路径不同 |
http://store.zsxcool.com/dir/inner/another.html | 同源 | 只有路径不同 |
https://store.zsxcool.com/secure.html | 失败 | 协议不同 |
http://store.zsxcool.com:81/dir/etc.html | 失败 | 端口不同 ( http:// 默认端口是 80) |
http://news.zsxcool.com/dir/other.html | 失败 | 主机不同 |
http://zsxcool.com/dir/other.html | 失败 | 主机不同,store 为 zsxcool.com 的一个子域名。 |
同源策略
浏览器认为互联网是不可信的,于是它引入了同源策略,拒绝非同源的请求。
浏览器共限制三种行为:
- Cookie、LocalStorage 和 IndexDB 只能同源读取。 其中,Cookie 的同源策略有点不同。可以为父域(e.g. zsxcool.com)设置 Cookie,那样其子域(e.g. www.zsxcool.com, abc.zsxcool.com)都共享该 Cookie。当然也可以只为子域设置 Cookie,这样该 Cookie 只能子域访问。 这个比较好理解,自己源的数据只能自己读取。要是别人都能读取,那信息不久全部泄露了。
- DOM 只能同源获得。 也就是两个不同源的页面之间无法对方的
DOM
元素。如果该限制取消了,就会造成安全隐患。最经典就是iframe
的例子: 你在某处打开了这个网站https://www.bilibil.com/
,这个网站看起来和哔哩哔哩没有任何区别,但是仔细观察它并不是官方网站。黑客使用 iframe 加载真正的哔哩哔哩网站,并将iframe
布局调整全屏,看起来就和真正的哔哩哔哩没有任何区别。而你也没有察觉这样的问题,于是你就登录你的账号。如果没有DOM
同源策略,黑客就可以从https://www.bilibil.com/
网站直接抓取输入框的DOM
,从而拿到你的账号和密码。 - XMLHttpRequest 只能同源请求。 XMLHttpRequest 也就是我们常说的 AJAX。AJAX 的同源可以免受浏览器遭受到 CSRF 攻击(并不能抵挡全部的 CSRF 攻击)。
这里主要讨论同源策略中不同源之间的请求、交互限制。
同源策略中对不同源之间的交互限制,比如主要分为三类:
- 跨域写操作(Cross-origin writes) 一般是被允许的。例如,链接(links),重定向以及表单提交。
- 跨域资源嵌入(Cross-origin embedding) 一般是被允许。例如,一系列可以通过
src
引入资源的标签:script
、img
、video
、audio
等;通过@font-face
引入的字体;通过iframe
载入的任何资源。 - 跨域读操作(Cross-origin reads) 一般是不被允许的。例如,AJAX。
解决方案
解决跨域,一般就是解决不同源之间可以互相请求 AJAX,不同源之间可以互相获取 DOM 节点。
AJAX 解决方案
在说明解决方案之前,来看看浏览器是如何阻止 AJAX 跨域的。
- 我们在当前网站中请求一个非同域的 API。
- 浏览器就会给该服务器发送请求。
- 服务器接收到请求后,处理过后成功响应。
- 浏览器接受到这个响应,发现该请求为跨域响应,于是拒绝了该响应。
从上面得出结论,浏览器并不是不能发送跨域请求,而是拒绝了跨域响应。
这也是我们在请求非同源网站时,浏览器会返回 net::ERR_FAILED 并告诉我们并没有设置 Access-Control-Allow-Origin 这个头信息。
解决方案一:CORS
经过多年的发展,前后端分离已成为业界标杆。既然是前后端分离就要处理跨域问题。
我们知道,在服务器返回响应给浏览器后,浏览器是拒绝了该响应。所以 W3C 就推出一个标准:CORS
。它是通过设定一系列的响应头来解决跨域问题的。
最重要的响应头当然是 Access-Control-Allow-Origin
,它可以设置成 *
代表允许所有网站的请求跨域。也可以设置成 URI,只允许指定的网站请求跨域。
这里列举常用的响应头,更详细的内容可以查看 MDN 的文档:
Access-Control-Allow-Origin
请求资源能共享给那些域Access-Control-Allow-Credentials
是否允许发送 CookieAccess-Control-Allow-Methods
对预请求的响应中,指示实际的请求中可以使用哪些 HTTP 头。Access-Control-Max-Age
预请求结果能被缓存多久,单位为秒。-1 表示禁用缓存。
因为是响应,所以 CORS 的解决方案在后端进行。
对了,CORS 分为 简单请求 和 非简单请求 这里不过多赘述,详情请看文档
解决方案二:代理服务器
我们知道同源策略是浏览器上的限制,服务器之间是没有跨域这个说法的。
所以可以加个中间层来解决这个问题:
- 浏览器的请求发到同源代理服务器。
- 同源代理服务器收到请求后进行转发,转发到不同源的服务器。以此进行数据交互。
- 数据交互之后,先发送到同源代理服务器,再转发到浏览器。
整个过程浏览器只和同源代理服务器交流,故解决跨域。
解决方案三:JSONP
JSONP 可以堪称是跨域解决的奇淫技巧,因为它是利用 <script>
元素的「漏洞」来实现的。
从上面我们可以知道,<script>
标签是可以引用其他域的 js 脚本,该 js 脚本载入成功后会触发回调函数,该回调函数里面就有我们想要的数据。
使用 JSONP 的好处为浏览器兼容性好,但它只能支持 GET 请求。
解决方案四:WebSocket
WebSocket 和 HTTP 一样都是应用层的协议,与 HTTP 不一样的是,它并没有同源策略。只要服务器支持该协议,就可以实现跨域通信。
获取 DOM 解决方案
我们知道 iframe
和 window.open
打开的窗口,他们与父窗口之前无法通信。父窗口无法获取子窗口的 DOM,反之亦然。
document.domain
通过 document.domain
这个属性值,如果两个窗口的一级域名相同,则可以施行窗口之间的通信。同样可以通信的还有 Cookie 值。
片段标识符(fragment identifier)
片段标识符指的是 URL 后面 #
号后面的部分,也成为锚(anchor)。
如果只是改变片段标识符的内容,页面就不会重新刷新,从而实现跨域。
window.name
windows.name 是浏览器窗口的一个属性。该属性,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。
window.postMessage
上面两种方法都是利用的漏洞,HTML 5 引入跨文档通信 API(Cross-document messaging)来解决这个问题。
这个 API 为 window
对象新增了一个 window.postMessage
方法,允许跨窗口通信,不论这两个窗口是否同源。
它可以解决一下方面的问题:
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的 iframe 消息传递
- 上面三个场景的跨域数据传递
window.postMessage 这个方法非常强大,详情请参考 文档。
参考链接
- developer.mozilla.org/zh-CN/docs/W…
- developer.mozilla.org/zh-CN/docs/L…
- juejin.cn/post/6879360544323665928
- www.ruanyifeng.com/blog/2016/04/sa…
- developer.mozilla.org/zh-CN/docs/W…
- www.ruanyifeng.com/blog/2016/04/sa…
必须 注册 为本站用户, 登录 后才可以发表评论!