相關前臺跨域的解決方式

李德東發表於2018-09-02

title: 前端跨域處理方式
date: 2018-07-08 00:37:29
categories:

  • Web
  • 前端

tags:

  • 跨域
  • cors

關於跨域請求解覺方案問題

關於瀏覽器跨域問題,專案中也遇到了,看了專案上一些程式碼的處理方式,感覺存在不少不大完善的地方,因此對於跨域,想好好梳理一下,但是最近一直在忙,因此週六抽出一天的時間了學習消化做了寫筆記。

跨域的概念

跨域和同源問題

跨域實質就是跨域名、跨埠、跨協議。

同源就是同域名、同埠、同協議。

假如一個網址是  http://baidu.com:8080?user=name&pwd=password
http://   是協議   
baidu.com  是域名(注意:前面加上“wwww”即www.baidu.com不是域名)
8080  是埠    
user=name&pwd=password   是地址帶的引數

引用地址 https://www.cnblogs.com/shirly77/p/6440635.html?utm_source=itdadao&utm_medium=referral

跨域的解決方法

跨域的解決方式主要有以下幾種方式:1.jsonp(侷限性比較大,缺點比較多,下面有具體介紹)。2.Cors 3.使用代理 4.websocket跨域

1.JSONP

在js中,我們直接用XMLHttpRequest請求不同域上的資料時,是不可以的。但是,在頁面上引入不同域上的js指令碼檔案卻是可以的,jsonp正是利用這個特性來實現的。

比如,有個a.html頁面,它裡面的程式碼需要利用ajax獲取一個不同域上的json資料,假設這個json資料地址是http://example.com/data,那麼a.html中的程式碼就可以這樣:

<script>
function doSomething(jsonData){
    
}
</script>
<script src="http://example.com/data?callback=doSomething"></script>

因為是當做一個js檔案來引入的,所以http://example.com/data返回的必須是一個能執行的js檔案

這樣jsonp的原理就很清楚了,通過script標籤引入一個js檔案,這個js檔案載入成功後會執行我們在url引數中指定的函式,並且會把我們需要的json資料作為引數傳入。所以jsonp是需要伺服器端的頁面進行相應的配合的。

基於JSONP的實現原理,所以JSONP只能是“GET”請求,不能進行較為複雜的POST和其它請求,所以遇到那種情況,就得參考下面的CORS解決跨域了(所以如今它也基本被淘汰了)

2.Cros(跨域資源共享)解決跨域問題

Cross-origin resource sharingCORS需要瀏覽器和伺服器同時支援。目前,所有瀏覽器都支援該功能,IE瀏覽器不能低於IE10。

整個CORS通訊過程,都是瀏覽器自動完成,不需要使用者參與。對於開發者來說,CORS通訊與同源的AJAX通訊沒有差別,程式碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動新增一些附加的頭資訊,有時還會多出一次附加的請求,但使用者不會有感覺。

因此,實現CORS通訊的關鍵是伺服器。只要伺服器實現了CORS介面,就可以跨源通訊。

瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。

只要同時滿足以下兩大條件,就屬於簡單請求。

(1) 請求方法是以下三種方法之一:
     HEAD
     GET
     POST
(2)HTTP的頭資訊不超出以下幾種欄位:
     Accept
     Accept-Language
     Content-Language
     Last-Event-ID
     Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
凡是不同時滿足上面兩個條件,就屬於非簡單請求。

1.對於簡單請求,瀏覽器直接發出CORS請求。具體來說,就是在頭資訊之中,增加一個Origin欄位。

2.非簡單請求是那種對伺服器有特殊要求的請求,比如請求方法是PUT或DELETE,或者Content-Type欄位的型別是application/json。

非簡單請求的CORS請求,會在正式通訊之前,增加一次HTTP查詢請求,稱為"預檢"請求(preflight).也就是說在正式的請求後臺之前,瀏覽器會先發一個預請求options表示這個請求是用來詢問的。

頭資訊裡面,關鍵欄位是Origin,表示請求來自哪個源。

除了Origin欄位,"預檢"請求的頭資訊包括兩個特殊欄位。

(1)Access-Control-Request-Method

該欄位是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法,上例是PUT。

(2)Access-Control-Request-Headers

該欄位是一個逗號分隔的字串,指定瀏覽器CORS請求會額外傳送的頭資訊欄位,上例是X-Custom-Header。

這是一段http對應的響應

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

上面的HTTP迴應中,關鍵的是Access-Control-Allow-Origin欄位,表示http://api.bob.com可以請求資料。該欄位也可以設為星號,表示同意任意跨源請求。

伺服器迴應的其他CORS相關欄位如下。

Access-Control-Allow-Methods: GET, POST, PUT
該欄位必需,它的值是逗號分隔的一個字串,表明伺服器支援的所有跨域請求的方法。注意,返回的是所有支援的方法,而不單是瀏覽器請求的那個方法。這是為了避免多次"預檢"請求。


