這篇文章我們聊一聊CORS跨域,它的全稱是"跨域資源共享"(Cross-origin resource sharing)。
在之前的文章中我們已經詳細介紹了如何使用JSONP進行接口跨域請求,如果不了解的可以參考作者之前的文章《詳解前端jquery中的JSONP如何實現跨域請求》,相信一定難不倒聰明的你。
那么CORS跨域方案和jsonp跨域有何不同呢?讀完這篇文章你肯定能找到答案!
跨域案例
頁面地址:http://client.cors.com:8000/greeter.html,代碼如下:
圖1
服務器接口地址:http://server.cors.com:3000/data,服務器代碼如下:
圖2
很明顯,當頁面在請求服務器接口時會發生跨域現象,如下:
圖3
我們去瀏覽器Network中看一下請求信息,
圖4
如圖4所示,響應為200,response Headers信息也很正常,這說明在跨域的情況下請求依然可以到達服務器,并且服務器能夠正常響應,但是由于瀏覽器的同源策略并沒有把返回的數據給到頁面。
那么如何讓頁面在跨域的情況下獲取到數據呢?我們回看圖3,似乎在說少了一個Access-Control-Allow-Origin頭,那么我們在服務器代碼中加一下。
圖5
現在刷新頁面,服務器返回的數據就能正常打印出來了。
'Access-Control-Allow-Origin': '*'表示接受任意域名的請求
攜帶憑證
在跨域的情況,服務器有時依然需要鑒權。通常服務器鑒權都是從cookie中獲取信息來判斷客戶端的身份,那么跨域的情況下請求還能傳遞cookie嗎?當然能,但是cookie會遵守同源策略!
1)服務器設置cookie
圖6
圖7
如果需要服務器設置cookie,必須設置Access-Control-Allow-Credentials: true,否則會出現如下錯誤。
圖8
頁面中的xhr對象也必須設置屬性withCredentials=true。
此時刷新頁面,在頁面控制臺中通過document.cookie查看server=123,你會發現server端設置的cookie并不存在。上面已經說了cookie會遵循同源策略,服務器的域名是server.cors.com,所以服務器設置的cookie應該是在這個域名下,并不會在頁面的域名(client.cors.com)下,那如何驗證服務器設置cookie成功呢?
圖9
- 先打開接口頁面,這個頁面是同源的;
- 回到請求頁面,刷新;
- 再回到接口頁面,在控制臺通過document.cookie就可以拿到服務器設置的cookie。
2)頁面設置cookie
如果主域名相同,比如現在的例子,主域名都是cors.com,那么就可以把cookie設置在這個主域名下。
document.cookie="client=1;domain=cors.com;"
這樣服務器就能獲取到頁面設置的cookie。
如果連主域名都不一樣,那就不要妄想在頁面上設置cookie讓服務器獲取到。這種情況下,服務器該如何鑒權呢?
第一種方式是讓后端把跨域的接口代理成同域的,這樣我們的后端可以拿到cookie,在他那把cookie轉發給跨域服務。
圖10
第二種方式是頁面發送請求時在header中附加一個token,用于鑒權,
圖11
當刷新頁面時,頁面控制臺又報錯了。
圖12
提示設置Access-Control-Allow-Headers,那我們就設置一下。
圖13
再刷新頁面,請求正常了,服務端也能拿到token的值了。
簡單請求與非簡單請求
圖13中我們拿到了token的值,此時我們再去瞧瞧瀏覽器中的Network,會發現頁面發送了兩個請求,第一個請求的method是OPTIONS,這是怎么回事呢?
圖14
原來cors跨域也分簡單請求和非簡單請求。
簡單請求條件如下:
- 請求方法是必須是HEAD/GET/POST三種方法之一;
- HTTP的頭信息不超出這幾種字段:Accept/Accept-Language/Content-Language/Content-Language/Last-Event-ID/Content-Type,Content-Type只限于三個值Application/x-www-form-urlencoded、multipart/form-data、text/plain。
圖11中我們設置了token請求頭,顯然不滿足以上條件,所以是非簡單請求。非簡單請求的CORS請求會在正式通信之前增加一次HTTP查詢請求,稱為預檢請求(preflight)。瀏覽器先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可以使用哪些HTTP動詞和頭信息字段。只有得到肯定答復,瀏覽器才會發出正式的XMLHttpRequest請求,否則就報錯。預檢請求用的請求方法是OPTIONS,表示這個請求是用來詢問的。
我們現在嘗試發送一次PUT請求,看看會有什么現象?
圖15
不出所料,瀏覽器再次報錯!
圖16
提示我們設置Access-Control-Allow-Methods,那就只能設置了!
圖17
再次刷新頁面,現在請求正常了!我們回頭看一下預檢請求,
圖18
不得不說,瀏覽器在訪問跨域接口時真的是非常的小心,當然這一切都是為了安全考慮。即使這樣,現在網絡中也不乏XSS、CSRF等攻擊。
總結
17年夏天作者去頭條面試,當時筆試有這么一道題“如果瀏覽器請求跨域,那么這個請求還能到達服務器嗎?如果能,服務器會返回什么?”。如果你讀完本文,并且能充分理解,我相信這道題肯定不在話下。跨域在業務中經常遇到,大部分后端同學并不知道什么叫跨域,更不清楚該如何解決。作為前端的你,這時候就可以大顯身手了!