同源策略
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
同源的定义
如果两个url的protocol(协议,设备之间通信要求设备接受正在交换的数据格式,定义格式的一组规则称之为协议)、port 和 host 都相同的话,则这两个url是同源。
源的继承:
在页面中,通过about:blank或javascript:url 执行的脚本会继承打开该URL的文档的源
源的更改
满足某些限制条件的情况下,页面是可以修改它的源。脚本可以将document.domain的值设为当前域或者当前域的父域。如果将其设置为当前域的父域,则这个较短的父域将用于后续检查。
例如: http://stroe.company.com/dir/other.html文档中的一个脚本执行以下语句
document.domain = "company.com";
这条语句执行之后,页面将会成功地通过与http://company.com/dir/page.html的同源检测。然而,company.com不能设置doucument.domain为othercompany.com,因为他不是company.com的父域。
跨源网络访问
同源策略控制不同源之间的交互,这些交互通常会分为三类:
跨域写操作 一般是被允许的
跨域资源嵌入
跨域读操作 一般是不被允许的,但通常可以通过内嵌资源来巧妙地进行读取访问
以下是嵌入跨源的资源的示例
<script src="..."> </script> 标签嵌入跨域脚本
<line rel="styleheet" href="..."> 标签嵌入css,由于CSS的松散的语法规则,css的跨域需要一个设置正确的HTTP头部Content-Type。
Web servers send a HTTP response header named "Content-Type" that specifies the MIME-type of the file that is being sent. For security and standardscompliance reasons, style sheets should be delivered with "text/css" MIME type。
通过<img>展示的图片
通过<video> 和 <audio>播放的媒体资源
通过<object>、<embed>、<applet>嵌入的插件
通过@font-face引入的字体,一些浏览器允许跨域字体(cross-origin fonts),一些需要同源字体(same-origin fonts)
通过<iframe>载入的任何资源
如何允许跨源访问
可以使用CORS来允许跨源访问。CORS是HTTP的一部分,它允许服务端来指定哪些主机可以从这个服务端加载资源。
如何阻止跨域访问
阻止跨域写操作,只要检测请求中的一个不可推测的标记CSRF token即可,这个标记被称为CrossSite Request Forgery(CSRF)标记,使用这个标记来阻止页面的跨站读操作
阻止资源的跨站读取,需要保证该资源是不可嵌入的,阻止嵌入行为是必须的,因为嵌入资源通常向其暴露信息
阻止跨站嵌入,需要确保你的资源不能通过以上列出的可嵌入资源格式使用,浏览器可能不会遵守content-tpe头部定义的类型。当您的资源不是您网站的入口点时,您还可以使用CSRF令牌来防止嵌入。
跨源脚本应用
javaScript的API中,如iframe。contentWidow、window.parent、window.open和window.opener 允许文档间直接相互引用。当两个文档的源不同时,这些引用方式将对Window和Location对象的访问添加限制,为了能让不同源文档进行交流,可以使用window.postMessage
允许以下对Window属性的跨域访问
方法
window.blur
window.close
window.focus
window.postMessage
属性
window.colosed
window.frames
window.length
window.location
window.opener
window.parent
window.self
window.top
window.window
某些浏览器允许访问除上述外更多的属性
Location
允许一下对Location属性的跨源访问
方法
location.replace
属性
URLUtils.href
访问存储在浏览器中的数据,如localStorage和IndexedDB,是以源进行分割,每个源都拥有自己单独的存储空间,一个源中的JavaScript脚本不能对属于其它源的数据进行读写操作
Cookies 使用不同的源定义方式,一个页面可以为本域和其父域设置cookie,只要是父域不是公公后缀即可。浏览器允许给定的域以及其任何子域名访问cookie.当你读取cookie时,你无法知道它是在哪里被设置的,即使您只使用安全的https连接,您看到的任何cookie都有可能是使用不安全的连接进行设置的。
跨域资源共享CORS
CORS (cross-origin resource sharing) 实现原理:使用HTTP来告诉浏览器,让运行在某一个origin上的web应用允许访问来自不同源服务器上的置指定资源。
浏览器一旦发现请求是一个跨域请求,首先会判断请求的类型,
如果是简单请求,会在请求头中会包含一个origin字段,表示这次请求是来自哪一个源。而服务器接受到请求后,会返回一个响应,响应头中会包含一个叫Access-Controle-Allow-Origin: 的字段,它的值要么包含Origin首部字段所致命的域名,要吗是一个*,表示接受任意域名的请求。如果响应头中没有这个字段,就说明当前源不在服务器的许可范围内,浏览器就会报错。
如果是非简单请求,会在正式通信之前,发送一个预检查请求,目的在于询问服务器,当前网页所在的域名是否在服务器的许可名单里,以及可以使用哪些HTTP动词和头信息字段,只有得到肯定答复,浏览器才会发出正式请求,否则就会报错。日常开发中,使用OPTION方法发起的请求,就是一个预检请求。
简单请求和非简单请求
请求类型
使用GET、HEAD、POST,
并且content-type的值权限于下列三者之一:
text/plain
multipart/from-data
application/x-www-form-urlencoded
Websocket 是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。什么是全双工通信?简单来说,就是在建立连接后,server 与 client 都能主动向对方发送或接收数据。需要注意的是,websocket属于长连接,在一个页面建立多个websocket连接可能会导致性能问题。
Nginx 反向代理
我们知道同源策略的限制的是,浏览器向服务器发送跨域请求需要遵守的标准,那如果是服务器向服务器发送跨域请求就不会受浏览器的同源策略限制。利用这个思路,我们就可以搭建一个代理服务器,接受客户端请求,然后将请求转发给服务器,拿到响应后,再将响应转发给客户端。
这就是Nginx反向代理的原理,只需要简单配置就可以实现跨域
# nginx.config # ... server { listen 80; 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 *; add_header Access-Control-Allow-Credentials true; # ... } }
Node中间件代理
实现的原理和我们前文提到的代理服务器原理如出一辙,只不过这里使用了Node中间件作为代理,需要注意的是,浏览器向代理服务器请求时仍然遵循同源策略,别忘了在Node层通过CORS做跨域处理。
const https = require('https') // 接受客户端请求 const sever = https.createServer((req, res) => { ... const { method, headers } = req // 设置 CORS 允许跨域 res.writeHead(200, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': '*', 'Access-Control-Allow-Headers': 'Content-Type', ... }) // 请求服务器 const proxy = https.request({ host: 'xxx', method, headers, ...}, response => { let body = '' response.on('data', chunk => { body = body + chunk }) response.on('end', () => { // 响应结果转发给客户端 res.end(body) }) }) // 结束请求 proxy.end() })
跨域方案总结
CORS支持所有的HTTP请求,目前主流的跨域解决方案
Node中间件和Nginx反向代理都是利用了服务器对服务器没有同源策略限制