Access-Control-Allow-Headers: X-Custom-Header
如果瀏覽器請求包括Access-Control-Request-Headers欄位,則Access-Control-Allow-Headers欄位是必需的。它也是一個逗號分隔的字串,表明伺服器支援的所有頭資訊欄位,不限於瀏覽器在"預檢"中請求的欄位。

Access-Control-Allow-Credentials: true
該欄位可選。它的值是一個布林值,表示是否允許傳送Cookie。如果伺服器不要瀏覽器傳送Cookie,刪除該欄位即可。

Access-Control-Max-Age: 1728000
該欄位可選,用來指定本次預檢請求的有效期,單位為秒。上面結果中,有效期是20天(1728000秒),即允許快取該條迴應1728000秒(即20天),在此期間,不用發出另一條預檢請求。

摘自阮一峰部落格 http://www.ruanyifeng.com/blog/2016/04/cors.html

3.使用代理

3.1nginx反向代理介面跨域

實現思路:通過nginx配置一個代理伺服器(域名與domain1相同,埠不同)做跳板機,反向代理訪問domain2介面,並且可以順便修改cookie中domain資訊,方便當前域cookie寫入,實現跨域登入。

nginx具體配置:

#proxy伺服器
server {
    listen       81;
    server_name  www.domain1.com;

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

        # 當用webpack-dev-server等中介軟體代理介面訪問nignx時,此時無瀏覽器參與,故沒有同源限制,下面的跨域配置可不啟用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #當前端只跨域不帶cookie時,可為*
        add_header Access-Control-Allow-Credentials true;
    }
}

1.) 前端程式碼示例:

var xhr = new XMLHttpRequest();

// 前端開關:瀏覽器是否讀寫cookie
xhr.withCredentials = true;

// 訪問nginx中的代理伺服器
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();

2.) Nodejs後臺示例:

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

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

    // 向前臺寫cookie
    res.writeHead(200, {
        'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'   // HttpOnly:指令碼無法讀取
    });

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

server.listen('8080');
console.log('Server is running at port 8080...');

3.2Nodejs中介軟體代理跨域

node中介軟體實現跨域代理,原理大致與nginx相同,都是通過啟一個代理伺服器,實現資料的轉發,也可以通過設定cookieDomainRewrite引數修改響應頭中cookie中域名,實現當前域的cookie寫入,方便介面登入認證。

3.2.1非vue框架的跨域(2次跨域)

利用node + express + http-proxy-middleware搭建一個proxy伺服器。

1.)前端程式碼示例:

var xhr = new XMLHttpRequest();

// 前端開關:瀏覽器是否讀寫cookie
xhr.withCredentials = true;

// 訪問http-proxy-middleware代理伺服器
xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);
xhr.send();

2.)中介軟體伺服器:

var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();

app.use('/', proxy({
    // 代理跨域目標介面
    target: 'http://www.domain2.com:8080',
    changeOrigin: true,

    // 修改響應頭資訊,實現跨域並允許帶cookie
    onProxyRes: function(proxyRes, req, res) {
        res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');
        res.header('Access-Control-Allow-Credentials', 'true');
    },

    // 修改響應資訊中的cookie域名
    cookieDomainRewrite: 'www.domain1.com'  // 可以為false,表示不修改
}));

app.listen(3000);
console.log('Proxy server is listen at port 3000...');

3.)Nodejs後臺同(nginx)

3.2.2vue框架的跨域(1次跨域)

利用node + webpack + webpack-dev-server代理介面跨域。在開發環境下,由於vue渲染服務和介面代理服務都是webpack-dev-server同一個,所以頁面與代理介面之間不再跨域,無須設定headers跨域資訊了。

webpack.config.js部分配置:

module.exports = {
    entry: {},
    module: {},
    ...
    devServer: {
        historyApiFallback: true,
        proxy: [{
            context: '/login',
            target: 'http://www.domain2.com:8080',  // 代理跨域目標介面
            changeOrigin: true,
            secure: false,  // 當代理某些https服務報錯時用
            cookieDomainRewrite: 'www.domain1.com'  // 可以為false,表示不修改
        }],
        noInfo: true
    }
}

4.WebSocket協議跨域

WebSocket protocol是HTML5一種新的協議。它實現了瀏覽器與伺服器全雙工通訊,同時允許跨域通訊,是server push技術的一種很好的實現。
原生WebSocket API使用起來不太方便,我們使用Socket.io,它很好地封裝了webSocket介面,提供了更簡單、靈活的介面,也對不支援webSocket的瀏覽器提供了向下相容。

1.)前端程式碼:

<div>user input:<input type="text"></div>
<script src="./socket.io.js"></script>
<script>
var socket = io('http://www.domain2.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.getElementsByTagName('input')[0].onblur = function() {
    socket.send(this.value);
};
</script>

2.)Nodejs socket後臺:

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.'); 
    });
});

跨域講解 https://segmentfault.com/a/1190000011145364

相關文章