什么是同源策略
同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。
所谓同源是指"协议+域名+端口" 三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
同源策略限制以下几种行为:
1.) Cookie、LocalStorage 和 IndexDB 无法读取
2.) DOM 和 Js对象无法获得
3.) AJAX 请求不能发送
本域指的是
- 同协议:如都是http或者https
- 同域名:如都是http://jirengu.com/a 和http://jirengu.com/b
- 同端口:如都是80端口
例子
不同源的例子:
- http://jirengu.com/main.js 和 https://jirengu.com/a.php (协议不同)
- http://jirengu.com/main.js 和 http://bbs.jirengu.com/a.php (域名不同,域名必须完全相同才可以)
- http://jiengu.com/main.js 和 http://jirengu.com:8080/a.php (端口不同,第一个是80)
需要注意的是: 对于当前页面来说页面存放的 JS 文件的域不重要,重要的是加载该 JS 页面所在什么域
什么是跨域?跨域有几种实现形式
广义的跨域:
- 资源跳转: A链接、重定向、表单提交
- 资源嵌入:
<link>、<script>、<img>、<frame>
等dom标签,还有样式中background:url()、@font-face()等文件外链 - 脚本请求: js发起的ajax请求、dom和js对象的跨域操作等
其实我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。
跨域解决方案
- 通过jsonp跨域
- document.domain + iframe跨域(降域)
- location.hash + iframe
- window.name + iframe跨域
- postMessage跨域
- 跨域资源共享(CORS)
- nginx代理跨域
- nodejs中间件代理跨域
- WebSocket协议跨域
1. JSONP
简易例子
请求方:frank.com的前端程序员(浏览器)
响应方:jack.com的后端程序员(服务器)
- 请求方创建 script,src指向响应方,同时传一个查询参数
?callbackName=xxx
- 响应方根据查询参数callbackName,构造形如
- xxx.call(undefined,'所需的数据')
- xxx('所需的数据')
- 浏览器接收到相应,就会执行xxx.call(undefined,'所需的数据')
- 那么请求方就知道他所需要的数据
query为temp.query,提取请求URL中的哈希值
约定
- callbackName 一般用 callback
- xxx 一般用 随机数 例如jq1253156(数值随机,避免函数名重复)
通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。
jsonp缺点:只能实现get一种请求。
步骤
- 定义数据处理函数onBack()
- 创建script标签,src的地址执行后端接口,最后加个参数callback=onBack
- 服务端在收到请求后,解析参数,计算返还数据,输出 onBack(date)字符串。
- onBack(date)会放到script标签做为js执行。此时会调用onBack函数,将data做为参数。
JSONP为何不支持POST??
- 因为JSONP是通过动态创建script实现
- script无法支持POST
详细
把<script>
元素的src属性设成一个回传JSON的URL是可以想像的,这也代表从HTML页面通过script元素抓取 JSON是可能的。
然而,一份JSON文件并不是一个JavaScript程序。为了让浏览器可以在<script>
元素运行,从src里URL 回传的必须是可运行的JavaScript。在JSONP的使用模式里,该URL回传的是由函数调用包起来的动态生成JSON,这就是JSONP的“填充(padding)”或是“前辍(prefix)”的由来。
惯例上浏览器提供回调函数的名称当作送至服务器的请求中命名查询参数的一部分,例如:
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参并指定回调执行函数为onBack
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack';
document.head.appendChild(script);
// 回调执行函数
function onBack(res) {
alert(JSON.stringify(res));
}
</script>
服务器会在传给浏览器前将JSON数据填充到回调函数(onBack)中。浏览器得到的回应已不是单纯的数据叙述而是一个脚本。在本例中,浏览器得到的是:
onBack({"Name": "小明", "Id" : 1823, "Rank": 7})
后端node.js代码示例:
var querystring = require('querystring');
var http = require('http');
var server = http.createServer();
server.on('request', function(req, res) {
var params = qs.parse(req.url.split('?')[1]);
var fn = params.callback;
// jsonp返回设置
res.writeHead(200, { 'Content-Type': 'text/javascript' });
res.write(fn + '(' + JSON.stringify(params) + ')');
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
2. CORS
CORS 全称是跨域资源共享(Cross-Origin Resource Sharing),是一种 ajax 跨域请求资源的方式,支持现代浏览器,IE支持10以上。
实现方式很简单,当你使用 XMLHttpRequest 发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin; 浏览器判断该相应头中是否包含 Origin 的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。
所以 CORS 的表象是让你觉得它与同源的 ajax 请求没啥区别,代码完全一样。
此方法的原理是:
- 在服务器上添加下面语句,告诉浏览器,除了同源网址外,还接收什么网址访问
res.header("Access-Control-Allow-Origin", "http://a.jrg.com:8080");
//允许http://a.jrg.com:8080 端口访问
res.header("Access-Control-Allow-Origin", "*"); //允许所有其他端口访问
3. 降域
例如有a(a.jinrengu.com)、b(b.jinrengu.com)两个网址,现在需要用a访问b网址,由于两个网址不一样,故不同源,浏览器阻止访问b网址。
此方案仅限主域相同,子域不同的跨域应用场景。
原理:实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
1.)父窗口:(http://a.yuanqianyi.com:8080/a.html)
<html>
<style>
.ct{
width: 910px;
margin: auto;
}
.main{
float: left;
width: 450px;
height: 300px;
border: 1px solid #ccc;
}
.main input{
margin: 20px;
width: 200px;
}
.iframe{
float: right;
}
iframe{
width: 450px;
height: 300px;
border: 1px dashed #ccc;
}
</style>
<div class="ct">
<h1>使用降域实现跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.yuanqianyi.com:8080/a.html">
</div>
<iframe src="http://b.yuanqianyi.com:8080/b.html" frameborder="0" ></iframe>
</div>
<script>
document.querySelector('.main input').addEventListener('input', function(){
console.log(this.value);
window.frames[0].document.querySelector('input').value = this.value;
})
document.domain = "yuanqianyi.com"
</script>
</html>
2.)子窗口:(http://b.yuanqianyi.com:8080/b.html)
<html>
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
哇哇哇哇
<input id="input" type="text" placeholder="http://b.yuanqianyi.com:8080/b.html">
<script>
document.querySelector('#input').addEventListener('input', function(){
window.parent.document.querySelector('input').value = this.value;
})
document.domain = 'yuanqianyi.com';
</script>
</html>
4. postMessage跨域
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的iframe消息传递
- 上面三个场景的跨域数据传递
- 用法:postMessage(data,origin)方法接受两个参数
- data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
- origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
- a.html: (http://a.yuanqianyi.com:8080/a.html)
//向b发送跨域请求
document.querySelector('.main input').addEventListener('input', function(){
console.log(this.value)
window.frames[0].postMessage(this.value,"*")
})
//接收b返回的数据
window.addEventListener("message",function(e){
document.querySelector('.main input').value = e.data
console.log(e.data)
})
- b.html:(http://b.yuanqianyi.com:8080/b.html)
// 接收a的数据请求
window.addEventListener("message",function(e){
document.querySelector('input').value = e.data
console.log(e.data)
})
//发送数据给a
document.querySelector('input').addEventListener('input', function(){
window.parent.postMessage(this.value,"*")
window.parent.document.querySelector('input').value = this.value;
})