前端跨域的幾種解決方案

什么是跨域

跨域,是指瀏覽器不能執(zhí)行其它網(wǎng)站的腳本,它是由瀏覽器的同源策略造成的,防止惡意攻擊,是瀏覽器對javascript實(shí)施的安全限制.

什么是同源策略

同源策略是一個重要的安全策略,它用于限制一個origin的文檔或者它加載的腳本如何能與另一個源的資源進(jìn)行交互,它能幫助阻隔惡意文檔,減少可能被攻擊的媒體.

同源策略限制的行為

1.Cookie、LocalStorage和IndexDB無法讀取
2.DOM和JS對象無法獲取
3.Ajax請求不能發(fā)送

什么是同源

同源就是指域名、協(xié)議、端口均為相同.

地址

http(協(xié)議)://store.company.com(域名):81(端口)

URL 結(jié)果 原因
http://store.company.com/dir2/other.html 同源 只有路徑不同
http://store.company.com/dir/inner/another.html 同源 只有路徑不同
https://store.company.com/secure.html 失敗 協(xié)議不同
http://store.company.com:81/dir/etc.html 失敗 端口不同(http://默認(rèn)端口80)
http://news.company.com/dir/other.html 失敗 域名不同

實(shí)現(xiàn)跨域的幾種方式

1.jsonp(只能發(fā)送get請求)

jsonp是JSON with padding簡寫,實(shí)現(xiàn)原理,動態(tài)創(chuàng)建script標(biāo)簽,然后利用script里的src不受同源策略約束通過get請求獲取數(shù)據(jù).利用callback回調(diào)函數(shù)接收服務(wù)器響應(yīng)數(shù)據(jù).

  • 代碼示例
var script=document.createElement('script');
script.src="https://api.test.com/v2/list/search?q=javascript&count=1&callback=handleResponse";
document.body.insertBefore(script,document.body.firstChild);
//接收回調(diào)
function handleResponse(response){
    console.log(response)
}

2.跨域資源共享(CORS)

CORS是W3C的標(biāo)準(zhǔn),全稱是"跨域資源共享".它允許瀏覽器向跨域的服務(wù)器,發(fā)出XMLHttpRequest請求,從而克服了AJAX只能同源的使用限制.

cors請求分成兩類

1.簡單請求
2.非簡單請求

簡單請求(http方法與請求頭信息的結(jié)合)

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

對于簡單請求就是瀏覽器直接發(fā)出CORS,具體來說就是請求頭添加Origin字段

GET /cors HTTP/1.1
Origin: http://api.bob.com //本次請求來自于哪個域(協(xié)議 + 域名 + 端口)服務(wù)器根據(jù)這個值,決定是否同意這次請求.
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

如果Origin指定的域名在許可范圍內(nèi),服務(wù)器返回的響應(yīng),會多出幾個頭信息響應(yīng)字段

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

CORS設(shè)置響應(yīng)頭字段

1.Access-Control-Allow-Origin:必選

該字段得值要么是請求時Origin字段的值,要么是一個*,表示接受任意域名的請求.

2.Access-Control-Allow-Credentials:可選

該字段的值是一個布爾值,表示是否允許發(fā)送cookie,默認(rèn)情況下,Cookie不包括在CORS請求中,設(shè)置為true表示服務(wù)器明確許可,瀏覽器可以把Cookie包含在請求中,一起發(fā)送給服務(wù)器,這個值也只能設(shè)置為true,如果服務(wù)器不要瀏覽器發(fā)送Cookie,不發(fā)送該字段即可.

3.Access-Control-Expose-Headers

該字段可選.CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個服務(wù)器返回的基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必須在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值

  • 代碼示例
//前端代碼
var xhr=new XMLHttpRequest();
xhr.withCredentials=true; //前端設(shè)置是否帶cookie
xhr.open('post', 'http://www.bai.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);
    }
};

//服務(wù)器端代碼node
var http = require('http');
var server = http.createServer();
var qs = require('querystring');

