此篇隨筆記錄了Remember-Me實現過程當中出現的問題和解決方案,以及相關的思考。前端
1. RememberMe是什麼?數據庫
RememberMe意爲記住我,對應登陸界面的那個勾選項。另外一種說法,就是自動登陸。瀏覽器
2. 那什麼又是自動登陸呢?服務器
咱們知道Tomcat或者其餘Servlet容器的會話都是有時限的,好比Tomcat的會話時間爲30分鐘,30分鐘後,會話將被清除,這時候就不知足登陸狀態了。那你發出下一個請求,按照正常邏輯就會被攔截下來,告知你沒有登陸,而後跳轉到登陸界面讓你從新登陸。自動登陸作的就是,當會話過時後,請求會根據Cookie信息實現透明登陸。也就是從新又登陸了一遍,產生了一個新的會話。可是在用戶看來,他根本不知道發生了什麼,並且從新登陸的過程當中不須要再讓用戶輸入帳號密碼。cookie
3. 爲何不把會話時間調大一點呢?網站
Tomcat的會話時間是可配置的。你能夠設置成3天或者更久。可是這樣就加大了服務器的開銷。你設置3天,意味着一個HttpSession將會由Tomcat保留3天。(這裏究竟是保存在內存仍是磁盤尚不肯定,運行時內存的可能比較大),前面說了RemememberMe實際上是自動登陸,也就是說,它不會影響會話存在的時間。並且新會話的產生必然由新的請求觸發。編碼
若是用戶登陸後只瀏覽了10分鐘,就掛着不動了,在都沒有進行登出操做的狀況下。對比一下兩種方案的最大開銷(服務端保留無用會話的時間)加密
第一種:3天會話時間 => 3天 - 10分鐘 => 3*24*60 - 10 = 4310 分鐘 spa
第二種:30分鐘會話時間(默認) => 30 - 10 分鐘 = 20分鐘code
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(); }