筆者由於工作需要,最近對國內外兩款知名的電商網站的用户會話管理(User Session Management) 的實現機制做了一些調研,這裏把我學習到的一些知識分享給各位同行,希望起到拋磚引玉的作用。
我們首先看看大家日常生活中都會使用的某寶網站的用户會話管理機制。
在電腦端訪問某寶網,輸入用户名和密碼,點擊登錄:
會觀察到一個 HTTP Post 請求,login,發送往後台服務器:
https://login.taobao.com/newl...
該請求的 Form Data 中包含 loginId 和 password2 兩個字段,分別維護了用户輸入的用户名的明文,以及密碼進行 RSA 加密後的值。
下面介紹如何自行找到前端將用户輸入的登錄密碼進行 RSA 加密的準確位置。
在 Chrome 開發者工具裏找到 login 請求,在 Initiator 面板裏找到發起該請求的調用棧。稍有經驗的前端開發人員,從 onClick 和 t.loginSubmit 就能推斷出,用户名和密碼的輸入框,實現在一個 Form 表單裏,點擊登錄按鈕後,觸發表單的 Submit.
打開上圖找到的 index.js 文件:
https://x.alicdn.com/vip/hava...
直接搜索關鍵字 password2,很快就能找到 RSA 加密的代碼位置:
設置斷點後,運行時點擊登錄按鈕,斷點觸發,可以進入 rsaPassword 函數,查看 RSA 加密算法的明細。
這個 index.js 裏還能發現一些有趣的東西。比如提供了 rsaPassword 方法的模塊內部,還維護了一個支持的國家列表,countryList,裏面有 168 個國家和地區:
但是在瀏覽器端打開某寶網,國家和地區的下拉列表裏,只能看到十餘條記錄。這應該是前台某處根據某種邏輯做了過濾:
此外,我們在這個電商網站首頁右邊區域,能看到快速充值話費的面板,如下圖綠色高亮區域所示:
該頁面的 HTML 源代碼,並不是靜態編寫於首頁的 HTML 文件中,而是通過一個叫做 bianming-phone("便民"的拼音加上"手機"的英文單詞 phone) 的 HTTP 請求,從後台讀取到前台,然後再注入到前台頁面中:
同理,還有這個旅行視圖片段(相當於 SAP UI5 裏的 View Fragment):
讀取該視圖片段的 HTTP 請求:bianming-trip
看到這裏,筆者不由得聯想起 SAP Commerce Cloud 前台的 CMS 驅動設計,二者都是從電商頁面連接的後台系統讀取部分頁面配置信息,可謂異曲同工。
- SAP S/4HANA 的 UI 風格是 Fiori UX,實現的前端框架是 SAP UI5;
- SAP Commerce Cloud UI 實現的前端框架是 Angular;
- help.sap.com 採用 AngulaJS 實現,www.sap.com 使用的是 React.
- 某寶網首頁,採用的是阿里自研的前端框架,Kissy:
我們在某寶網首頁看到琳琅滿目的商品圖片,都是被 Kissy 驅動,通過向 CDN 服務器發起的數據請求而被加載的:
在這些頁面片段的源代碼裏還看到一些有意思的內容,比如這種“上線請刪除”的註釋。我現在瀏覽的就是上線後的代碼呀,咋還能夠看到這些註釋 :)
我們在電商網站上購物時,選擇好了自己心儀的商品,加入購物車之後,當然不希望點擊結帳之後,忽然彈出要求重新登錄的界面,這豈不是令人掃興。另外,當我們不小心誤操作,點擊了瀏覽器刷新按鈕,我們期望頁面刷新後,仍然處於登錄狀態,之前添加到購物車裏的商品不會丟失。這些都屬於用户會話管理的範疇。
某寶網頁面的用户會話管理,是通過客户端 Cookie 和服務器端維護的用户會話對象來實現的。
用户成功登錄之後,服務器創建對應的 Session 對象,返回給 login HTTP 請求的響應頭部,包含了很多 set-cookie 字段:
瀏覽器解析這些響應,將服務器頒發的 Cookie 設置到本地。下一次用户再次操作電商網站,觸發新的發向服務器端的請求時,瀏覽器會自動將這些 Cookie 字段設置到請求頭部。服務器接收到這些 Cookie 字段,就能在內存中定位到之前該用户登錄後對應創建的 Session 對象,從而能夠識別出該用户。
這些 Cookie 在 Chrome 開發者工具裏也能查看。
- Cookie 的 Expires 字段存放的是過期時間,當 Expires 為 Session 時,意為該 Cookie 只在當前會話內有效,瀏覽器關閉則 Cookie 自動刪除。
- 帶有 Http Only 的 Cookie,無法被客户端 JavaScript 代碼讀取,提高了安全性,避免了 Cookie 通過 XSS 攻擊被竊取的可能。
- Secure 為 true 的 Cookie,無法通過 HTTP 協議傳輸到服務器,只能通過 HTTPS 發送。
這個電商網站服務器頒發的 Cookie 裏,字段 lid 存儲的是用户名經過 URL encode 之後的值,dnk 存放的是用户名的 Unicode:
下面再看另一款來自國外的電商網站,SAP Commerce Cloud 的用户會話管理機制的設計和實現。
SAP Commerce Cloud UI,基於 100% API 驅動的無頭電商架構,Commerce 後台將 Commerce 核心業務通過 OCC(Omni Commerce Connect) API 的方式暴露出來。藉助這些 API 和開源的 SAP Spartacus 庫,客户可以自行開發具備個性化 UX 的電商網站。
SAP Commerce Cloud 有個名為 Oauth2 的 extension,基於 OAuth 2.0 協議實現了用户認證和令牌頒發/功能,支持 OAuth 2.0 協議定義的包括 Resource Owner Password Flow 在內的全部四種認證流。
SAP Commerce Cloud UI 扮演了 OAuth 2.0 認證框架中的客户端 (Client) 角色,通過消費 SAP Commerce Oauth2 擴展提供的 OAuth 系列 API,實現用户會話管理。
讓我們從最初始的用户登錄場景説起。
輸入用户名和密碼:
SAP Commerce Cloud UI 調用 Commerce OAuth2 API,endpoint 為 /authorizationserver/oauth/token, 將用户名,密碼,client_id 和 client_secret 去換取訪問令牌(Access Token)和刷新令牌(Refresh Token).
這裏的 SAP Commerce Cloud UI 作為 OAuth 認證體系裏的客户端,其 client_id 和 client_secret 在 Commerce Backoffice 裏配置:
服務器端驗證通過後,會頒發訪問令牌和刷新令牌,如下圖 access_token 和 refresh_token 字段所示:
SAP Commerce Cloud UI 在 OAuth 體系中扮演的角色是客户端,通過訪問令牌,獲得訪問 Commerce 後台服務器上的業務數據的許可。而刷新令牌,用於當訪問令牌過期時,由客户端憑藉其換取新的訪問令牌。刷新令牌本身是一個憑證,表明持有其的客户端,曾經通過 OAuth 認證,獲得了訪問受保護資源的許可,當通過刷新令牌再次請求新的訪問令牌時,客户端不用再從頭開始走一遍 OAuth 認證的完整流程。
SAP Commerce Cloud 的訪問令牌和刷新令牌都有過期時間,有時也稱為 TTL(Time-to-Live,存活時間),默認值分別為12小時和30天。
而我們團隊的開發人員,在開發 SAP Commerce Cloud UI 用户會話管理功能,進行各種邊界條件的測試時,為了方便起見,通常將自己本地搭建的 Commerce 服務器上令牌的過期時間進行了調整。比如下圖的例子,二者分別調整為30秒和60秒之後過期:
訪問令牌獲取之後,在接下來 Commerce Cloud UI 消費後台 OCC API 時,會將其附加在 HTTP 請求的頭部字段裏:
如果此時訪問令牌已經過期,則該請求會收到服務器 401 錯誤的應答:
以及錯誤詳情 InvalidTokenError:Access token expired: IqQ-8cYzHV1gjQOpnYytHTFPt30
顯然這種偏技術的錯誤消息不應該顯示給用户,幸運的是我們還有刷新令牌。此時,SAP Commerce Cloud UI 會將過期的訪問令牌,連同刷新令牌一齊發送給 Commerce 後台,申請一個新的訪問令牌:
SAP Commerce Cloud UI 初次登錄申請令牌時,grant_type 的值為 password;而訪問令牌過期,使用刷新令牌重新申請時,grant_type 的值應該填充為 refresh_token.
如果刷新令牌的過期時間也到達了,該怎麼辦?沒有刷新令牌,也就無從獲取新的訪問令牌。因此,我們會將用户重定向到登錄頁面,顯示一條“Session expired”的提示信息,讓其登錄之後,重新獲取訪問令牌和刷新令牌。
本文前一章節,從某寶首頁登錄説起曾經提到,我們在電商網站上購物,如果不小心刷新了瀏覽器,只要客户端存儲的 Cookie 尚未過期,就可仍然保持登錄狀態。這樣,客户刷新之前的會話,比如添加商品到購物車,或者正在進行結帳的某一步,仍然處於有效狀態。
SAP Commerce Cloud UI 通過將訪問令牌持久化到瀏覽器的 Local Storage 中來實現上述場景。
每當用户成功登錄後,我們將 Commerce 後台服務器頒發的訪問令牌進行持久化存儲,保存到瀏覽器 Local Storage 中。
每次 SAP Commerce Cloud UI 初始化時,通過 Angular APP_INITIALIZER 這個注入令牌,我們開發了 AuthStatePersistenceService 服務,將瀏覽器本地存儲中的令牌同步到內存中。
採取這種設計後,即使用户在購物過程中刷新了瀏覽器,SAP Commerce Cloud UI 重新加載後,從 Local Storage 中取出訪問令牌同步到內存中,接下來的用户操作,繼續使用該令牌調用 Commerce OCC API,不會因瀏覽器刷新而中斷。
總結起來,SAP Commerce Cloud UI 有關訪問令牌和刷新令牌的使用場景如下:
(1) 用户登錄後,SAP Commerce Cloud UI 將服務器頒發的訪問令牌存儲於內存中,並持久化到瀏覽器 Local Storage.
對於刷新令牌,出於安全性考慮,我們團隊實現時,僅將其維護在應用內存中,並不進行持久化操作。
(2) 當用户操作 UI,觸發 API 調用後收到服務器返回的訪問令牌過期的錯誤之後,SAP Commerce Cloud UI 自動利用刷新令牌,申請新的訪問令牌;待拿到新的訪問令牌之後,使用該令牌重新調用之前因為舊的訪問令牌過期而失敗的 API;這一系列機制對於用户來説完全透明,用户在界面上的操作不會受到任何影響。
(3) 如果用户操作觸發的 API 調用收到的服務器返回為刷新令牌過期,SAP Commerce Cloud UI 會暫存當前用户瀏覽頁面的 URL,並將用户重定向到登錄頁面;用户重新登錄後,獲取到新的訪問令牌和刷新令牌,再被 SAP Commerce Cloud 重定向到刷新令牌過期時正在操作的頁面。
總結
本文選擇了國內外兩款最具代表性的電商購物網站,使用 Chrome 開發者工具進行探究,分析了這兩款電商網站用户會話管理機制的設計原理,並從前端實現源代碼層面進行了剖析,分享了用户會話管理的各種 Boundary Condition(邊界情況)下實現的注意事項。