這篇文章我們聊一聊CORS跨域,它的全稱是"跨域資源共享"(Cross-origin resource sharing)。
在之前的文章中我們已經(jīng)詳細(xì)介紹了如何使用JSONP進行接口跨域請求,如果不了解的可以參考作者之前的文章《詳解前端jquery中的JSONP如何實現(xiàn)跨域請求》,相信一定難不倒聰明的你。
那么CORS跨域方案和jsonp跨域有何不同呢?讀完這篇文章你肯定能找到答案!
跨域案例
頁面地址:http://client.cors.com:8000/greeter.html,代碼如下:

圖1
服務(wù)器接口地址:http://server.cors.com:3000/data,服務(wù)器代碼如下:

圖2
很明顯,當(dāng)頁面在請求服務(wù)器接口時會發(fā)生跨域現(xiàn)象,如下:

圖3
我們?nèi)g覽器Network中看一下請求信息,

圖4
如圖4所示,響應(yīng)為200,response Headers信息也很正常,這說明在跨域的情況下請求依然可以到達服務(wù)器,并且服務(wù)器能夠正常響應(yīng),但是由于瀏覽器的同源策略并沒有把返回的數(shù)據(jù)給到頁面。
那么如何讓頁面在跨域的情況下獲取到數(shù)據(jù)呢?我們回看圖3,似乎在說少了一個Access-Control-Allow-Origin頭,那么我們在服務(wù)器代碼中加一下。

圖5
現(xiàn)在刷新頁面,服務(wù)器返回的數(shù)據(jù)就能正常打印出來了。
'Access-Control-Allow-Origin': '*'表示接受任意域名的請求
攜帶憑證
在跨域的情況,服務(wù)器有時依然需要鑒權(quán)。通常服務(wù)器鑒權(quán)都是從cookie中獲取信息來判斷客戶端的身份,那么跨域的情況下請求還能傳遞cookie嗎?當(dāng)然能,但是cookie會遵守同源策略!
1)服務(wù)器設(shè)置cookie

圖6

圖7
如果需要服務(wù)器設(shè)置cookie,必須設(shè)置Access-Control-Allow-Credentials: true,否則會出現(xiàn)如下錯誤。

圖8
頁面中的xhr對象也必須設(shè)置屬性withCredentials=true。
此時刷新頁面,在頁面控制臺中通過document.cookie查看server=123,你會發(fā)現(xiàn)server端設(shè)置的cookie并不存在。上面已經(jīng)說了cookie會遵循同源策略,服務(wù)器的域名是server.cors.com,所以服務(wù)器設(shè)置的cookie應(yīng)該是在這個域名下,并不會在頁面的域名(client.cors.com)下,那如何驗證服務(wù)器設(shè)置cookie成功呢?

圖9
- 先打開接口頁面,這個頁面是同源的;
- 回到請求頁面,刷新;
- 再回到接口頁面,在控制臺通過document.cookie就可以拿到服務(wù)器設(shè)置的cookie。
2)頁面設(shè)置cookie
如果主域名相同,比如現(xiàn)在的例子,主域名都是cors.com,那么就可以把cookie設(shè)置在這個主域名下。
document.cookie="client=1;domain=cors.com;"
這樣服務(wù)器就能獲取到頁面設(shè)置的cookie。
如果連主域名都不一樣,那就不要妄想在頁面上設(shè)置cookie讓服務(wù)器獲取到。這種情況下,服務(wù)器該如何鑒權(quán)呢?
第一種方式是讓后端把跨域的接口代理成同域的,這樣我們的后端可以拿到cookie,在他那把cookie轉(zhuǎn)發(fā)給跨域服務(wù)。

圖10
第二種方式是頁面發(fā)送請求時在header中附加一個token,用于鑒權(quán),

圖11
當(dāng)刷新頁面時,頁面控制臺又報錯了。

圖12
提示設(shè)置Access-Control-Allow-Headers,那我們就設(shè)置一下。

圖13
再刷新頁面,請求正常了,服務(wù)端也能拿到token的值了。
簡單請求與非簡單請求
圖13中我們拿到了token的值,此時我們再去瞧瞧瀏覽器中的Network,會發(fā)現(xiàn)頁面發(fā)送了兩個請求,第一個請求的method是OPTIONS,這是怎么回事呢?

圖14
原來cors跨域也分簡單請求和非簡單請求。
簡單請求條件如下:
- 請求方法是必須是HEAD/GET/POST三種方法之一;
- HTTP的頭信息不超出這幾種字段:Accept/Accept-Language/Content-Language/Content-Language/Last-Event-ID/Content-Type,Content-Type只限于三個值A(chǔ)pplication/x-www-form-urlencoded、multipart/form-data、text/plain。
圖11中我們設(shè)置了token請求頭,顯然不滿足以上條件,所以是非簡單請求。非簡單請求的CORS請求會在正式通信之前增加一次HTTP查詢請求,稱為預(yù)檢請求(preflight)。瀏覽器先詢問服務(wù)器,當(dāng)前網(wǎng)頁所在的域名是否在服務(wù)器的許可名單之中,以及可以使用哪些HTTP動詞和頭信息字段。只有得到肯定答復(fù),瀏覽器才會發(fā)出正式的XMLHttpRequest請求,否則就報錯。預(yù)檢請求用的請求方法是OPTIONS,表示這個請求是用來詢問的。
我們現(xiàn)在嘗試發(fā)送一次PUT請求,看看會有什么現(xiàn)象?

圖15
不出所料,瀏覽器再次報錯!

圖16
提示我們設(shè)置Access-Control-Allow-Methods,那就只能設(shè)置了!

圖17
再次刷新頁面,現(xiàn)在請求正常了!我們回頭看一下預(yù)檢請求,

圖18
不得不說,瀏覽器在訪問跨域接口時真的是非常的小心,當(dāng)然這一切都是為了安全考慮。即使這樣,現(xiàn)在網(wǎng)絡(luò)中也不乏XSS、CSRF等攻擊。
總結(jié)
17年夏天作者去頭條面試,當(dāng)時筆試有這么一道題“如果瀏覽器請求跨域,那么這個請求還能到達服務(wù)器嗎?如果能,服務(wù)器會返回什么?”。如果你讀完本文,并且能充分理解,我相信這道題肯定不在話下。跨域在業(yè)務(wù)中經(jīng)常遇到,大部分后端同學(xué)并不知道什么叫跨域,更不清楚該如何解決。作為前端的你,這時候就可以大顯身手了!