-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
259 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
# 同源策略 | ||
|
||
同源: 同源策略/SOP(Same origin policy)是一种约定,"协议+域名+端口"三者相同 | ||
|
||
同源策略限制以下几种行为: | ||
|
||
- Cookie、LocalStorage 和 IndexDB 无法读取 | ||
- DOM 和 Js对象无法获得 | ||
- AJAX 请求不能发送 | ||
|
||
## 跨域解决方案 | ||
|
||
- 通过jsonp跨域 | ||
- document.domain + iframe跨域 | ||
- location.hash + iframe | ||
- window.name + iframe跨域 | ||
- postMessage跨域 | ||
- 跨域资源共享(CORS) | ||
- nginx代理跨域 | ||
- nodejs中间件代理跨域 | ||
- WebSocket协议跨域 | ||
|
||
### 通过jsonp跨域 | ||
|
||
通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许 | ||
|
||
基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。 | ||
|
||
jsonp缺点:只能实现get一种请求。 | ||
|
||
### document.domain + iframe跨域 | ||
|
||
此方案仅限主域相同,子域不同的跨域应用场景。 | ||
|
||
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。 | ||
|
||
1. 父窗口:(http://www.domain.com/a.html) | ||
|
||
```html | ||
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe> | ||
<script> | ||
document.domain = 'domain.com'; | ||
var user = 'admin'; | ||
</script> | ||
``` | ||
|
||
2. 子窗口:(http://child.domain.com/b.html) | ||
|
||
```html | ||
<script> | ||
document.domain = 'domain.com'; | ||
// 获取父窗口中变量 | ||
alert('get js data from parent ---> ' + window.parent.user); | ||
</script> | ||
``` | ||
|
||
### location.hash + iframe跨域 | ||
|
||
实现原理: a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。 | ||
|
||
具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。 | ||
|
||
1. a.html:(http://www.domain1.com/a.html) | ||
|
||
```html | ||
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe> | ||
<script> | ||
var iframe = document.getElementById('iframe'); | ||
// 向b.html传hash值 | ||
setTimeout(function() { | ||
iframe.src = iframe.src + '#user=admin'; | ||
}, 1000); | ||
// 开放给同域c.html的回调方法 | ||
function onCallback(res) { | ||
alert('data from c.html ---> ' + res); | ||
} | ||
</script> | ||
``` | ||
|
||
2. b.html:(http://www.domain2.com/b.html) | ||
|
||
```html | ||
<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe> | ||
<script> | ||
var iframe = document.getElementById('iframe'); | ||
// 监听a.html传来的hash值,再传给c.html | ||
window.onhashchange = function () { | ||
iframe.src = iframe.src + location.hash; | ||
}; | ||
</script> | ||
``` | ||
|
||
3. c.html:(http://www.domain1.com/c.html) | ||
|
||
```html | ||
<script> | ||
// 监听b.html传来的hash值 | ||
window.onhashchange = function () { | ||
// 再通过操作同域a.html的js回调,将结果传回 | ||
window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', '')); | ||
}; | ||
</script> | ||
``` | ||
|
||
## window.name + iframe跨域 | ||
|
||
window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。 | ||
|
||
1. a.html:(http://www.domain1.com/a.html) | ||
|
||
```html | ||
var proxy = function(url, callback) { | ||
var state = 0; | ||
var iframe = document.createElement('iframe'); | ||
|
||
// 加载跨域页面 | ||
iframe.src = url; | ||
|
||
// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name | ||
iframe.onload = function() { | ||
if (state === 1) { | ||
// 第2次onload(同域proxy页)成功后,读取同域window.name中数据 | ||
callback(iframe.contentWindow.name); | ||
destoryFrame(); | ||
|
||
} else if (state === 0) { | ||
// 第1次onload(跨域页)成功后,切换到同域代理页面 | ||
iframe.contentWindow.location = 'http://www.domain1.com/proxy.html'; | ||
state = 1; | ||
} | ||
}; | ||
|
||
document.body.appendChild(iframe); | ||
|
||
// 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问) | ||
function destoryFrame() { | ||
iframe.contentWindow.document.write(''); | ||
iframe.contentWindow.close(); | ||
document.body.removeChild(iframe); | ||
} | ||
}; | ||
|
||
// 请求跨域b页面数据 | ||
proxy('http://www.domain2.com/b.html', function(data){ | ||
alert(data); | ||
}); | ||
``` | ||
|
||
2. proxy.html:(http://www.domain1.com/proxy.... | ||
|
||
中间代理页,与a.html同域,内容为空即可。 | ||
|
||
3. b.html:(http://www.domain2.com/b.html) | ||
|
||
```html | ||
<script> | ||
window.name = 'This is domain2 data!'; | ||
</script> | ||
``` | ||
|
||
总结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。 | ||
|
||
## postMessage跨域 | ||
|
||
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题: | ||
|
||
- 页面和其打开的新窗口的数据传递 | ||
- 多窗口之间消息传递 | ||
- 页面与嵌套的iframe消息传递 | ||
- 上面三个场景的跨域数据传递 | ||
|
||
1. a.html:(http://www.domain1.com/a.html) | ||
|
||
```html | ||
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe> | ||
<script> | ||
var iframe = document.getElementById('iframe'); | ||
iframe.onload = function() { | ||
var data = { | ||
name: 'aym' | ||
}; | ||
// 向domain2传送跨域数据 | ||
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com'); | ||
}; | ||
// 接受domain2返回数据 | ||
window.addEventListener('message', function(e) { | ||
alert('data from domain2 ---> ' + e.data); | ||
}, false); | ||
</script> | ||
``` | ||
|
||
2. b.html:(http://www.domain2.com/b.html) | ||
|
||
```html | ||
<script> | ||
// 接收domain1的数据 | ||
window.addEventListener('message', function(e) { | ||
alert('data from domain1 ---> ' + e.data); | ||
var data = JSON.parse(e.data); | ||
if (data) { | ||
data.number = 16; | ||
// 处理后再发回domain1 | ||
window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com'); | ||
} | ||
}, false); | ||
</script> | ||
``` | ||
|
||
## 跨域资源共享(CORS) | ||
|
||
普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。 | ||
|
||
需注意的是:由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页。 | ||
|
||
目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。 | ||
|
||
1. 前端设置 | ||
|
||
```javascript | ||
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容 | ||
|
||
// 前端设置是否带cookie | ||
xhr.withCredentials = true; | ||
|
||
xhr.open('post', 'http://www.domain2.com:8080/login', true); | ||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | ||
xhr.send('user=admin'); | ||
|
||
xhr.onreadystatechange = function() { | ||
if (xhr.readyState == 4 && xhr.status == 200) { | ||
alert(xhr.responseText); | ||
} | ||
}; | ||
``` | ||
|
||
2. 服务端设置 | ||
|
||
响应头: | ||
- Access-Control-Allow-Origin 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/' | ||
- Access-Control-Allow-Credentials 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会提示 | ||
- Access-Control-Allow-Headers 允许的跨域头字段 | ||
|
||
## nginx代理跨域 | ||
|
||
## Nodejs中间件代理跨域 | ||
|
||
## WebSocket协议跨域 | ||
|
||
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
|
||
HttpOnly | ||
|
||
只能由http请求带cookie,js无法读取cookie |