iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 3
1
Modern Web

平時沒注意的 JavaScript - JS 生態系及週邊工具整理系列 第 3

打造 Messenger Extension - Day 3 - CORS, Same-origin policy 和 iFrame

前言

在昨天的最後,我們成功地設定了我們粉專的 home_urlmenu

但是打開過後卻發現,從手機版的 menu 是可以成功讀到 Hacker News 的

而瀏覽器版本卻不行,在開發者工具上我們讀到了一個似懂非懂的訊息

Refused to display 'URL' in a frame because it set 'X-Frame-Options' to 'deny'.

接下來,我們將花額外 1 ~ 2 個篇幅介紹一個常碰到的瀏覽器標準 - Same-origin policy(同源規範)

由於 Facebook 對於跑在自己環境的擴充套件,一定有許多安全上的考量

所以今天將會介紹 Same-origin policy, CORS, 和我們碰到的 iFrame 問題

Same-origin policy(同源規範)

如果你已經做前端一陣子了,並且有串接過別人開的 API

應該會對一個錯誤訊息不陌生

Failed to load https://github.com/alxtz: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://www.google.com.tw' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

第一次見到這樣的訊息都會有點疑惑

通常後端開出來的 API,習慣上會使用 Postman 或 curl 先做些測試 (至少確定網址沒給錯)

但是自己進行 fetch 再抓的時候,卻怎麼樣都不成功,這是怎麼回事?

範例

就舉自己的 GitHub 頁面 https://github.com/USER

  1. 使用 Postman 來抓取資料

    (https://github.com/alxtz 正常地回傳一份 HTML)

  2. 使用 curl

    (https://github.com/alxtz 正常地回傳一份 HTML)

  3. 試著在 www.google.com 開啟 devtool,用 fetch 來抓 GitHub 的資料

這樣卻失敗了!

猜測

如果是要 Debug 的話,通常會這樣去做猜想

  1. 是不是瀏覽器本身有什麼問題
  2. 或是 fetch 這段程式碼寫錯了

嘗試 1

直接在網址欄輸入 www.github.com,按下 enter

如果你這邊同時打開 devtool(開發者工具) 的 「Network」 觀察網路流向的話

可以看到,瀏覽器抓取網頁的方式跟你使用 Postman 來 GET 其實根本就一樣

如果不確定的話,可以連同瀏覽器自動幫你帶的 Headers 都加進 Postman或 curl 裡,但依然沒有影響

但是用這些參數去在 www.google.com 再呼叫一次 fetch ,卻還是失敗

嘗試 2

github.com 下使用 fetch

這樣卻成功了?

這是為何呢

Internet 規範

基本上為什麼會有這樣的現象,要從 Internet (網際網路) 的規範開始介紹
(盡量講的淺顯易懂)

我們為了讓一門技術(像是網路)能夠廣為流通,通常會做的方式是

把想實作的技術寫成一份比較抽象的「規範」,可以把它想成規格書

接下來所有的開發者或是公司,都可以去把它實作出來

其中一個例子就是瀏覽器,各家公司都會實作自己的瀏覽器(Chrome, Firefox, Opera)

而瀏覽器本身就是遵從各種規範所做出來的 (HTML, CSS, JavaScript, HTTP...)

現在世界上有許多組織致力於寫這些規範文件,舉例來說

W3C -> 包含 HTML, CSS, SVG, XML 比較和我們熟悉的 UI 及檔案格式相關
IETF -> 網路工程任務小組,主要負責 OSI 模型(由ISO提出的的網路架構)的第三 & 第四層。主要大家熟悉的有像是 HTTP 和這次討論主題的 CORS

同源政策 Same-origin policy

IETF 所提出的備忘錄 RFC 6454 中有一段定義叫做 Same-origin policy

內容很簡單,那就是只要你在「網頁的環境中」發的任何請求(不管你是使用 fetch, $ajax)

都只能同個網域底下的資源做互動

所以在剛剛的範例裡面,由於 www.google.comgithub.com 所在的網域不同

所以我們發的 fetch 都會失敗,只有在 github.com 這個網域下寫的 fetch 才會成功

跨網域請求 CORS

但是你可以想見,只限制跟自己同網域的程式碼才可以請求是一件非常嚴格的事情

(當然,因為 CORS 只限制了「網頁的環境」,所以你對於想獲取的資源。你可以自己開一支 API ,來抓取這些資料)

但是這同樣不方便,我們平時使用的「第三方登入」、「CDN」似乎不受這類限制

當然 IETF 也有注意到這件事情,所以他們提出了另一套規範,那就是 CORS

根據同源政策,你向 API 撈到的資料

開啟開發者工具的 Network 可以看到正確的回傳資料

不過瀏覽器本身會限制,讓你的 JavaScript 讀不到這些內容

CORS 基本上在「同源政策」上加了一個例外條款

那就是,如果特別把你的加上一條 Header

Access-Control-Allow-Origin: http://www.google.com

那麼這個網址,不受 CORS 限制!它可以讓你指定的網域進行跨域請求

一般,如果我們想要架一個公共的 API,像是 CDN 好了

我們會讓這個 CDN 多回傳一個 Header

Access-Control-Allow-Origin: *

這樣任何網站才可以都存取到這個 CDN

範例

舉例來說,今天我們去找一個 CDN
(註: 給不知道 CDN 的人,CDN 是一種線上幫你 host 一些函式庫的服務,可以免得你自己去載來使用)

我們這邊就拿 cdnjs.com 的 vue 來做範例好了

我們一樣在 www.google.com 做一個 fetch

可以看到,我們這次的 fetch 是成功的!但是 www.google.comcdnjs.com 並不在同個網域下

我們再次打開 devtool 的 Network

可以看到從 https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.9/vue.js 來的回傳

果真包含了 Access-Control-Allow-Origin: *,而這就是我們可以對他進行 CORS 請求的的原因

怎麼解決?

在我們昨天的範例中

我們看到了 iFrame 也被類似的 Headers 擋住了

只是在規範中,iFrame 其實相反

iFrame 預設是支援不同網域下連線的,但是只要你回傳 X-Frame-Options: DENY

瀏覽器就會幫你阻擋想透過 iFrame 來請求你網站的 HTML

我們實際對 https://news.ycombinator.com/ 進行一個 fetch 來試試看

果真跟昨天的結果一樣

Messenger 網頁版開啟這個 URL 的方式是使用 iFrame,所以被 X-Frame-Options: DENY 阻擋了

而在 iOS 版的 Messenger,他使用的是 iOS 原生功能,叫做 WebView,而同源政策不會去限制「非網頁環境」的存取

這就是為什麼在 iOS 上可以開啟的原因

明天

經過介紹後,應該對 CORS 以及 Extension 讀取我們網頁的方式有更深的認知了

我們後續將不會只顯示 https://news.ycombinator.com/ ,而是會自己實際 host 一個網站

並且接到我們的 Messenger 上,我們明天見!


上一篇
打造 Messenger Extension - Day 2 - 設定 whitelisted_domains, home_url, persistent_menu
下一篇
打造 Messenger Extension - Day 4 - Vue.js 基本介紹 + 優缺點
系列文
平時沒注意的 JavaScript - JS 生態系及週邊工具整理33

尚未有邦友留言

立即登入留言