前端跨域 問題及多種解決方案,你get到了嗎

前端跨域 問題及多種解決方案,你get到了嗎

原理:script標籤不受同源策略的影響,把鏈接掛在script標籤上,通過回調函數傳遞數據
優點:兼容性好,前後端分離
缺點:僅支持get請求,安全性較差,容易引發xss攻擊

/* server.js */
const express = require('express');
const app = express();
app.get('/say', (req, res) => {
    let { wd, cb } = req.query;
    console.log('客戶端:' + wd);//客戶端:Hello
    res.end(`${cb}('服務端:Hi')`)
})
app.listen(3333, () => {
    console.log("Server start http://localhost:3333");
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
<script>
    function jsonp({ url, params, cb }) {
        /* 返回一個Promise */
        return new Promise((resolve) => {
            /* 創建script */
            let script = document.createElement('script');
            /* 全局cb函數 */
            window[cb] = function (data) {
                resolve(data);/* 執行返回的數據 */
                document.body.remove(script);/* 執行完畢刪除標籤 */
            }
            /* 轉換url */
            let arr = [];
            params = { ...params, cb };
            for (let key in params)
                arr.push(`${key}=${params[key]}`);
            //http://localhost:3333/say?wd=Hello&cb=show
            script.src = `${url}?${arr.join('&')}`;
            document.body.appendChild(script);
        })
    }
    jsonp({
        url: 'http://localhost:3333/say',
        params: { wd: 'Hello' },
        cb: 'show'
    }).then(data => console.log(data));//服務端:Hi
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

2. CORS

原理:通過在服務端添加白名單,放寬對請求源的限制,從而實現跨域

優點:可以發任意請求
缺點:上是複雜請求的時候得先做一個預檢,再發真實的請求,發了兩次請求會有性能上的損耗。
3333端口下的indexhtml發出ajax請求

<!-- html在3333端口服務器上 -->
<script>
    let xhr = new XMLHttpRequest;
    /* 設置cookie 需要Access-Control-Allow-Credential設置 */
    document.cookie = "name=aeipyuan";
    xhr.withCredentials = true;
    /* 請求4444端口數據 */
    xhr.open('put', 'http://localhost:4444/getData', true);
    /* 需要設置Access-Control-Allow-Headers */
    xhr.setRequestHeader('name', 'A');
    xhr.send();
    xhr.onreadystatechange = function (e) {
        if (xhr.readyState === 4) {
            if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                console.log(xhr.response);//4444已收到
                // 需要設置Access-Control-Expose-Headers
                console.log(xhr.getResponseHeader('name'));//mike
            }
        }
    }
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

4444端口服務器對數據進行處理

const express = require('express');
const app = express();
/* 設置白名單 */
let writeList = ['http://localhost:3333'];
app.use((req, res, next) => {
    /* 獲取請求源 */
    let { origin } = req.headers;
    /* 判斷請求源 */
    if (writeList.includes(origin)) {
        /* 允許origin訪問 */
        res.setHeader('Access-Control-Allow-Origin', origin)
        /* 允許哪個頭 */
        res.setHeader('Access-Control-Allow-Headers', 'name')
        /* 允許哪個方法 */
        res.setHeader('Access-Control-Allow-Methods', 'PUT')
        /* 允許攜帶cookie */
        res.setHeader('Access-Control-Allow-Credentials', true)
        /* 預檢的存活時間 */
        res.setHeader('Access-Control-Allow-Max-Age', 6)
        /* 允許前端獲取哪個頭 */
        res.setHeader('Access-Control-Expose-Headers', 'name')
        /* OPTIONS請求不做處理 */
        if (req.method === 'OPTIONS') {
            res.end();
        }
    }
    next();
})
app.put('/getData', (req, res) => {
    console.log(req.headers);
    res.setHeader('name', 'B');
    res.send('4444已收到');
})
app.listen(4444, () => {
    console.log("Server start http://localhost:4444");
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

3. postMessage實現跨域

原理:將另一個域的網頁放到iframe中,利用postMessage進行傳值
3333端口下的a.html

<div>AAAAAAA</div>
<iframe id="frame" src="http://localhost:4444/b.html" frameborder="0" onload="load()"></iframe>
<script>
    function load() {
        frame.contentWindow.postMessage(
            'Hello', 'http://localhost:4444/b.html'
        )
    }
    window.onmessage = function (e) {
        console.log('B說:' + e.data);//B說:Hi
    }
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

4444端口下的b.html

<div>BBBBBB</div>
<script>
    window.onmessage = function (e) {
        console.log('A說:' + e.data);//A說:Hello
        e.source.postMessage('Hi', e.origin);//給A發送消息
    }
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

4. window.name傳值

原理:先用iframe的window.name存儲跨域頁面要傳入的數據,然後將iframe的src屬性改變爲同源src,實現獲取name存儲的值

舉例:
A,B頁面在3333端口下,C頁面在4444端口下,目標是實現a頁面獲取c頁面數據
第一步,A頁面用iframe標籤引入C頁面
第二步,C頁面設置window.name=數據
第三步,將iframe的src由C頁面切換爲B頁面(同源)
第四步,獲取iframe頁面的window.name屬性
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
<!-- a.html -->
<iframe src="http://localhost:8082/c.html" frameborder="10" onload="load()" id="frame"></iframe>
<script>
    let first = true;
    function load() {
        if (first) {
            let frame = document.getElementById('frame');
            frame.src = "http://localhost:8081/b.html";//切換src
            first = false;
        } else {
            console.log(frame.contentWindow.name)
        }
    }
</script>
<!-- c.html -->
<script>
    window.onload = function () {
        window.name = "傳給A的數據"
    }
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

5. hash傳值

原理:和window.name相似,A使用iframe引入C並給C傳hash值,C使用iframe引入B並給B傳hash值,B和A同源,所以把hash值賦給A,A監聽到hash變化輸出hash值

<!-- a.html -->
<iframe id="frame" src="http://localhost:4444/c.html#A2C" frameborder="0"></iframe>
<script>
    window.onhashchange = function () {
        console.log('C傳入數據:' + location.hash)//C傳入數據:#C2B2A
    }
</script>
<!-- b.html -->
<script>
    window.parent.parent.location.hash = location.hash;/* 將hash傳給A */
</script>
<!-- c.html -->
<script>
    console.log('A傳入的數據:' + location.hash);//A傳入的數據:#A2C
    /* 創建iframe */
    let iframe = document.createElement('iframe');
    iframe.src = "http://localhost:3333/b.html#C2B2A";
    document.body.appendChild(iframe);
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

6. Websocket

原理:Websocket是HTML5的一個持久化的協議,它實現了瀏覽器與服務器的全雙工通信,同時也是跨域的一種解決方案。WebSocket和HTTP都是應用層協議,都基於 TCP 協議。但是 WebSocket 是一種雙向通信協議,在建立連接之後,WebSocket 的 server 與 client 都能主動向對方發送或接收數據。同時,WebSocket 在建立連接時需要藉助 HTTP 協議,連接建立好了之後 client 與 server 之間的雙向通信就與 HTTP 無關了
開啓服務

/* server.js */
let WebSocket = require('ws');
/* 創建服務 */
let wss = new WebSocket.Server({ port: 3333 });
wss.on('connection', ws => {
    ws.on('message', e => {
        console.log(e);//前端數據
        ws.send('後臺數據');
    })
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

傳送數據

<script>
    let socket = new WebSocket('ws://localhost:3333');
    socket.onopen = function () {
        socket.send('前端數據');
    }
    socket.onmessage = ({ data }) => {
        console.log(data);//後臺數據
    }
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

7. domain實現跨域

不同的頁面可能放在不同的服務器上,這些服務器域名不同,但是擁有相同的上級域名,比如id.qq.com、www.qq.com、user.qzone.qq.com,它們都有公共的上級域名qq.com ,設置頁面documen.domain爲上級域名即可實現跨域

<!-- http://a.aeipyuan.cn:3333/a.html  -->
<div>AAAAA</div>
<iframe id="frame" src="http://b.aeipyuan.cn:4444/b.html" frameborder="0" onload="load()"></iframe>
<script>
    document.domain = "aeipyuan.cn";
    function load() {
        console.log('b頁面數據:' + frame.contentWindow.a);//b頁面數據:100
    }
</script>
<!-- http://b.aeipyuan.cn:4444/b.html -->
<div>BBBBB</div>
<script>
    document.domain = "aeipyuan.cn";
    window.a = 100;
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

8. nginx實現

在conf文件配置以下參數,瞭解較淺,日後補充

location / {  
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
    if ($request_method = 'OPTIONS') {
        return 204;
    }
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

9. webpack配置實現跨域

//方式1  webpack.config.js
devServer: {
    port: 8081,
    contentBase: './build',
    hot: true,
    proxy: {
        '/api': {
            target: 'http://localhost:8888',//目標域
            pathRewrite: { '/api': '' }/* 路徑重寫 */
        }
    }
}
//方式2 server.js 直接在8888端口訪問webpack打包文件
let express = require('express');
let app = express();
/* webpack */
let webpack = require('webpack');
let config = require('../webpack.config.js');
let compiler = webpack(config);
//中間件
let middle = require('webpack-dev-middleware');
app.use(middle(compiler));
/* 請求 */
app.get('/user', (req, res) => {
    res.json({ name: "aeipyuan" })
})
app.listen(8888);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章