CORS詳解

  CORS(Cross-Origin Resource Sharing, 跨源資源共享)是W3C出的一個標準,其思想是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功,仍是應該失敗。所以,要想實現CORS進行跨域,須要服務器進行一些設置,同時前端也須要作一些配置和分析。本文簡單的對服務端的配置和前端的一些設置進行分析。前端

服務端的配置

本文服務端的代碼採用的是node,使用koa,koa-router和koa2-cors。node

配置的主要代碼以下:git

app.use(cors({
    origin: function(ctx) { const regexp = new RegExp('/CORS'); const regexpWith = new RegExp('/CORSWith'); if (regexpWith.test(ctx.url)) { return `http://${packageData.url}:7000`; } else if(regexp.test(ctx.url)) { return '*' } else if(~String(ctx.url).indexOf('/imgs/')) { return `http://${packageData.url}:7000`;  } return false; }, exposeHeaders: ['WWW-Authenticate', 'Server-Authorization', 'Date'], maxAge: 100, credentials: true, allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'X-Custom-Header', 'anonymous'], }));

主要是使用了koa2-cors進行了配置,下面對配置項進行簡單介紹:github

origin:

設置Access-Control-Allow-Origin,源碼以下:canvas

  Access-Control-Allow-Origin表示容許跨域的域名,能夠設置爲*也能夠設置爲具體的域,其中,*表示所有,即全部的域名下的請求都容許,但設置爲*後,全部的請求都不會攜帶附帶身份憑證(好比cookie);設置爲具體的域則表示只有該域下的請求容許,別的域下的請求不被容許,設置爲具體的域是請求中攜帶身份憑證的基礎。跨域

exposeHeaders:

設置Access-Control-Expose-Headers,源碼以下:瀏覽器

  Access-Control-Expose-Headers表示容許腳本訪問的返回頭,請求成功後,腳本能夠在XMLHttpRequest中訪問這些頭的信息,如在上面配置的代碼中設置了Date,在瀏覽器端就能夠經過js拿到服務器的時間了,下面是經過XMLHttpRequest實例的getResponseHeader()獲取服務器時間的代碼:安全

