1. 程式人生 > >CORS——跨域請求那些事兒

CORS——跨域請求那些事兒

lob 有效期 site cati 項目 light 另一個 send 決定

在日常的項目開發時會不可避免的需要進行跨域操作,而在實際進行跨域請求時,經常會遇到類似 No ‘Access-Control-Allow-Origin‘ header is present on the requested resource.這樣的報錯。
技術分享
這樣的錯誤,一般是由於CORS跨域驗證機制設置不正確導致的,本文將詳細講解CORS跨域驗證機制的原理,讓您輕松掌握CORS跨域設置的使用方法,安全、方便的進行前端開發。

什麽是CORS

CORS(Cross-Origin Resource Sharing 跨源資源共享),當一個請求url的協議、域名、端口三者之間任意一與當前頁面地址不同即為跨域。

例如最常見的,在一個域名下的網頁中,調用另一個域名中的資源。
技術分享

相對於上面這種靜態的調用方式,還可以通過Ajax技術來動態發起跨域請求。例如如下的方式,利用XMLHttpRequest對象發送一個GET請求,獲取另一個域名下的圖片內容。

<!DOCTYPE html> 
<html>
    <head>CORS Test</head>
    <body>
        <div id="img_Div"></div>
    <script type="text/javascript">  
        //XmlHttpRequest對象  
        function createXmlHttpRequest(){  
            if(window.ActiveXObject){ //如果是IE瀏覽器  
                return new ActiveXObject("Microsoft.XMLHTTP");  
            }else if(window.XMLHttpRequest){ //非IE瀏覽器  
                return new XMLHttpRequest();  
            }  
        }  

        function getFile() {
            var img_Container = document.getElementById("img_Div");
            var xhr = createXmlHttpRequest();
            xhr.open(‘GET‘, ‘http://oss.youkouyang.com/1.jpg‘, true);
            xhr.setRequestHeader(‘Content-Type‘, ‘image/jpeg‘);
            xhr.responseType = "blob";
            xhr.onload = function() {
                if (this.status == 200) {
                    var blob = this.response;
                    var img = document.createElement("img");
                    img.onload = function(e) {
                        window.URL.revokeObjectURL(img.src); 
                    };
                    img.src = window.URL.createObjectURL(blob);
                    img_Container.appendChild(img);    
                }
            }
            xhr.send(null);
        }
    </script>
    <div class="row">
        <input type="button" onclick="getFile()" value="Get" />
    </div>
    </body>
</html>

CORS的作用

為了改善網絡應用程序,開發人員要求瀏覽器供應商允許跨域請求。跨域請求主要用於:

  • 調用XMLHttpRequest或fetchAPI通過跨站點方式訪問資源
  • 網絡字體,例如Bootstrap([email protected] 跨域調用字體)
  • 通過canvas標簽,繪制圖表和視頻。

CORS的安全隱患

跨域請求和Ajax技術都會極大地提高頁面的體驗,但同時也會帶來安全的隱患,其中最主要的隱患來自於CSRF(Cross-site request forgery)跨站請求偽造。
技術分享

CSRF攻擊的大致原理是:

  1. 用戶通過瀏覽器,訪問正常網站A(例如某銀行),通過用戶的身份認證(比如用戶名/密碼)成功A網站。
  2. 網站A產生Cookie信息並返回給用戶的瀏覽器;
  3. 用戶保持A網站頁面登錄狀態,在同一瀏覽器中,打開一個新的TAB頁訪問惡意網站B;
  4. 網站B接收到用戶請求後,返回一些攻擊性代碼,請求A網站的資源(例如轉賬請求);
  5. 瀏覽器執行惡意代碼,在用戶不知情的情況下攜帶Cookie信息,向網站A發出請求。
  6. 網站A根據用戶的Cookie信息核實用戶身份(此時用戶在A網站是已登錄狀態),A網站會處理該請求,導致來自網站B的惡意請求被執行。

CORS驗證機制

出於安全原因,瀏覽器限制從腳本中發起的跨域HTTP請求。默認的安全限制為同源策略, 即JavaScript或Cookie只能訪問同域下的內容。
W3C推薦了一種跨域的訪問驗證的機制,即CORS(Cross-Origin Resource Sharing 跨源資源共享)。
這種機制讓Web應用服務器能支持跨站訪問控制,使跨站數據傳輸更加安全,減輕跨域HTTP請求的風險。
CORS驗證機制需要客戶端和服務端協同處理。

CORS瀏覽器支持情況

目前主流瀏覽器都已基本提供對跨域資源共享的支持,移動端瀏覽器也幾乎全部支持。
技術分享

客戶端處理機制

基於上述的CSRF的風險,各主流的瀏覽器都會對動態的跨域請求進行特殊的驗證處理。驗證處理分為簡單請求驗證處理和預先請求驗證處理。

簡單請求

當請求同時滿足下面兩個條件時,瀏覽器會直接發送GET請求,在同一個請求中做跨域權限的驗證。

請求方法是下列之一:

  • GET
  • HEAD
  • POST

請求頭中的Content-Type請求頭的值是下列之一:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

簡單請求時,瀏覽器會直接發送跨域請求,並在請求頭中攜帶Origin 的header,表明這是一個跨域的請求。
服務器端接到請求後,會根據自己的跨域規則,通過Access-Control-Allow-Origin和Access-Control-Allow-Methods響應頭,來返回驗證結果。
如果驗證成功,則會直接返回訪問的資源內容。
技術分享

如果驗證失敗,則返回403的狀態碼,不會返回跨域請求的資源內容。
技術分享

可以通過瀏覽器的Console查看具體的驗證失敗原因
技術分享

預先請求

當請求滿足下面任意一個條件時,瀏覽器會先發送一個OPTION請求,用來與目標域名服務器協商決定是否可以發送實際的跨域請求。

請求方法不是下列之一:

  • GET
  • HEAD
  • POST

請求頭中的Content-Type請求頭的值不是下列之一:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

瀏覽器在發現頁面中有上述條件的動態跨域請求的時候,並不會立即執行對應的請求代碼,而是會先發送Preflighted requests(預先驗證請求),Preflighted requests是一個OPTION請求,用於詢問要被跨域訪問的服務器,是否允許當前域名下的頁面發送跨域的請求。 技術分享

OPTIONS請求頭部中會包含以下頭部:Origin、Access-Control-Request-Method、Access-Control-Request-Headers。
服務器收到OPTIONS請求後,設置Access-Control-Allow-Origin、Access-Control-Allow-Method、Access-Control-Allow-Headers頭部與瀏覽器溝通來判斷是否允許這個請求。
如果Preflighted requests驗證通過,瀏覽器才會發送真正的跨域請求。
技術分享  

如果Preflighted requests驗證失敗,則會返回403狀態,瀏覽器不會發送真正的跨域請求。
技術分享

可以通過瀏覽器的Console查看具體的驗證失敗原因
技術分享

帶認證的請求

默認情況下,跨源請求不提供憑據(cookie、HTTP認證及客戶端SSL證明等)。通過將withCredentials屬性設置為true,可以指定某個請求應該發送憑據。
xhr.withCredentials = true;
如果服務器接收帶憑據的請求,會用下面的HTTP頭部來響應。
Access-Control-Allow-Credentials: true
服務器還可以在Preflight響應中發送這個HTTP頭部,表示允許源發送帶憑據的請求。
技術分享

如果發送的是帶憑據的請求,但服務器的響應中沒有包含這個頭,那麽瀏覽器就不會把響應交給JavaScript(responseText中將是空字符串,size為0)。
技術分享

註意,當withCredentials屬性設置為true,需要response header中的‘Access-Control-Allow-Origin‘為一個確定的域名,而不能使用‘*‘這樣的通配符。
技術分享

服務端處理機制

服務器端對於跨域請求的處理流程如下:

  1. 首先查看http頭部有無origin字段;
  2. 如果沒有,或者不允許,直接當成普通請求處理,結束;
  3. 如果有並且是允許的,那麽再看是否是preflight(method=OPTIONS);
  4. 如果不是preflight(簡單請求),就返回Allow-Origin、Allow-Credentials等,並返回正常內容。
  5. 如果是preflight(預先請求),就返回Allow-Headers、Allow-Methods等,內容為空;

HTTP Header

Request header

Origin

Origin頭在跨域請求或預先請求中,標明發起跨域請求的源域名。

Access-Control-Request-Method

Access-Control-Request-Method頭用於表明跨域請求使用的實際HTTP方法

Access-Control-Request-Headers

Access-Control-Request-Headers用於在預先請求時,告知服務器要發起的跨域請求中會攜帶的請求頭信息

Response header

Access-Control-Allow-Origin

Access-Control-Allow-Origin頭中攜帶了服務器端驗證後的允許的跨域請求域名,可以是一個具體的域名或是一個*(表示任意域名)。簡單請求時,瀏覽器會根據此響應頭的內容決定是否給腳本返回相應內容,預先驗證請求時,瀏覽器會根據此響應頭決定是否發送實際的跨域請求。

Access-Control-Expose-Headers

Access-Control-Expose-Headers頭用於允許返回給跨域請求的響應頭列表,在列表中的響應頭的內容,才可以被瀏覽器訪問。

Access-Control-Max-Age

Access-Control-Max-Age用於告知瀏覽器可以將預先檢查請求返回結果緩存的時間,在緩存有效期內,瀏覽器會使用緩存的預先檢查結果判斷是否發送跨域請求。

Access-Control-Allow-Credentials

Access-Control-Allow-Credentials用於告知瀏覽器當withCredentials屬性設置為true時,是否可以顯示跨域請求返回的內容。簡單請求時,瀏覽器會根據此響應頭決定是否顯示響應的內容。預先驗證請求時,瀏覽器會根據此響應頭決定在發送實際跨域請求時,是否攜帶認證信息。

Access-Control-Allow-Methods

Access-Control-Allow-Methods用於告知瀏覽器可以在實際發送跨域請求時,可以支持的請求方法,可以是一個具體的方法列表或是一個*(表示任意方法)。簡單請求時,瀏覽器會根據此響應頭的內容決定是否給腳本返回相應內容,預先驗證請求時,瀏覽器會根據此響應頭決定是否發送實際的跨域請求。

Access-Control-Allow-Headers

Access-Control-Allow-Headers用於告知瀏覽器可以在實際發送跨域請求時,可以支持的請求頭,可以是一個具體的請求頭列表或是一個*(表示任意請求頭)。簡單請求時,瀏覽器會根據此響應頭的內容決定是否給腳本返回相應內容,預先驗證請求時,瀏覽器會根據此響應頭決定是否發送實際的跨域請求。

配置CORS規則

nginx上的CORS配置
技術分享

OSS上的CORS配置

技術分享

CDN上的CORS配置
技術分享

註意:由於CDN的緩存特性,CDN配合OSS時,需要在CDN中設置CORS配置。

【本文原發布於阿裏雲雲享頻道(阿裏雲官網首頁-支持-雲享),作者:睿得(阿裏雲雲享專家)】

CORS——跨域請求那些事兒