前端跨域相關的總結

這裏主要記錄在平常中對知識的學習,經過結合筆記與自身理解的方式嘗試寫下總結
文章對細節可能不會一一介紹解釋,內容僅做參考
複製代碼

1、同源策略

同源策略/SOP(Same origin policy)是一種約定,由Netscape公司1995年引入瀏覽器,它是瀏覽器最核心也最基本的安全功能,若是缺乏了同源策略,瀏覽器很容易受到XSS、CSFR等攻擊。所謂同源是指"協議+域名+端口"三者相同,即使兩個不一樣的域名指向同一個ip地址,也非同源。javascript

同源策略限制如下幾種行爲:css

1.) Cookie、LocalStorage 和 IndexDB 沒法讀取
2.) DOM 和 Js對象沒法得到
3.) AJAX 請求不能發送
複製代碼

對於 http://a.cat.com/src/one.html 進行同源檢測:

http://a.cat.com/src2/one.html 成功
http://a.cat.com/src/two/xx.html 成功
https://a.cat.com/src/one.html 失敗(協議不一樣)
http://b.cat.com/src/one.html 失敗(子域名不一樣)
http://a.cat.com:9900/src/one.html 失敗(端口不一樣)
複製代碼

只要協議,域名,端口有任何一個的不一樣,就被看成是跨域html

2、跨域的產生

跨域是指一個域下的文檔或腳本試圖去請求另外一個域下的資源前端

1.) 資源跳轉: A連接、重定向、表單提交
2.) 資源嵌入: <link>、<script>、<img>、<frame>等dom標籤,還有樣式中background:url()、@font-face()等文件外鏈
3.) 腳本請求: js發起的ajax請求、dom和js對象的跨域操做等
複製代碼

咱們一般所說的跨域是狹義的,是由瀏覽器同源策略限制的一類請求場景html5

3、解決跨域方式

1. JSONP

一般爲了減輕web服務器的負載,咱們把js、css,img等靜態資源分離到另外一臺獨立域名的服務器上,在html頁面中再經過相應的標籤從不一樣域名下加載靜態資源,而被瀏覽器容許java

基於此原理,利用script元素的這個開放策略,網頁能夠經過動態建立script,再請求一個帶參網址實現跨域通訊來獲得數據node

前端代碼實現:webpack

var script = document.createElement('script');
script.type = 'text/javascript';

// 傳參一個回調函數名給後端,提供參數返回後調用的函數
script.src = 'http://www.cat.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);

// 回調執行函數 須是全局函數
function handleCallback(res) {
    alert(JSON.stringify(res));
}
複製代碼

服務端返回代碼以下(返回時即執行全局函數):nginx

//這裏的handleCallback是服務端接收到回調函數名參數後構造生成的
handleCallback({"status": true, "user": "admin"})
複製代碼

有幾點須要注意:
1.) JSONP僅支持GET請求
2.) 獲取到的是JS執行代碼 (面試時被問過
3.) 總體實現思路 (在上面web

2. 跨域資源共享 CORS

整個CORS通訊過程,都是瀏覽器自動完成,不須要用戶參與。對於開發者來講,CORS通訊與同源的AJAX通訊沒有差異,代碼徹底同樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感受。

普通跨域請求:只服務端設置Access-Control-Allow-Origin便可,前端無須設置,若要帶cookie請求:先後端都須要設置。所以,實現CORS通訊的關鍵是服務器。只要服務器實現了CORS接口,就能夠跨源通訊

CORS要求瀏覽器(>IE10)和服務器的同時支持,是跨域的根本解決方法,由瀏覽器自動完成 前端代碼實現:

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);
    }
}
複製代碼

服務端代碼實現:

//容許跨域訪問的域名:如有端口需寫全(協議+域名+端口),若沒有端口末尾不用加'/'
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com")

// 容許前端帶認證cookie:啓用此項後,上面的域名不能爲'*',必須指定具體的域名,不然瀏覽器會提示
response.setHeader("Access-Control-Allow-Credentials", "true")

// 提示OPTIONS預檢時,後端須要設置的兩個經常使用自定義頭
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With")
複製代碼

3. postMessage

能夠先看一下概念和基礎 - MDN:postMessage

postMessage是html5引入的API,容許來自不一樣源的腳本採用異步方式進行有限的通訊,能夠實現跨文本檔、多窗口、跨域消息傳遞。能夠更方便、有效、安全的解決如下問題:
a.) 頁面和其打開的新窗口的數據傳遞 (window.open
b.) 多窗口之間消息傳遞
c.) 頁面與嵌套的iframe消息傳遞
d.) 上面三個場景的跨域數據傳遞

用法:

