一文梳理同源策略与跨域技术
1.同源策略
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
1.1何谓同源?
如果两个 URL 的 protocol、port (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。
下表给出了与 URL http://store.company.com/dir/page.html
的源进行对比的示例:
URL | 结果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html |
同源 | 只有路径不同 |
http://store.company.com/dir/inner/another.html |
同源 | 只有路径不同 |
https://store.company.com/secure.html |
失败 | 协议不同 |
http://store.company.com:81/dir/etc.html |
失败 | 端口不同 ( http:// 默认端口是80) |
http://news.company.com/dir/other.html |
失败 | 主机不同 |
随着互联网的发展,"同源政策"越来越严格。目前,如果非同源,共有三种行为受到限制。
(1) Cookie、LocalStorage 和 IndexDB 无法读取。
(2) DOM 无法获得。
(3) AJAX 请求不能发送。
虽然这些限制是必要的,但是有时很不方便,合理的用途也受到影响。
2.跨域
2.1 何谓跨域?
跨域问题是由于浏览器为了防止CSRF(跨站请求伪造)攻击,避免恶意攻击而带来的风险而采取的同源策略限制。当一个页面中使用XMLHTTPRequest对象发送HTTP请求时(XHR请求),必须保证当前页面和请求的对象是同源的
能实现跨域的技术还是比较多的,篇幅有限,这篇文章主要给大家带来:
- JSONP跨域
- CORS跨域
- postmessage
- 服务器代理
- WebSocket
2.2 JSONP跨域
JSONP的原理:静态资源请求不受同源策略影响。具体:浏览器只对XHR(XMLHttpRequest)请求有同源请求限制,而对script标签src属性、link标签ref属性和img标签src属性没有这种限制,利用这个“漏洞”就可以很好的解决跨域请求.
JSONP 由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调
函数的名字一般是在请求中指定的。而数据就是传入回调函数中的JSON数据。
<script>
function jsoncallback(response){
console.log(response);
}
var script = document.createElement("script");
script.src = "https://me.kervi.cn/v1/datas/jsondata.php?callback=jsoncallback";
document.body.insertBefore(script, document.body.firstChild);
</script>
JSONP 是通过动态<script>
元素来使用的,使用时可以为src 属性指定一个跨域 URL。
总结:
- 简单易用
- 能够直接访问响应文本,支持在浏览器与服务器之间双向通信
- 仅支持GET请求(通过
script
标签的的src属性发送); - 需要后端配合,前后端约定一个字段名,这里约定的jsoncallback,来传递一个函数名,从而使得前端可以使用对应的callback函数,拿到数据,处理数据。
- JSONP 是从其他域中加载代码执行。如果其他域不安全,很可能会在响应中夹带一些恶意代码,而此时除了完全放弃 JSONP 调用之外,没有办法追究。因此在使用不是你自己运维的 Web 服务时,一定得保证它安全可靠;
- 要确定 JSONP 请求是否失败并不容易。虽然 HTML5 给
<script>
元素新增了一个 onerror事件处理程序,但目前还没有得到任何浏览器支持。为此,开发人员不得不使用计时器检测指定时间内是否接收到了响应。但就算这样也不能尽如人意,毕竟不是每个用户上网的速度和带宽都一样。
2.3 CORS跨域
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest
请求,从而克服了AJAX只能同源使用的限制。它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
在各种服务端代码实现如下:
// 根据不同语言规则,具体语法有所不同,此处以NodeJs的express为例
//设置跨域访问
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
next();
});
Nginx实现如下:
server {
...
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Origin $http_origin;
location /file {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods $http_access_control_request_method;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Headers $http_access_control_request_headers;
add_header Access-Control-Max-Age 1728000;
return 204;
}
}
...
}
2.4 postMessage
「window.postMessage()」 方法可以安全地实现跨源通信。可以允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本文档,多窗口,跨域消息传递.多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案.
兼容性:
语法
otherWindow.postMessage(message, targetOrigin, [transfer])
message
你要发送的信息(字符串和对象都可以)targetOrigin
你要发送信息的目标域名transfer
可选参数,与消息一起传输的Transferable
对象序列。这些对象的所有权将提供给目标端,并且它们在发送端不再可用。
已经有人写了不错的实践,博主这里就不做介绍,可以阅读postmessage可真太有用了了解。
总结:
-
postMessage是html5新增的一个解决跨域的方法,主要解决不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递,多用于页面与嵌套的iframe消息传递
-
由于默认允许跨域,所以导致一些安全问题,主要攻击方式有两种,一是伪造数据发送端,易造成XSS,二是伪造数据获取端,类似JSONP劫持。
-
用于接收消息的任何事件侦听器必须首先使用origin和可能的source属性检查消息发送者的身份。未能检查origin和可能的source属性可以实现跨站点脚本攻击。
2.5 websocket
WebSocket 是一种网络通信协议,可在网络浏览器和服务器之间建立“套接字”连接。这种方式本质没有使用了 HTTP 的响应头, 因此也没有跨域的限制。
前端部分(代码来自秋风的笔记-10种跨域解决方案(附终极大招))
<script> let socket = new WebSocket("ws://localhost:8080"); socket.onopen = function() { socket.send("秋风的笔记"); }; socket.onmessage = function(e) { console.log(e.data); };</script>复制代码
后端部分
const WebSocket = require("ws");const server = new WebSocket.Server({ port: 8080 });server.on("connection", function(socket) { socket.on("message", function(data) { socket.send(data); });});
总结:
- 建立在 TCP 协议之上,服务器端的实现比较容易。
- 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符是
ws
(如果加密,则为wss
),服务器网址就是 URL。
2.6 Nginx反向代理
这里需要说明的是: 跨域是浏览器行为,不是服务器行为。同源策略仅是针对浏览器的安全策略。服务器端调用HTTP接口只是使用HTTP协议,不需要同源策略,也就不存在跨域问题。
实际上,请求已经到达服务器了,只不过在回来的时候被浏览器限制了
实现思路:通过Nginx配置一个代理服务器域名与发出请求域名相同,端口不同,反向代理访问目标域名。
背景:domain1需要跨域访问domain2
server {
listen 80;
server_name www.domain1.com;
location / {
proxy_pass www.domain2.com:8080;
}
}
需要说明的是,前端跨域的技术还有许多,只是限于篇幅没有讲到,比如node正向代理,window.name+iframe等。还想了解别的跨域方案的小伙伴可以看本文的参考文章去观摩观摩。
小结
同源策略仅仅是针对浏览器的安全策略,跨域是浏览器行为,如果两个 URL 的 protocol、port (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。现在有许多的跨域方案可供大家选择,本文仅仅选择了JSONP、CORS、postMessage等6种跨域技术进行了阐述,希望能在梳理自己知识点的同时帮助到一些人。
参考文章: