什么是同源策略
同源策略/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;
})