記錄我開發工作中遇到HTTP跨域和OPTION請求的一個坑

i042416發表於2018-08-27

我透過這篇文章把今天工作中遇到的HTTP跨域和OPTION請求的一個坑記錄下來。

場景是我需要在部署在域名a的Web應用裡用JavaScript去消費一個部署在域名b的伺服器上的服務。域名b上的服務也是我開發的,因此我將域名a加到了該服務的HTTP響應結構的標頭檔案裡,這樣就允許了域名a上的JavaScript程式碼用AJAX訪問域名b的服務。

記錄我開發工作中遇到HTTP跨域和OPTION請求的一個坑

域名b上的服務是一個Servlet,允許域名a跨域訪問的程式碼就一行:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 做業務邏輯
         response.setHeader("Access-Control-Allow-Origin", "域名a");
}

我在域名a的Web應用裡用AJAX發起服務請求:

記錄我開發工作中遇到HTTP跨域和OPTION請求的一個坑

執行後,發現並沒有顯示200的彈出視窗。

錯誤訊息:Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response.

記錄我開發工作中遇到HTTP跨域和OPTION請求的一個坑

觀察Chrome開發者工具,發現其實域名b的服務已經成功執行了,確實返回了200的Status code,

記錄我開發工作中遇到HTTP跨域和OPTION請求的一個坑

而且我已經從Chrome開發者工具裡觀察到瀏覽器已經成功接到域名b傳送回來的請求了。

那這個錯誤是什麼鬼呢?根據錯誤訊息“Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response” Google了一下,發現一些朋友遇到同樣的問題:

  1. 如何解決出現AXIOS的Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

網頁地址:  https://www.cnblogs.com/caimuqing/p/6733405.html

這位朋友的解決方案:

response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Allow-Headers", "Content-Type,Access-Token");
response.setHeader("Access-Control-Expose-Headers", "*");if (request.getMethod().equals("OPTIONS")) {
     HttpUtil.setResponse(response, HttpStatus.OK.value(), null);     return;
}

記錄我開發工作中遇到HTTP跨域和OPTION請求的一個坑

但我試過,在我的場景下還是不工作,因為我的例子裡,伺服器已經針對OPTIONS請求返回HTTP 200的狀態碼了。

2. 這個Stackoverflow的帖子裡,很多朋友都提供了自己的解決方案。

我一一試過,在我的場景裡都不能工作。

於是我查詢了Mozilla的一篇文件: HTTP訪問控制(CORS)

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

裡面談到了,在某些情況下,瀏覽器在發起“需要預檢的請求”之前,必須首先發起一個“預檢請求(Preflight)”到伺服器,以探測伺服器是否允許這個實際請求。"預檢請求"機制的使用,是為了避免跨域請求對伺服器的使用者資料產生未預期的影響。

那麼哪些請求算作“需要預檢的請求”呢?Mozilla的這篇文件定義得很清楚:

當請求滿足下述任一條件時,即應首先傳送預檢請求:

  • 使用了下面任一 HTTP 方法:

  • PUT

  • DELETE

  • CONNECT

  • OPTIONS

  • TRACE

  • PATCH

  • 人為設定了對 CORS 安全的首部欄位集合之外的其他首部欄位。該集合為:

  • Accept

  • Accept-Language

  • Content-Language

  • Content-Type (but note the additional requirements below)

  • DPR

  • Downlink

  • Save-Data

  • Viewport-Width

  • Width

  • Content-Type 的值不屬於下列之一:

  • application/x-www-form-urlencoded

  • multipart/form-data

  • text/plain

我再檢查我的程式碼,因為我在HTTP請求裡用xhr.setRequestHeader("Authorization", "使用者名稱:密碼的base64編碼" )新增了用於Basic Authentication的頭部,因此迫使該請求成為了“需要預檢的請求”,所以才有了OPTION請求的傳送。

現在我將其註釋掉:

記錄我開發工作中遇到HTTP跨域和OPTION請求的一個坑

這次遇到了401 Unauthorized錯誤了:

記錄我開發工作中遇到HTTP跨域和OPTION請求的一個坑

然而沒有預檢請求OPTION發出來了,請求型別變成了我期望的POST方式了。

但是現在就陷入了一個矛盾的境地:如果在請求頭部加上Basic Authentication的資訊,會遇到錯誤訊息“Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response.”。如果去掉,雖然避免了預檢請求,但是又遇到401 Unauthorized錯誤了。

於是,我換了一種認證方式,終於成功實現了期望的跨域請求,在我域名a的前端應用裡列印出了來自於域名b的服務的響應。

記錄我開發工作中遇到HTTP跨域和OPTION請求的一個坑

我使用了form認證方式,這種方式不會造成該請求成為一個”需要預檢的請求“,所以最後跨域成功了。

var formData = new FormData();
formData.append('sap-client', "001");
formData.append('sap-user', "使用者名稱");
formData.append('sap-password', "使用者密碼");var request = new XMLHttpRequest();
request.open("POST", "域名b的url",false);
request.send(formData);
alert("response: " + request.responseText);

記錄我開發工作中遇到HTTP跨域和OPTION請求的一個坑

希望我的這個踩坑經歷對大家有點幫助。

要獲取更多Jerry的原創技術文章,請關注公眾號"汪子熙"或者掃描下面二維碼:


記錄我開發工作中遇到HTTP跨域和OPTION請求的一個坑

記錄我開發工作中遇到HTTP跨域和OPTION請求的一個坑


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24475491/viewspace-2212876/,如需轉載,請註明出處,否則將追究法律責任。

相關文章