【雜談】Remember-Me的實現

貓毛·波拿巴發表於2019-03-17

前言

  此篇隨筆記錄了Remember-Me實現過程中出現的問題和解決方案,以及相關的思考。

正文

1. RememberMe是什麼?

RememberMe意為記住我,對應登入介面的那個勾選項。另一種說法,就是自動登入。

2. 那什麼又是自動登入呢?

我們知道Tomcat或者其他Servlet容器的會話都是有時限的,比如Tomcat的會話時間為30分鐘,30分鐘後,會話將被清除,這時候就不滿足登入狀態了。那你發出下一個請求,按照正常邏輯就會被攔截下來,告知你沒有登入,然後跳轉到登入介面讓你重新登入。自動登入做的就是,當會話過期後,請求會根據Cookie資訊實現透明登入。也就是重新又登入了一遍,產生了一個新的會話。但是在使用者看來,他根本不知道發生了什麼,而且重新登入的過程中不需要再讓使用者輸入賬號密碼。

3. 為什麼不把會話時間調大一點呢?

Tomcat的會話時間是可配置的。你可以設定成3天或者更久。但是這樣就加大了伺服器的開銷。你設定3天,意味著一個HttpSession將會由Tomcat保留3天。(這裡到底是儲存在記憶體還是磁碟尚不確定,執行時記憶體的可能比較大),前面說了RemememberMe其實是自動登入,也就是說,它不會影響會話存在的時間。而且新會話的產生必然由新的請求觸發。

如果使用者登入後只瀏覽了10分鐘,就掛著不動了,在都沒有進行登出操作的情況下。對比一下兩種方案的最大開銷(服務端保留無用會話的時間)

第一種:3天會話時間  => 3天 - 10分鐘 => 3*24*60 - 10 = 4310 分鐘 

第二種:30分鐘會話時間(預設) => 30 - 10 分鐘 = 20分鐘

4. RememberMe的原理是什麼?

本質上就是Cookie。在登入成功後,如果使用者有勾選“記住我”,則服務端在響應中加上Remember-Me的cookie,讓瀏覽器儲存一段時間,如7天。在7天之內,如果使用者會話過期,可憑此實現透明的重新登入。

5. Cookie中包含哪些內容呢?

1. 使用者賬號  => 不然無法知道誰要登入,而且需要依此獲取密碼,生成新簽名進行比對

2. 過期時間 => 過期的時間節點,即如果瀏覽器沒有及時自動清除此cookie,服務端收到後要據此刪除。

3. 與密碼有關的簽名 => 如果沒有密碼的相關資訊,那就很容易地通過偽造cookie,來登入其他人的賬號。但是密碼又不能是明文,必須經過加密。而且不能或者不能太容易被解開。

6. Spring Security中的Cookie格式

Base64(username:expireTime:MD5(username:expireTime:password:secretKey))

其中,簽名signature由username、expireTime、password、secretKey組成的字串,進過MD5加密而成。隨後再整合username、expireTime利用Base64編碼而成,Base64是可解碼的。最終結果,基本上就是一條亂碼了。

7. Cookie有了,服務端要怎麼處理呢?

那就是Filter的事情了。需要定義一個Remember Me的Filter專門處理這個Cookie。值得一提的是,校驗Cookie重新認證的過程還是要查資料庫的,所以應該做到只有在必要的時候,即會話過期的時候,才執行校驗操作。

8. Remember Me Filter與Login Filter的相容

Login Filter所做的就是,讓沒有登入的使用者,必須登入後才能處理請求。一般會直接返回錯誤碼,讓前端跳轉到登入頁,或者直接重定向到登入頁。所以Remeber Me Filter肯定要放置在Login Filter之前。即會話過期後,先進行的應該是自動登入。

9. 登出後,Cookie的刪除

使用者執行登出操作後,肯定要刪除瀏覽器的Cookie。否則,在Cookie過期之前,還是可以通過自動登入的方式,進入網站。但是,你不可能要求使用者去操作瀏覽器,刪除Cookie。這些操作應該由服務端完成。但是HTTP協議只有Set-Cookie的頭,卻沒有類似Remove-Cookie這樣的頭。HttpServletResponse也沒有明確的API可以刪除客戶端的Cookie。後來想到即可以通過新增不儲存的同名Cookie來覆蓋要刪除的Cookie,後來參考了下Security的實現,發現它也是這麼做的。程式碼如下:

public void logout(HttpServletRequest request, HttpServletResponse response) {
    //利用覆蓋的方法刪除客戶端的remember-me cookie
    Cookie cookie = new Cookie("remember-me", (String)null);
    cookie.setMaxAge(0);
    response.addCookie(cookie);
    request.getSession().invalidate();
}

 

相關文章