背景:
同源策略:NetSpace公司引入,基於瀏覽器安全,防止瀏覽器收到XSS、CSFR等攻擊。同源,即協議+域名+端口完全一致。
同源策略:為保障用戶信息安全,防止惡意網站竊取數據的一種安全策略。
“同源”:協議相同、域名相同、端口號相同
同源策略限制的行為:
- Cookie、LocalStorage和IndexDB無法讀取
- DOM和JS對象無法獲取
- Ajax請求不能發送
解決方案:
方案一:JSONP
原理:通過script標簽引入的js不受同源策略的限制,而XmlHttpRequest對象受到同源策略的影響。可以加載跨域服務器上的腳本,用JSONP獲取的不是JSON數據,而是可以直接運行的JS腳本。
eg1:jquery
function jsonpCallback(data) {
console.log("jsonpCallback: " + data.name)
}
$.ajax({
url:"http://www.nanhuaqiushui.com:8080/login",
type:"get",
dataType:"jsonp",
data:{
name: $("#name").val(),
id: $("#id").val()
},
cache: false,
timeout: 5000,
jsonp: "callback", //jsonp字段含義為服務器通過什么字段獲取回調函數的名稱
jsonpCallback:"jsonpCallback", //自定義回調函數名
success:function(data){
console.log("ajax success callback: " + data.name);
},
error: function(jqXHR, textStatus, errorThrown){
console.log(textStatus + ' ' + errorThrown);
}
})
eg2:vue
this.$http.jsonp("http://www.nanhuaqiushui.com:8080/login",{
params:{
name: $("#name").val(),
id: $("#id").val()
},
jsonp: "jsonpCallback"
}).then((res) => {
console.log(res);
});
服務器端處理:
app.get("/login", function(req,res){
console.log("server accept: " + req.query.name, req.query.id);
var data = "{" + "name:" + req.query.name + "- server 3001 process" + "," + "id:" + req.query.id + "- server 3001 process" + "}";
var callback = req.query.callback;
var jsonp = callback + "(" + data + ")";
console.log(jsonp);
res.send(jsonp);
res.end();
});
注意:data中字符串拼接,不能直接將JSON格式的data直接傳給回調函數,否則會發生編譯錯誤。
本質:
<script src = 'http://www.nanhuaqiushui.com:8080/login?callback=jsonpCallback&name=lidachui&id=3001&_=1473164876032'></script>
不足:
1.只能使用GET請求(即時POST也會被轉換成GET請求)。
2.JSONP本質上是通過script標簽的src屬性實現跨域請求的,而非ajax,因此不是通過XMLHttpRequest進行傳輸的,所以無法注冊success、error等事件監聽函數。
方案二:CORS(跨資源共享)實現跨域調用
原理:使用自定義的HTTP頭部讓瀏覽器與服務器溝通,從而決定請求或響應是否成功。
Cross-Origin Resource Sharing(CORS)跨域資源共享是一份瀏覽器技術的規范,提供了Web服務從不同域傳來沙盒腳本的方法,以避開瀏覽器的同源策略。(現代瀏覽器都支持CORS)
優點:采用XMLHttpRequest對象傳遞,同時支持GET/POST等多種請求方式,方便調試。是JSONP模式的現代版。
eg:
app.post('/cors',function(req,res){
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");
res.header("Content-Type", "application/json;charset=utf-8");
var data = {
name: req.body.name + ' - server 3001 cors process',
id: req.body.id + ' - server 3001 cors process'
};
res.send(data);
res.end();
})
優點:只需服務端設置Access-Control-Allow-Origin即可,前端無需設置。
withCredentials: true // 前端設置是否帶cookie
方案三:window.name + iframe
原理:window對象的name屬性很特別,name值在不同的頁面(甚至不同域名)加載后依舊存在,而且可以支持非常長的name值(2MB)。
Iframe元素可以在當前網頁之中,嵌入其他網頁。每個iframe元素形成自己的窗口,即有自己的window對象。iframe之中的腳本,可以獲得父窗口和子窗口。但是在同源的情況下,父窗口和子窗口才能通信;如果跨域,就無法拿到對方的DOM。
var proxy = function(url, callback){
var state = 0;
var iframe = document.createElement("iframe");
//加載跨域頁面
iframe.src = url;
iframe.onload = function(){
if(state == 0){
//第一次onload(跨域頁)成功后,切換到同域代理頁面
iframe.contentWindow.location = "http://www.nanhuaqiushui.com";
state = 1;
}else if(state == 1){
//第二次onload(同域proxy頁)成功后,讀取同域window.name中數據
callback(iframe.contentWindow.name);
destroyFrame();
}
}
}
documetn.body.appendChild(iframe);
//獲取數據后銷毀這個iframe,釋放內存
function destroyFrame(){
iframe.contentWindow.document.write("");
iframe.contentWindow.close();
document.body.removeChild(iframe);
}
//請求跨域b頁面數據
proxy("http://www.domain2.com/b.html",function(data){
alert(data);
});
方案四:postMessage跨域(XDM)
postMessage是H5 中新的api,可解決以下問題:
頁面和其打開的新窗口的數據傳遞;
多窗口之間消息傳遞;
頁面與嵌套的iframe消息傳遞;
上面三個場景的跨域數據傳遞。
XDM
跨文本消息傳遞(cross-document messaging),簡稱XDM,指來自不同域的頁面間傳遞消息。
postMessage(),參數1表示消息值,參數2表示接收方是來自哪個域的字符串。
var iframeWindow = document.getElementById("rayframe").contentWindow,
iframeWindow.postMessage("A secret","http://www.wrox.com");
接收XDM消息時,會觸發window對象的message事件。這個事件以異步形式觸發,因此從發送消息到接收消息可能經過一段時間的延遲。觸發message事件后,傳遞給onmessage事件對象包含一下三方面重要信息:
1.data:作為postMessage()第一個參數傳入的字符串數據
2.origin:發送消息的文檔所在的域
3.source:發送消息的文檔的window對象的代理。這個代理對象的主要用於在發送上一條消息的窗口中調用postMessage()方法。
用法:postMessage(data,origin)
父窗口:
<iframe src="http://www.xiaokeai.com"></iframe>
window.onmessage = function(e){
if(e.origin == "http://www.wrox.com"){
//處理接收到的數據
processMessage(e.data);
//可選,向來源窗口發送回執
e.source.postMessage("Received","http://p2p.wrox.com")
}
}
子窗口:
if(window.parent !== window.self){
window.parent.postMessage("xiaohuochai","http://fatherxiaokeai.com");
}
方法五:document.domain(兩個iframe之間)
背景:同源策略認為域和子域隸屬於不同的域,因此會被瀏覽器攔截。
方法:如果兩個窗口一級域名相同,只是二級域名不同,可以通過設置document.domain來使其通信。
通過設置document.domain只能獲取DOM,而Cookie、LocalStorage和IndexedDB無法獲取。
問題:安全性,當一個站點被攻擊后,另一個站點也會引起安全漏洞;
如果一個頁面中引入多個iframe,要想跨域訪問,就要都設置為相同的domain值
方法六:ngix代理跨域
1.ngix配置解決iconfont跨域
背景:瀏覽器跨域訪問js\css\img等常規靜態資源被同源策略許可,但是iconfont字體文件(eot|otf|ttf|woff|svg)例外,此時需在ngix的靜態資源服務器中加入配置:;
location / {
add_header Access-Control-Allow-Origin * ;
}
2.ngix反向代理接口跨域
原理:同源策略是瀏覽器的安全策略,不是HTTP協議的一部分。服務器端調用HTTP接口只是使用HTTP協議,不會執行JS腳本,不需要同源策略,也就不存在跨域問題。
實現思路:通過ngix配置一個代理服務器(域名與domain1相同,端口不同)做跳板機,反向代理訪問domain2接口,並且可以順便修改cookie中domain信息,方便當前與cookie寫入,實現跨域登錄。
方案七:WebSocket協議跨域
WebSocket protocol是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通信,同時允許跨域通訊,是server push技術的一種很好的實現。原生WebSocket API使用起來不太方便,我們使用Socket.io,它很好地封裝了webSocket接口,提供了更簡單、靈活的接口,也對不支持webSocket的瀏覽器提供了向下兼容。
前端:
<div>user input:<input type="text"></div>
<script src="./socket.io.js"></script>
node后台:
var http = require('http');
var socket = require('socket.io');
// 啟http服務
var server = http.createServer(function(req, res) {
res.writeHead(200, {
'Content-type': 'text/html'
});
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
// 監聽socket連接
socket.listen(server).on('connection', function(client) {
// 接收信息
client.on('message', function(msg) {
client.send('hello:' + msg);
console.log('data from client: ---> ' + msg);
});
// 斷開處理
client.on('disconnect', function() {
console.log('Client socket has closed.');
});
});
方案八:錨點值
又稱為片段標識符,指的是URL的#后面的部分。如果只是改變片段標識符,頁面不會重新刷新。
父窗口可以把信息,寫入子窗口的錨點值
var src = originURL + "#" + data;
document.getElementById("myIframe").src = src;
子窗口通過監聽hashchange事件得到通知
window.onhashchange = checkMessage;
function checkMessage(){
var message = window.location.hash;
...
}