原文連結:iframe頁面二次登入問題
生產問題
問題背景
由於歷史原因,公司內部系統有一些頁面是基於iframe嵌入的其他系統的頁面,之前一直執行正常,最近不知什麼原因接連出現訪問所有iframe頁面時提示需要登入的情況,並且點選iframe頁面的登入按鈕時會出現頁面閃一下,沒有任何跳轉的現象。
問題顯而易見,怎麼解決呢?透過現象看本質,旁邊業務還在催問什麼時候能恢復,只能硬著頭皮一步步分析著看。
首先,這些系統都接入了公司內部的sso服務,主體系統A已經成功登入的情況下,內嵌系統B還需要二次登入,肯定是相關鑑權未通過。
而整個鑑權資訊都是基於Cookie機制來完成的,所以藉此機會先回顧一下Cookie相關知識。
原因定位
跟蹤網路請求發現,當請求內嵌頁面時,首先會發起SSO認證請求https://sso.xxx.com/authentication/login?sevice=http://b.xxx.com
,這個請求的scheme是HTTPS,而主系統A沒有升級HTTPS, 所以B系統發起SSO認證時屬於cross-site
情形,無法攜帶此前生成的使用者身份Cookie資訊,使用者鑑權失敗。
當點選內嵌頁面中的登入按鈕時,由於此時發起的請求仍然是cross-site
的,所以Cookie始終無法寫入,就會出現頁面閃一下,但並未登入的情況。
Cookie
HTTP Cookie是伺服器傳送到使用者瀏覽器並儲存在本地的一小塊資料,它會在瀏覽器下次向同一伺服器發起請求時被攜帶併傳送到伺服器上。
Cookie主要用於以下三個方面:
- 會話狀態管理,如使用者登入狀態、購物車、遊戲分數等。
- 個性化設定,如使用者自定義設定,主題等。
- 瀏覽器行為跟蹤,如跟蹤分析使用者行為等。
第三方Cookie
Cookie與域關聯。如果此域與您所在頁面的域相同,則該Cookie稱為第一方Cookie(first-party cookie)。如果域不同,則它是第三方Cookie(thirty-patry cookie)。第三方Cookie是由第三方網站引導發出的,可以用於CSRF攻擊以及使用者行為追蹤。
限制訪問Cookie
通過以下方式可以確保Cookie被安全傳送,不會被意外的參與者或指令碼訪問:
-
Secure
屬性:標記為Secure
的Cookie只能通過被HTTPS協議加密過的請求傳送給服務端,可以藉此來預防中間人攻擊從 Chrome 52 和 Firefox 52 開始,不安全的站點(
http:
)無法使用Cookie的Secure
標記。 -
HttpOnly
屬性:JavaScript的Document.cookie
API無法訪問帶有HttpOnly屬性的Cookie,此類Cookie僅作用於伺服器。例如,持久化伺服器會話的Cookie不需要對JavaScript可用,因此可以設定HttpOnly
屬性。可以藉此來緩解XSS攻擊。
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
Cookie作用域
通過Domain
和Path
標識來定義Cookie的作用域:即允許Cookie應該傳送給哪些URL。
Domain屬性
指定哪些主機可以接受Cookie。預設為origin,不包含子域名。如果指定了Domain
,則一般包含子域名。
當前大多數瀏覽器遵循 RFC 6265,設定 Domain 時 不需要加前導點。瀏覽器不遵循該規範,則需要加前導點,例如:Domain=.mozilla.org
Path屬性
指定主機下哪些路徑可以接受Cookie(該URL路徑必須存在於請求URL中)。以字元%X2F("/")作為路徑分隔符,子路徑也會被匹配。
例如,設定Path=/docs,以下地址都會匹配:
- /docs
- /docs/web/
- /docs/web/HTTP
SameSite屬性
SameSite
屬性允許伺服器要求某個Cookie在跨站請求時不會被髮送,從而可以阻止跨站請求偽造攻擊(CSRF)。
Set-Cookie: key=value; SameSite=Strict
SameSite
取值有以下三種:
-
None
:瀏覽器會在同站請求、跨站請求下繼續傳送Cookie,不區分大小寫。大多數網站已經將
SameSite
的預設值設定為Lax
,此時如果網站想要關閉SameSite
屬性,必須在將SameSite
屬性設定為None
的同時設定Secure
屬性,否則無效。
// 該設定無效
Set-Cookie: widget_session=abc123; SameSite=None
// 有效設定
Set-Cookie: widget_session=abc123; SameSite=None; Secure
Strict
瀏覽器只在訪問相同站點時傳送Cookie。Lax
:與Strict
類似,但使用者從外部站點導航至目標網址的Get請求除外(連結、預載入請求以及GET表單)。
請求型別 | 示例 | 正常情況 | Lax |
---|---|---|---|
連結 | <a href="..."></a> |
傳送 Cookie | 傳送 Cookie |
預載入 | <link rel="prerender" href="..."/> |
傳送 Cookie | 傳送 Cookie |
GET 表單 | <form method="GET" action="..."> |
傳送 Cookie | 傳送 Cookie |
POST 表單 | <form method="POST" action="..."> |
傳送 Cookie | 不傳送 |
iframe | <iframe src="..."></iframe> |
傳送 Cookie | 不傳送 |
AJAX | $.get("...") |
傳送 Cookie | 不傳送 |
Image | <img src="..."> |
傳送 Cookie | 不傳送 |
same-origin和same-site
同源(same-origin
)、同站點(same-site
)是兩個隨處可見的概念,但是這兩個概念經常容易被混淆。
Origin
首先來看一下origin的定義是什麼。scheme + hostname + port
構成的整體叫做origin,例如https://www.example.com/443/foo
的origin是https://www.example.com/443
。
具有相同的scheme、hostname以及port的站點被視為同源站點,否則視為跨域(cross-origin
)站點.
Site
學習site之前,先來了解一下什麼是TLD、eTLD。
TLD
TLD全稱叫做頂級域名(Top-Level-Domain),比如我們經常看到的com、cn、io之類的,都屬於頂級域名。
TLD有一個記錄列表,這個列表叫做Root Zone Database,裡面記錄了所有的頂級域名。需要注意的是,頂級域名不一定都是單詞很短且只有一級的域名。
如上圖,TLD和它之前部分的域構成的整體叫做"site",比如https://www.example.com:443/foo
,"site" 是example.com
。
eTLD
除了頂級域名之外,還有一種叫做eTLD(effective Top-Level-Domain)的東西,它表示的是有效頂級域名。
例如.io
是一個TLD,而像.github.io
,是一個開放給使用者的用於搭建個人網站的一個域。比如現在有以下三個網站:
http://zhangsan.github.io
http://lisi.github.io
http://wangwu.github.io
我們判斷是否是同一個站點,通常是採用頂級域名+二級域名來判斷,這裡如果直接用.io
這個TLD來識別,就會認為這三個網站是同一個站點,Cookie可以共享。
顯然這是有問題的,因此需要引入eTLD的概念,將.io
與github
合起來的.github.io
註冊為一個"effective TLDs"。將eTLD+1整體視為網站的站點名稱,這樣一來,上述三個網址表示的就是不同的網站,Cookie就會相互隔離。
eTLD資訊在Public Suffix List列表中定義,可以通過publicsuffix.org/list查詢域名是否為有效頂級域名。
schemeful same-site
關於"same-site的定義一直在不斷演變,由此出現將URL Scheme看作是site的一部分的這種策略。基於這種策略可以避免網站遭受基於HTTP協議的一些攻擊。
在schemeful same-site規則下,由於scheme不同,http://www.example.com
和https://www.example.com
被認為是不同的站點。
如何判斷
Chrome瀏覽器傳送請求時會攜帶名為Sec-Fetch-Site
的Header資訊,根據該Header的取值可以判斷當前請求是否同源、是否跨站(schemeful-same-site
未被記錄在Sec-Fetch-Site
中)。
Sec-Fetch-Site
取值如下:
cross-site
same-site
same-origin
none