刚迈进前端大门的时候,就听说过一句话,浏览器不允许跨域是为了安全,所以,为啥允许跨域就会不安全?
自由开放是人们不变的追求,Web 世界也是这样,这很符合 Web 的理念,但是,如果是绝对自由的,任何资源都可以接入,可以下载其他站点的可执行脚本,让我们发散一下,这会发生什么现象?
这一天,我登录了我的银行页面,这时候,如果下面有一个恶意站点的小广告,我手一贱就那么点进去了,然后愉快的在银行页面输入了我的账号密码,注意,现在浏览器没有什么安全策略,绝对的自由,那么,这个恶意站点就可以修改银行站点的 DOM、样式等信息,并且可以植入 JS 脚本,劫持到了我的用户名和密码,读取银行站点的 cookie、indexDB 等数据,然后把这些东西都发送到了自己的服务器,这样,这个恶意网站就可以在我本人并不知情的情况下伪造一些转账请求。我的天,这不是要命?
所以,这种现象是万万不能允许存在的,如果浏览器没有安全策略,那我们的信息就相当于在操场上裸奔,毫无隐私可言,这样就引出了我们页面中最核心的安全策略:同源策略(Same-origin policy)
什么是同源策略?
三同:同协议、同域名、同端口
浏览器默认两个相同源之间是可以相互访问资源和 DOM 的,具体来说,同源策略主要体现在 DOM、web 数据和网络层面上。
DOM 层面
如果两个页面是同源关系,那么我们就可以在第二个页面中操作第一个页面,否则会报错
接下来,就是见证奇迹的时刻了:
第一个页面的 DOM 元素真的被我们"藏起来"了。
但是,如果我们两个页面是不同域的,就会报错
数据层面
同源策略限制了我们没有办法拿到非同源下面的 cokkie、IndexDB、localStorage 等数据
网络层面
XMLHttpRequest 在默认情况下,不允许访问跨域的资源。
简单点来说,就是同域下,你想怎么搞,就怎么搞,放飞自我,不受限制,但是想要在不同源之间瞎搞就只能使用点特殊手段了。兴奋的搓手手~
我们怎么解决跨域问题?
让我们看看有哪些手段能让我们打破跨域的带来的限制。
DOM 层面
在开发过程中,可能需要两个不同源页面的DOM 进行通信,所以浏览器引入了跨文档消息机制,可以通过 window.postMessage 的 JavaScript 的接口。
这里有几个点需要注意,postMessage 的语法是:
targetWindow.postMessage(message, targetOrigin, [transfer]);
第三个参数 targetOrigin 最好提供具体的 url 而不是 *,以防恶意的第三方窃取密码。如果不希望收到其他站点的信息,不要为 message 事件添加任何事件监听器,如果在迫不得已的情况下必须要接受来自其他站点的消息,那么请务必检验发送者的身份信息,这很重要。
第三方资源
第三方资源我们要解决的是给他的自由过了火,需要有所限制。
同源策略要让网页的所有资源都来自同一个源,这样所有资源必须部署在同一台服务器上,太不自由了,带来了太多限制,所以同源策略在页面引用开了个后门,可以对任意文件进行引用。
一开始浏览器都是支持外部引用资源文件的,不过这也带来了很多问题,容易受到 XSS 攻击。这时候。浏览器又引入了CSP(内容安全策略),CSP 的核心思想是让服务器来决定浏览器能够加载哪些资源,让服务器决定浏览器能否执行内联的 JS 代码。说白了,就是白名单制度,这样,攻击者就算是发现了漏洞,也没有办法注入脚本。
可以通过 HTTP 头部信息的 Content-Security-Policy 字段来启用 CSP,另一种方式是通过页面的 标签
网络层面
经历过面试的小伙伴都知道,有一个经典的面试题:请问你怎么解决跨域?
跨域资源共享(CORS)可以进行跨域访问控制,需要浏览器和服务器同时支持,是为了跨域数据传输更加安全。
浏览器将 CORS 分为两类请求:简单请求、非简单请求。
只要同时满足以下两大条件就属于简单请求:
请求方法是以下三种之一:
HEAD
GET
POST
2. HTTP 头部信息不超出以下几种字段
-
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type: 只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain
DPR
Downlink
Save-Data
Viewport-Width
Width
非简单请求时那种对服务器有特殊要求的请求,像是 PUT 或者 DELETE,或者 Content-Type 字段的类型是 application/json。
浏览器会先发送一个预检请求,请求方法是 OPTIONS,表示这个请求时用来询问的,头部信息里面,关键字是 Origin,表示来自哪个源,除了 Origin 以外,还包括 Access-Control-Request-Method 和 Access-Control-Request-Header。一旦服务器通过了预检请求,以后每次浏览器正常的 CORS 请求就像是简单请求一样了,与 JSONP 相比,CORS 更加强大, JSONP 只支持 GET 请求,而 CORS 支持所有类型的 HTTP 请求, JSONP 的优势是支持老式浏览器,老式浏览器不支持 CORS 的话可以使用 JSONP。不过,如果你还需要兼容到这么老版本了浏览器,表示默默的心疼你。
服务端一般需要设置 Access-Control-Allow-Origin: * 等字段来允许浏览器跨域,如果浏览器需要发送 cookie 信息,在前端项目中需要设置 XMLHttpRequest 的 withCredentials 属性为 true。
除了 CORS 以外,使用 nginx 反向代理也是我们常见解决跨域的方式,这种方式的的原理跟 CORS 跨域原理一样,通过配置文件设置响应头 Access-Control-Allow-Origin...等字段。通过 nginx 配置一个代理服务器域名与 domain1 相同,端口不同做跳板机,反向代理访问 domain2 接口,并且可以顺便更改 cookie 中的 domain 信息,方便当前域 cookie 写入,实现跨域访问。
#proxy服务器
server {
listen 8081;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
总结
首先我们讲解了一下浏览器最核心的安全策略—同源策略(三同:同协议、同域名、同端口), 同源策略确实能够让我们的页面更加安全,但是安全性与便利性是相对的,如果让不同源之间绝对隔离,这会让 Web 项目变得难以开发和使用,浏览器不得不出让在此策略下的一些安全性,为了我们能够在像是同源一样胡作非为,提供了 跨资源共享策略和跨文档信息机制。但是如果页面足够灵活,又容易引发安全问题,比如 XSS 攻击,为了防止第三方资源引起的 XSS 攻击问题,浏览器采用了 CSP 来限制其自由度。
参考链接
https://juejin.im/post/5d1ecb96f265da1b6d404433#heading-12
https://www.ruanyifeng.com/blog/2016/04/cors.html
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP
http://www.ruanyifeng.com/blog/2016/09/csp.html