為什麼會存在同源策略
概念
1995年有Netscape公司引入的一個安全策略,現在所有瀏覽器都在使用這個策略。它限制了一個源從另外一個源請求資源,用於隔離潛在惡意檔案。
什麼才是不同的源
我們都知道一般的地址又下面三部分組成
- 協議相同
- 域名相同
- 埠相同
只要兩個地址其中有一個不相同,那麼這兩個地址就是不同的源。
舉個例子
//地址
http://www.address.com/item/page.html
協議: http://
域名:www.example.com
埠:8080(http)/443(https) (埠預設省略)
http://www.address.com/item2/other.html:同源
http://address.com/item/other.html:不同源(域名不同)
http://v2.www.address.com/item/other.html:不同源(域名不同)
http://www.address.com:81/item/other.html:不同源(埠不同)
複製程式碼
同源的目的
目的是為了保護使用者資訊的安全,防止惡意網站竊取資料,否則Cookie可以共享。有的網站一般會把一些重要資訊存放在cookie或者LocalStorage中,這時如果別的網站能夠獲取獲取到這個資料,可想而知,這樣就沒有什麼安全可言了。
限制範圍
目前共有三種行為受到限制
- Cookie、LocalStorage和IndexDB 無法讀取
- DOM無法獲得
- AJAX 請求不能傳送
解決方案
1、通過jsonp跨域
原理
script、img、iframe等標籤的src屬性都擁有跨域請求資源的能力,我們可以把js、css、img等資源放到一個獨立域名的伺服器上。然後通過動態建立script標籤,再請求一個回撥函式的網址,伺服器把需要傳遞的引數塞入這個回撥函式中, 然後我們在js程式碼中執行這個函式就可已獲取到你想要的引數。
前端原生實現
<script>
window.xxx = function (value) {
console.log(value)
}
var script = document.createElement('script')
script.src = 'https://www.address.com:433/json?callback=xxx'
document.body.appendChild(script)
</script>
複製程式碼
jquery實現
$.ajax({
type: "get",
url: "https://www.address.com:433/json",
dataType: "jsonp",
jsonp: "callback",
jsonpCallback:"xxx"//自定義回撥函式名
success: function(res){
console.log(res)
},
error: function(){
console.log('fail');
}
});
複製程式碼
jsonp外掛
//安裝
npm install jsonp
const jsonp = require('jsonp');
jsonp('https://www.address.com:433/json', {parma:'xxx'}, (err, data) => {
if (err) {
console.error(err.message);
} else {
console.log(data);
}
});
複製程式碼
node.js egg 服務端
//需要看egg官網構建一個simple框架
egg-init --type=simple
// router.js 用egg內建jspnp方法 https://eggjs.org/api/Config.html#jsonp
module.exports = app => {
app.get('/json', app.jsonp({ callback: 'xxx' }), app.controller.json.index)
}
複製程式碼
缺點
- 只支援GET請求,不支援POST請求
- 呼叫失敗沒有HTTP狀態碼
- 不夠安全。
2、CORS
原理
CORS(Cross-Origin Resource Sharing)跨資源分享,瀏覽器不能低於IE10,伺服器支援任何型別的請求。
熟悉幾個後臺需要設定的欄位
- Access-Control-Allow-Origin
欄位必傳,為*表示允許任意域名的請求。當有cookie需要傳遞時,需要指定域名。
- Access-Control-Allow-Credentials
欄位可選,預設為false,表示是否允許傳送cookie。若允許,通知瀏覽器也要開啟cookie值的傳遞。
- Access-Control-Expose-Headers
欄位可選。如果想要瀏覽器拿到getResponesHeader()其他欄位,就在這裡指定。
- Access-Control-Request-Method
必須欄位,非簡單請求時設定的欄位,例如PUT請求。
- Access-Control-Request-Headers
指定額外的傳送頭資訊,以逗號分割字串。
前端原生程式碼
var xhr = new XMLHttpRequest()
// 設定攜帶cookie
xhr.withCredentials = true;
xhr.open('POST', 'https://www.address.com:433/json', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.send(null)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(JSON.parse(xhr.responseText).msg)
}
}
複製程式碼
後端程式碼
module.exports = app => {
class CrosController extends app.Controller {
* index(req) {
this.ctx.set('Access-Control-Allow-Origin', 'https://www.address.com');
this.ctx.set('Access-Control-Allow-Credentials', 'true')
this.ctx.body = { msg: 'hello world' }
}
}
return CrosController
}
複製程式碼
優點
- 支援所有型別的HTTP請求。
缺點
- 不相容IE10以下。
3、iframe結合locaction.hash方法
原理
也是利用iframe可以在不同域中傳值的特點,而location.hash正好可以攜帶引數,所以利用iframe作為這個不同域之間的橋樑。
具體實現步驟
- 1、向A域名頁面插入一個B域名的iframe標籤。
- 2、然後在B域名的iframe頁面中ajax請求同域名的伺服器。
- 3、iframe頁面拿到資料後通過praent.location.href將想要傳遞的引數放到#後面,作為hash傳遞。
- 4、這樣在A域名頁面就能通過window.onhashchange 監聽處理你想要的資料。
A域名頁面
var iframe = document.createElement('iframe')
iframe.src = 'http://www.B.com:80/hash.html'
document.body.appendChild(iframe)
window.onhashchange = function () {
//處理hash
console.log(location.hash)
}
複製程式碼
B域名頁面
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var res = JSON.parse(xhr.responseText)
console.log(res.msg)
parent.location.href = `http://www.A.com:80/a.html#msg=${res.msg}`
}
}
xhr.open('GET', 'http://www.B.com:80/json', true)
xhr.send(null)
複製程式碼
缺點
- iframe雖然能解決問題,但是安全風險還是比較重要的。
- hash傳參處理起來比較麻煩。
4、iframe結合window.name方法
原理
原理其實是和上面的方法一樣,區別在於window.name能夠傳遞2MB以上的資料。
A域名頁面
var iframe = document.createElement('iframe')
iframe.src = 'http://www.B.com:80/name.html'
document.body.appendChild(iframe)
var times = 0
iframe.onload = function () {
if (times === 1) {
console.log(JSON.parse(iframe.contentWindow.name))
destoryFrame()
} else if (times === 0) {
times = 1
}
}
// 獲取資料以後銷燬這個iframe,釋放記憶體;
function destoryFrame() {
document.body.removeChild(iframe);
}
複製程式碼
B域名頁面
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
window.name = xhr.responseText
location.href = 'http://www.A.com:80/a.html'
}
}
xhr.open('GET', 'http://www.B.com:80/json', true)
xhr.send(null)
複製程式碼
5、postMessage跨域
原理
postMessage是H5原生API支援,可以在兩個頁面或者多個頁面,以及不同源頁面之間傳遞資料。視窗之間能夠傳遞資料的前提是必須從一個視窗獲取到另外一個視窗的目標物件(target window),比如說用iframe開啟另外一個視窗,我們得獲取到這個iframe的contentWindow。或者通過window.open()開啟另外一個視窗時會返回這個視窗的window物件。
引數傳遞
/**
data 需要傳遞的資料,使用JSON.stringify 序列化
origin 設定為'*'時,表示傳遞給所有視窗,也可以指定地址。 如果是同源下設定為'/'
**/
postMessage(data,origin)
複製程式碼
// 開啟一個新的視窗
var popup = window.open('http://localhost:8080');
/// 等待新視窗載入完
setTimeout(function() {
// 當前視窗向目標源傳資料
popup.postMessage({"age":10}, 'http://localhost:8080');
}, 1000);
複製程式碼
// 設定監聽,如果有資料傳過來,則列印
window.addEventListener('message', function(e) {
console.log(e);
//判斷是不是目標地址
if(e.origin !== 'http://localhost:8069')return;
// console.log(e.source === window.opener); // true
//發回資料
e.source.postMessage({"age":20}, e.origin);
});
複製程式碼
其他的解決方式
以上五種是比較常見的跨域解決方案,各有優缺點。沒有絕對的最優方案,只有最合適應用場景。
常見的兩種代理跨域
- nginx反向代理跨域 node中介軟體代理
配置就不講了,其實我也不熟悉,平時工作用不到這麼高大上的。就來講講原理以及思路。
什麼是代理
既然是代理跨域,那麼代理(Proxy Server)就是一個很重要的點,這裡的代理說的伺服器代理,是一種很重要的伺服器安全功能,也是一種很常見的設計模式,來隔絕不同的模組,解耦模組。生活中也很容易見到這種模式,我們買房(買不起呀)買車,就好比跟一個大型伺服器打交道,別人是大老闆,自然不會那麼容易親自接待你,這時候有中介在中間,幫你們兩方理清好思路,做好鋪墊,然後後面的交流才會更加順通高效。我們瀏覽器訪問不同源的伺服器是有限定的,但是nginx和node中介軟體這些代理就沒有跨域的限定,所以我們可以放心的把任務交給他們,讓他們幫我們去做我們做不了的事。
為什麼代理是反的
我們知道單個伺服器的處理能力是有限的,就好比如中國也不可能只有一家汽車生產商,中國那麼大的市場,自然需要很多生產商才能滿足的過來。那麼使用者選購的時候需要節省時間和精力,汽車之間就承擔這樣一個角色,把成千上萬的使用者需求分配出去,nginx就能夠把使用者的請求分發到空閒的伺服器上,然後伺服器返回自己的服務到負載均衡裝置上,然後負載均衡的裝置會講伺服器的服務返回給使用者,所以我們並不知道為什麼服務的是哪一臺伺服器傳送出來的,這就很好的隱藏了伺服器。有一句精闢的話是這麼說的:“反向代理就是流量發散狀,代理是流量匯聚狀。”
最後附上一張畫的很醜陋的圖
如果大神您想繼續探討或者學習更多知識,歡迎加入QQ或者微信一起探討:854280588