原文連結:https://www.dubby.cn/detail.html?id=9109
隨著服務化的普及,直接維護session的越來越困難,現在一般來說都會使用一個token來表示使用者的登入狀態,用來標識這個使用者的身份,這就是登入態。
登入態的解析一般就是入參是token,而返回結果是userId的方法(或服務、介面)。
一般來說,登入態校驗的服務,QPS都會很大,因為大部分請求都需要依賴這個校驗結果來做自己的業務邏輯。所以如果保證登入態解析的可靠性,和低延時是相當重要的。
這裡我簡單描述下我所知道的幾種常見的實現方式。
1.儲存型
儲存型token,就是根據token這個字串,可以在服務端儲存上查到使用者資訊,那就是校驗成功,如果查不到,那就是校驗失敗。
對於QPS很低的應用,可以使用MySQL來做儲存,對於QPS很高的,可以使用Redis來做儲存。這個方案的優勢是,簡單清晰,易於實現,且很自由,邏輯控制完全在服務端,比如踢誰下線、統計多少人登入,都一目瞭然;缺點是,這個解析的強依賴於這個儲存,儲存響應慢,那解析延時就高,儲存可靠性低,那解析可靠性就不會高。雖說MySQL Cluster或者Redis Cluster已經很可靠的,但網路抖動,就真的無能為力了,網路一抖,解析就失敗或超時。
且,成本其實很高的,每個使用者的每次登陸,都需要在儲存中記錄一個資料,如果是MySQL還好,如果是Redis,那記憶體成本其實不小,感興趣的可以自己算下。
2.計算型
計算型token,就是把使用者資訊如userId加密成一個字串,這個字串就是token,那麼每次解析其實就是解密,解密出明文,比如userId,generateTimestamp,那麼再根據generateTimestamp判斷是否過期,如果解析都失敗了,那就直接失敗。
這個方案的優點是,效能很好,延時極低,且做到了真正的服務端無狀態,不依賴任何外部儲存,單看服務的話,可靠性幾乎是最高的;但是缺點也很明顯,完全依賴加密演算法,那如果被破解,就完蛋了,這個可以考慮定期更換金鑰,但是登入態資訊完全放在客戶端,服務端對的登入態的控制就很難了,比如踢誰下線,統計登入使用者幾乎不可能。
3.計算+儲存
考慮到計算和儲存的優劣勢,我們可以考慮結合他們。這裡舉個例子token是個加密後的密文,解密後可以分成好幾個欄位,分別代表userId,generateTime,randomString。那麼,如果解析都失敗了,那就直接失敗了,如果解析成功了,再根據generateTime來判斷是否過期,如果過期了,那也直接失敗了,如果都通過了,在拿randomString去儲存中查詢,以Redis為例,我們的儲存結構可以設計成key是userId,value是sorted set,其中每個元素就是randomString,查得到就是合法,查不到就是非法。
優點是,那麼在極端情況下,如果Redis訪問失敗/超時,那也可以退回成純計算型token,暫時不去校驗randomString,等Redis恢復後繼續校驗;缺點是,實現複雜。
說到這裡可靠性其實已經很高了,但延時呢?
4.長短token
這裡借鑑了快取的概念,當然這裡的快取不是Redis。以PC為例,cookie裡可以set兩個,longToken,shortToken,其中longToken可以使用第三步說的計算+儲存來實現,那麼shortToken呢?每次校驗登入態時,同時傳入longToken,shortToken,如果沒有shortToken,那麼就去解析longToken,解析完之後如果成功,就生成一個新的shortToken給客戶端;如果有shortToken,那麼就去解析shortToken,shortToken完全使用加密的方式,如果shortToken解析成功就算成功。
需要注意shortToken的有效期一定要合適,這裡的shortToken其實就是快取,如果有效期合適的話,大部分請求都會由shortToken解析出來,避免了對儲存的網路呼叫。如果有效期太長,會不安全,且踢出某個使用者可能會有延時才能生效,有效期如果太短的話,那快取效果可能不明顯,所以需要結合業務特性來做決定。