let xhr = new XMLHttpRequest();
    xhr.open('GET', `http://${url}/CORS/userInfo/12`, true);
    xhr.onload = function() { if(xhr.readyState == 4) { try { // 獲取服務器時間 console.log(xhr.getResponseHeader('Date')); } catch(ex) { new Error(ex); } } }; xhr.send();

maxAge:

設置Access-Control-Max-Age,源碼以下:服務器

Access-Control-Max-Age用來指定本次預檢請求的有效期,單位爲秒,預檢請求將在下面具體介紹,如今先過。cookie

credentials:

設置Access-Control-Allow-Credentials,源碼以下:

  Access-Control-Allow-Credentials爲服務端標識瀏覽器請求CORS時是否能夠附帶身份憑證,對於附帶身份憑證的請求,服務器不得設置 Access-Control-Allow-Origin 的值爲「*」。

allowMethods:

設置Access-Control-Allow-Methods,源碼以下:

Access-Control-Allow-Methods用來設置檢查網絡請求的方式,如GET、POST等。

allowHeaders:

設置Access-Control-Request-Headers,源碼以下:

Access-Control-Request-Headers用來將實際請求所攜帶的首部字段告訴服務器,在這裏能夠自定義頭部信息,用來對瀏覽器的非簡單請求進行預檢判斷。

前端的配置

前端的配置主要經過簡單請求和非簡單請求,攜帶身份憑證,canvas中畫圖使用的跨域圖片三部分進行講解

簡單請求和非簡單請求

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

簡單請求:

簡單請求知足如下條件:

1. 使用下列方法之一:

  • GET
  • HEAD
  • POST

2.HTTP的頭信息不超出如下幾種字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type:值屬於下列之一:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

簡單請求如圖所示:

瀏覽器與服務器之間請求只進行了一次。

非簡單請求:

不知足簡單請求條件的請求則要先進行預檢請求,即便用OPTIONS方法發起一個預檢請求到服務器,已獲知服務器是否容許該實際請求。

非簡單請求以下所示:

下面是PUT請求第一次返回的結果:

  經過PUT請求結果能夠看出,當檢測到PUT請求爲非簡單請求時,瀏覽器便會發送一個預檢請求,目的是詢問,自定義頭部X-Custom-Header的PUT請求是否被容許,瀏覽器返回了全部能夠請求的方法和自定義的頭部(把全部能夠的返回是爲了不屢次預檢請求),這時候預檢請求成功了,便會發送真正的PUT請求。

關於預檢請求,須要注意一下兩點:

  1. 預檢請求對js來講是透明的,js獲取不到預檢請求的任何信息。
  2. 預檢請求並非每次請求都發生,服務端設置的Access-Control-Max-Age頭部指定了預檢請求的有效期,在有效期內的非簡單請求不須要再次發生預檢請求。

攜帶身份憑證

大部分的請求是須要用戶攜帶着用戶信息的,好比在一個登陸的系統中,用戶會攜帶着相應的cookie或token,但CORS跨域默認是不帶身份憑證的。

若是須要附帶身份憑證,在發送請求時,經過將withCredentials屬性設置爲true,能夠指定某個請求能夠發送憑據。

下面提供針對XMLHttpRequest附帶身份憑證的兼容性寫法:

function createCORSRequest(method, url) {
    var xhr = new XMLHttpRequest(); xhr.onload = function() { if(xhr.readyState == 4) { try { if((xhr.status >= 200 && xhr.status < 300) || xhr == 304) { console.log(xhr.response); } else { console.log('Request was unsuccessful: ' + xhr.status); } } catch(ex) { new Error(ex); } } }; if('withCredentials' in xhr) { xhr.open(method,url, true); } else if(typeof XDomainRequest != 'undefined') { xhr = new XDomainRequest(); xhr.open(method, url); } else { xhr = null; } return xhr; }

附帶身份憑證對服務端有兩個要求:

  1. 服務端的Access-Control-Allow-Origin頭部不能設置爲*
  2. 服務端的Access-Control-Allow-Credentials頭部設置爲true

canvas中畫圖使用的跨域圖片

  儘管不經過 CORS 就能夠在畫布中使用圖片,可是這會污染畫布。一旦畫布被污染,你就沒法讀取其數據。例如,你不能再使用畫布的 toBlob(), toDataURL() 或 getImageData() 方法,調用它們會拋出安全錯誤。

這種機制能夠避免未經許可拉取遠程網站信息而致使的用戶隱私泄露。

對跨域圖片進行修改的話,img須要添加crossOrigin屬性,代碼以下:

function drawCanvas(id, drawId, url) {
        let canvas = document.getElementById(id); let ctx = canvas.getContext('2d'); var img = document.createElement('img'); img.src = url; img.crossOrigin = 'anonymous'; // 必須等到圖片徹底加載後才能對其進行操做。瀏覽器一般會在頁面腳本執行的同時異步加載圖片。若是試圖在圖片未徹底加載以前就將其呈現到canvas上,那麼canvas將不會顯示任何圖片 if (img.complete) { canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0 ); } else { img.onload = function () { canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0 ); const src = canvas.toDataURL(" image/jpeg", 0.3); $(drawId).attr('src', src); }; } }

服務器針對跨域修改的圖片也要作兩點限制:

  1. 服務端的Access-Control-Allow-Origin頭部不能設置爲*
  2. 服務端的Access-Control-Request-Headers頭部添加一個自定義頭部,其值爲img的crossOrigin的值

常見的圖片跨域修改的錯誤有兩種:

  • Uncaught SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
  • Image from origin 'http://127.0.0.1:4000' has been blocked from loading by Cross-Origin Resource Sharing policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:7000' is therefore not allowed access.

總結

至此,關於CORS的一些知識點已經分享完了,總結以下:

CORS是瀏覽器和服務器配合完成的跨域請求,我的認爲,主要是服務端配置好後,瀏覽器根據服務端配置的自定義頭部和提供的能夠進行的CORS的方法來進行跨域操做。

基於以上總結,提供測試Demo:

  https://github.com/weiruifeng/fetchTest

參考資料:

  HTTP訪問控制(CORS): https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

 

若有問題,歡迎你們指正。

相關文章
相關標籤/搜索