賬戶登錄唯一性方案

某日小明得知iPlan推出了個人資源庫功能,興致勃勃的申請了賬號想嚐個鮮。

可是小明是個不愛吃獨食的好同學,於是他把自己的賬戶分享給了好機油小智。花了一份錢實際得到了兩個賬戶,小明暗暗爲自己的智商點了個贊。


蜜月期總是充滿驚喜的,

剛打開資源庫,發現好幾個分類已經在那兒擺好造型等我光顧了

直接搜索機器人,能想到的機器人都有了,想用哪個用哪個

菜籃子都填的嚴嚴實實的,也不用費腦子選這選那了


然而小明的喜悅才持續幾分鐘就發現了不對。

隨便打開個分類,系統就顯示:“對不起,您操作的分類可能已刪除”

編輯個資源看看?系統提示:“對不起,該資源已刪除”

好嘛,那我加到菜籃子裏先,然而過一會兒再去看的時候,菜籃子已經空空如也


什麼鬼?這iPlan不會騙錢吧,趕緊找客服,這不找不知道一找嚇一跳。客服提示:您的賬戶在異地登錄並進行了大量操作,請保管好你的賬號


一切已經真相大白了,還是好機油搞的鬼,看來自己的賬戶還是不能隨便分享給別人啊。
咋辦呢?
改密碼?這是要友盡的節奏啊;
直接說?丟不了這人啊。


正在小明內心萬馬奔騰的檔口,iPlan客服又很貼心的給出了反饋:
我們鑑於部分用戶在多臺機器終端使用同一賬戶的操作可能造成數據錯誤和體驗下滑,大制決定完善賬戶管理規則,單賬戶同時只能在一臺終端使用。後續發佈會加上限制措施,確保用戶體驗。


這客服也太貼心了,小明立馬找到好機油傳達了這個反饋,機油也給力,當即決定自己也買個賬號以後使用資源分享功能一樣可以在iPlan協(gao)作(ji)。
本來到這裏,小明就可以坐等升級繼續搞基了,然後小明是個閒不住的主,打電話給客服一定要弄清楚限制措施是怎麼實現的。

——————————正經的分割線——————————



小明遇到的問題,我們將他稱爲單一登錄問題,區別於單點登錄(SSO)

單一登錄是指同一賬號只允許在一臺終端(瀏覽器,PAD)進行登錄和相關操作,主要適用於應用型系統,可以有效的防止數據錯誤和髒操作。
單點登錄則是指對於多系統採用統一的認證機制,以至於用戶只需要登錄一次就可以獲得多個系統的信任進行相關操作,主要適用於企業業務整合解決方案。

如果不實現單一登錄會有什麼問題呢,

  1. 你看到的東西可能不是最新的(其他終端正在編輯呢)
    小明看到了一個分類,點進去發現小智在其他終端已經刪除了這個分類

  2. 你編輯進去的東西可能不會保存(其他終端可能馬上就把你改掉了)
    小明加了很多東西在菜籃子裏,過一會兒再進去看小智已經幫你清空了

仔細看看這不就是我們數據庫事務經常遇到了不可重複讀和幻讀問題嗎?
我們的解決思路也可以參照數據庫事務的控制策略,加入版本控制(MVCC)的用戶登錄級別控制。思路如下:


a) 小明首次登錄的時候,服務器針對xiaoming這個賬戶分配一個版本號的令牌,並在小明本地和服務器上同時保存,如圖所示(xiaoming-0001)

b) 之後小明每一次和服務器對話都需要驗證一下令牌,
小明:我是小明,此地無銀三百兩
服務器:一枝紅杏出牆來,成交

c) 在小明玩的正high的時候,好
機油小智上線,當然用的還是xiaoming這個賬號,於是服務器很公平的給了小智一個新的令牌(xiaoming-0002),
小智也開始不停的和服務器對話了

小智:我是小明,天王蓋地虎
服務器:寶寶心裏苦,成交

d) 輪到小明瞭
小明:我是小明,此地無銀三百兩
服務器:寶寶心裏苦,失敗。
服務器:您已經成功被綠,請重新登錄。

於是乎小明就只能眼睜睜的看着曾今的最愛iPlan和好機油小智眉來眼去了。


那麼有沒有辦法不被綠呢?
很簡單,捂好你的賬號,再好的機油也不能借。
或者土豪一點直接買個賬號送給機油,這不剛好過年了嘛。


——————————技術的分割線——————————

最後在技術實現層面進行簡略說明,滿足一下小明的好奇心。其實這個功能實現的重點在於令牌的生成,需要確保不同終端用戶獲取到的令牌是不一致的。

  1.  很容易就想到的一個策略是通過系統時間來作爲生成令牌的基礎,這當然是一個思路,畢竟毫秒級完全相同的請求概率非常小;

  2.  通過http頭信息獲取用戶登錄ip也是一種可行的策略,這種方式需要解決通過代理登錄的僞IP或者隱藏IP;

  3. 還有一種直接使用sessionID作爲生成令牌的基礎,因爲sessionID是由服務器生成並保證唯一性的隨機值


最終考慮方案複雜度,選定sessionID作爲令牌的生成基礎。在動手之前先梳理一下session在tomcat下的工作機制。

cookie flow.png

1) 客戶端向服務器發出請求
2) 服務器生成session,並返回sessionId給客戶端,包含setCookie在頭部
3) 瀏覽器將sessionId保存在cookie中,失效期爲會話結束
4) 正常會話的時候在request head攜帶sessionId
5) 會話結束(關閉瀏覽器),cookie失效


session lifecycle.jpg


實現步驟

1, 登錄方法

if (userKeyOnHandService.findUserToKen(userName)) {
	userKeyOnHandService.replaceUserToken(userName, httpSession.getId());
} else {
	userKeyOnHandService.addUserToken(userName, httpSession.getId());
}


2, 攔截器方法

boolean validateResult = userTokenOnHandService.validateToken(userName, request.getSession().getId());

//validate token using userName and sessionId
public boolean validateToken(String userName, String token) {
	if (this.exists(USER_TOKEN_PREFIX + userName) && this.get(USER_TOKEN_PREFIX + userName).equals(token)) {
		return true;
	}
	return false;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章