server.on('request', function(req, res) {
    var postData = '';

    // 數(shù)據(jù)塊接收中
    req.addListener('data', function(chunk) {
        postData += chunk;
    });

    // 數(shù)據(jù)接收完畢
    req.addListener('end', function() {
        postData = qs.parse(postData);

        // 跨域后臺設(shè)置
        res.writeHead(200, {
            'Access-Control-Allow-Credentials': 'true',     // 后端允許發(fā)送Cookie
            'Access-Control-Allow-Origin': 'http://www.bai.com',    // 允許訪問的域(協(xié)議+域名+端口)
            /* 
             * 此處設(shè)置的cookie還是domain2的而非domain1,因?yàn)楹蠖艘膊荒芸缬驅(qū)慶ookie(nginx反向代理可以實(shí)現(xiàn)),
             * 但只要domain2中寫入一次cookie認(rèn)證,后面的跨域接口都能從domain2中獲取cookie,從而實(shí)現(xiàn)所有的接口都能跨域訪問
             */
            'Set-Cookie': 'l=a123456;Path=/;Domain=http://www.bai.com;HttpOnly'  // HttpOnly的作用是讓js無法讀取cookie
        });

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

server.listen('8080');
非簡單請求

凡是不同時滿足上面兩個條件,就屬于非簡單請求,非簡單請求是對服務(wù)器提出特殊的要求,比如請求方法是PUT或DELETE,或者Content-Type字段是application/json.非簡單請求的CORS請求,會在正式通信之前增加一次http查詢請求,稱為"預(yù)檢"請求,瀏覽器先詢問服務(wù)器,當(dāng)前網(wǎng)頁所在的域名是否在服務(wù)器的許可名單之中,以及可以使用哪些 HTTP 動詞和頭信息字段。只有得到肯定答復(fù),瀏覽器才會發(fā)出正式的XMLHttpRequest請求,否則就報錯。這是為了防止這些新增的請求,對傳統(tǒng)的沒有 CORS 支持的服務(wù)器形成壓力,給服務(wù)器一個提前拒絕的機(jī)會,這樣可以防止服務(wù)器大量收到DELETE和PUT請求,這些傳統(tǒng)的表單不可能跨域發(fā)出的請求。

//前端代碼
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

//預(yù)測http請求頭
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

"預(yù)檢"請求用的請求方法是OPTIONS,表示這個請求是用來詢問的。頭信息里面,關(guān)鍵字段是Origin,表示請求來自哪個源。

除了Origin字段,“預(yù)檢”請求的頭信息包括兩個特殊字段。

1.Access-Control-Request-Method

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

2.Access-Control-Request-Headers

該字段是一個逗號分隔的字符串,指定瀏覽器 CORS 請求會額外發(fā)送的頭信息字段,上例是X-Custom-Header

服務(wù)器收到“預(yù)檢”請求以后,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,確認(rèn)允許跨源請求,就可以做出回應(yīng)。

3.document.domain+iframe跨域

此方案僅限主域相同,子域不同的跨域應(yīng)用場景.實(shí)現(xiàn)原理兩個頁面都通過js強(qiáng)制設(shè)置document.domain為基礎(chǔ)主域,就實(shí)現(xiàn)了同域.

  • 代碼示例
//父窗口
<iframe id="iframe" src="http://child.domain.com/child.html"></iframe>
<script>
    document.domain="domain.com"
    var user='admin'
</script>

//子窗口
<script>
    document.domain = 'domain.com';
    // 獲取父窗口中變量
    console.log('get js data from parent ---> ' + window.parent.user);
</script>

4.vue-cli+nginx代理跨域

利用webpack的webpack-dev-server,實(shí)現(xiàn)開發(fā)環(huán)境下,vue渲染服務(wù)和接口代理都是webpack-dev-server同一個,所以頁面與代理之間不需要跨域.

//vue.config.js
devServer:{
        open:true,
        compress:true,//對所有服務(wù)器啟用gzip壓縮
        port:5200,
        hot:true,
        host:'0.0.0.0',
        overlay:{ //錯誤遮罩
            warnings: true,
            errors: true   
        },
        proxy:{
            '/api':{
                target: 'www.domain2.com:8080',
                changeOrigin: true,  //是否跨域
                pathRewrite:{
                    '^/api':'/api'
                }
            }
     }
//nginx
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;

        # 當(dāng)用webpack-dev-server等中間件代理接口訪問nignx時,此時無瀏覽器參與,故沒有同源限制,下面的跨域配置可不啟用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #當(dāng)前端只跨域不帶cookie時,可為*
        add_header Access-Control-Allow-Credentials true;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容