原文鏈接: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