移動端會話問題常在「重新打開應用」後才暴露:舊帳號的快取短暫閃現,多個請求同時更新權杖,或使用者登出後背景回呼又把舊憑證寫回來。解決它需要把憑證生命週期與業務資料生命週期一起設計。
明確存放與邊界
敏感權杖放在平台安全儲存能力中;普通業務快取應按使用者 ID 分區,不能僅以介面路徑作為鍵。不要把權杖寫進記錄、深鏈或可被其他應用讀取的明文文件。啟動時先恢復會話並確認使用者身份,再顯示該使用者的私有快取。
更新只允許一個進行中
權杖將過期時,多個請求共享同一個更新 Promise / Future;更新成功後繼續請求。若伺服器端拒絕更新,統一失效會話並取消待發送請求,不能讓每個頁面各自彈一次登入框。登出時遞增會話世代:所有帶舊世代的非同步響應即使晚到,也不得寫入新帳戶狀態。
session A, epoch 7 → 發出請求
使用者登出 / 切換帳戶 → epoch 8,清理 A 私有快取
舊請求回傳 → epoch 不匹配,丟棄響應
驗證時覆蓋強制關閉應用重啟、時鐘偏差、並行 401、離線登出與快速切換帳號。客戶端防護不能代替伺服器端對每個請求的權限檢查。
把會話當成一組可失效的狀態
啓動應用時,不要因為安全存儲里有 token 就立即顯示已登錄頁面。先讀取憑據、確認本地賬號標識,再向服務端驗證或刷新會話;校驗期間頁面處於明確的 restoring 狀態。訪問令牌盡量短壽命,刷新憑據放在系統提供的受保護存儲中;普通偏好設置、日誌和崩潰報告不應包含憑據。需要離線瀏覽時,應事先定義哪些緩存可在未驗證會話時顯示,以及何時必須遮蔽敏感內容。
併發 401 最容易把實現寫亂:五個請求同時發現過期,不能各自刷新並互相覆蓋。讓一個賬戶同一時刻只有一個刷新任務,其餘請求等待同一結果。刷新失敗或服務端撤銷會話時,原子地清掉憑據、賬戶作用域緩存和待執行的敏感操作,再導航到登錄頁。賬號 A 登出後立刻登錄 B,A 的遲到響應和刷新結果必須因會話世代不匹配而丟棄。
restoring → authenticated → refreshing → authenticated
↘ refresh rejected → signedOut
signedOut → signingIn → authenticated(new account generation)
明確註銷和離線邊界
用戶主動登出時,先在本地阻斷新的認證請求並清理敏感狀態;若當前離線,服務端撤銷可以排隊或在恢復網絡後執行,但界面必須立即視作已登出。對高風險操作,服務端始終按每次請求重新校驗權限和令牌,不能依賴客戶端頁面是否隱藏按鈕。驗收覆蓋殺進程、設備時間偏差、併發 401、離線登出、A/B 快速切換和刷新憑據輪換。記錄會話狀態轉移及失敗類別,不記錄令牌本身。