// 發送數據
otherWindow.postMessage(message, targetOrigin, [transfer])
// otherWindow: 其餘窗口的一個引用,好比iframe的contentWindow屬性、執行window.open返回的窗口對象、或者是命名過或數值索引的window.frames
// message: 將要發送到otherWindow的數據。它將會被結構化克隆算法序列化。這意味着你能夠不受什麼限制的將數據對象安全的傳送給目標窗口而無需本身序列化
// targetOrigin: 指定哪些源的窗口能接收到消息事件,其值能夠是字符串"*"(表示無限制)或者一個URI。在發送消息的時候,若是目標窗口的協議、主機地址或端口這三者的任意一項不匹配targetOrigin提供的值,那麼消息就不會被髮送。最好不要直接填"*"來代替一個明確的地址
// transfer: 是一串和message同時傳遞的Transferable對象. 這些對象的全部權將被轉移給消息的接收方,而發送一方將再也不保有全部權
複製代碼
// 接收數據
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
    // var origin = event.origin || event.originalEvent.origin; 
    var origin = event.origin
    if (origin !== "http://xxx.xxx:9090") {
        return;
    }
    // event.data 從其餘窗口傳過來的數據
    console.log(event)
    // do something
}
複製代碼

舉個栗子: 情景 - a頁面中嵌套b頁面,進行消息傳遞

// a頁面 http://www.catone.com/a.html
<iframe id="iframe" src="http://www.cattwo.com/b.html"></iframe>
<script>       
    let iframe = document.getElementById('iframe')
    iframe.onload = function() {
        let data = {
            say: 'hello'
        }
        // 向cattwo傳送跨域數據
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.cattwo.com');
    }

    // 接收cattwo返回的數據
    window.addEventListener('message', function(e) {
        console.log(`data from cattwo ---> ${e.data}`)
    }, false)
</script>
複製代碼
// b頁面 http://www.cattwo.com/b.html
<script>
    // 接收catone的數據
    window.addEventListener('message', function(e) {
        console.log(`data from catone ---> ${e.data}`)

        let data = JSON.parse(e.data)
        if (data) {
            console.log(data)
            data.good = 'bye'
            // 處理後再發回catone
            window.parent.postMessage(JSON.stringify(data), 'http://www.catone.com')
        }
    }, false)
</script>
複製代碼

4. nginx

nginx是一個高性能的HTTP的反向代理服務器,也是一個通用的TCP/UDP代理服務器
他的做用有:解決跨域、請求過濾、配置gzip、負載均衡、做爲靜態資源服務器等

nginx的使用在這裏就不詳細講了,建議能夠去查閱一下相關的知識,在平時開發中仍是挺有用的

實現思路:經過nginx配置開啓一個代理服務器作跳板機,反向代理訪問cattwo服務,而且順便修改cookie中domain信息,方便當前域cookie寫入,實現跨域登陸

#nginx
// 監聽 www.catone.com:9090
server {
    listen       9090;
    server_name  www.catone.com;

    location / {
        proxy_pass   http://www.cattwo.com:8080;  #反向代理
        proxy_cookie_domain www.cattwo.com www.catone.com; #修改cookie裏域名
        index  index.html index.htm;

        # 當用webpack-dev-server等中間件代理接口訪問nignx時,此時無瀏覽器參與,故沒有同源限制,下面的跨域配置可不啓用
        add_header Access-Control-Allow-Origin http://www.cattwo.com;  #當前端只跨域不帶cookie時,可爲*
        add_header Access-Control-Allow-Credentials true;
    }
}
複製代碼

前端代碼示例:

let xhr = new XMLHttpRequest()

// 瀏覽器是否讀寫cookie
xhr.withCredentials = true

// 訪問nginx中的代理服務器
xhr.open('get', 'http://www.catone.com:9090?hello=world', true)
xhr.send()
複製代碼

後臺node代碼示例:

var http = require('http')
var server = http.createServer()
var qs = require('querystring')

server.on('request', function(req, res) {
    var params = qs.parse(req.url)

    // 寫入cookie
    res.writeHead(200, {
        'Set-Cookie': 'cat=cat;Path=/;Domain=www.domain2.com;HttpOnly'
    })

    res.write(JSON.stringify(params))
    res.end()
})

server.listen('8080')
複製代碼

5. webSocket

WebSocket protocol是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通訊,同時容許跨域通信,是server push技術的一種很好的實現
這裏使用Socket.io示例一下,若是有時間的話,原生仍是能夠看一下的

前端代碼:

<input type="text" id="input">

<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<script>
var socket = io('http://www.cattwo.com:8080');

// 鏈接成功處理
socket.on('connect', function() {
    // 監聽服務端消息
    socket.on('message', function(msg) {
        console.log('data from server: ---> ' + msg);
    })

    // 監聽服務端關閉
    socket.on('disconnect', function() { 
        console.log('Server socket has closed'); 
    }
})

document.getElementsById('input').onblur = function() {
    socket.send(this.value)
}
</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');

// 監聽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.'); 
    });
});
複製代碼

未完待續

參考:segmentfault.com/a/119000001…

相關文章
相關標籤/搜索