你真的瞭解 Session 和 Cookie 嗎?

陳皮的JavaLib發表於2021-07-18

我是陳皮,一個在網際網路 Coding 的 ITer,微信搜尋「陳皮的JavaLib」第一時間閱讀最新文章,回覆【資料】,即可獲得我精心整理的技術資料,電子書籍,一線大廠面試資料和優秀簡歷模板。


前言

我們知道,HTTP 是無狀態的協議,服務端並不知道哪個請求是哪個使用者發起的。有些場景我們需要知道請求是哪個使用者發起的,哪個使用者操作的。例如商城服務,使用者發起請求下單,服務端需要識別是哪個具體的使用者。所以服務端需要使用某種機制來識別,記錄使用者的資訊,狀態等。

Session 機制就能實現,它可以讓無狀態協議的 HTTP 有狀態化。服務端為每個請求服務端的使用者建立其獨享的 Session,用於標識,跟蹤此使用者。Session 是儲存在服務端的,可以儲存在檔案,記憶體,資料等等,並且有唯一的標識 Session ID。服務端建立 Session 之後,服務端通過 HTTP 協議告訴客戶端,在本地 Cookie 中記錄這個 Session ID。這樣同個客戶端以後的每一個請求將 Cookie 一起傳送給服務端,服務端通過儲存在 Cookie 的 Session ID 查出儲存在服務端的 Session ,就能知道此次請求的是哪個使用者了。

Session

Session 中文意思即會話,時效。其實就是客戶端和服務端一對一互動的會話狀態,是一種抽象概念。很多人認為 Session 就是以下程式碼獲取的 Session 物件,其實這只是其中的藉助 Cookie 的一種通用性較好的實現而已。Session 有很多種實現的。

HttpSession session = request.getSession();

因為大部分應用程式藉助 Cookie 來實現 Session 跟蹤,即上述那一行程式碼。Cookie 是實際存在的。客戶端請求服務端並且第一次建立 Session 的時候,服務端通過 HTTP 協議(HTTP響應頭的 Set-Cookie)告訴客戶端,需要在本地 Cookie 中記錄這個 Session ID。key 的值為 JSESSIONID

這樣同個客戶端以後的每一個請求會將 Cookie 一起傳送給服務端,服務端通過儲存在 Cookie 的 Session ID 查出儲存在服務端的 Session ,就能知道此次請求的是哪個使用者了。

HttpSession session = request.getSession();

不過客戶端瀏覽器是可以禁用 Cookie 的,那這種方式就會出現問題。但是我們可以使用 URL 重寫的技術來實現 Session 跟蹤,即在請求服務端的所有請求引數增加一個代表使用者標識或者 Session ID 即可。

http://chenpi.com/list?sid=xxx

前面我們說過 Session 可以儲存在檔案,記憶體,資料庫等地方。會話資訊具體儲存在哪裡其實都得根據自身業務來定,一切脫離業務場景談技術架構的都是耍流氓, 技術本身無好壞,不過是什麼業務場景適合什麼技術而已,這也是架構師考慮技術選型的一方面能力。

不過 Session 機制在叢集服務中需要考慮 Session 一致性問題。可以在叢集服務中做 Session 同步,不過這種方法有一些缺點,例如同步麻煩,同步延遲,多機儲存相同的 Session 浪費儲存空間。另外一種比較常用的方法就是使用專門的 Session 服務叢集來儲存使用者會話資訊,例如 Redis 快取服務,不僅可搭建叢集模式實現高可用可擴充,而且基於記憶體效能速度快。

public UserContext getUserContext(HttpServletRequest request) {
    String userToken = getUserToken(request, COOKIE_KEY);
    if (!StringUtils.isEmpty(userToken)) {
        String userContextStr = redisUtils.getString(RedisKeyUtil.genKey(userToken));
        if (!StringUtils.isEmpty(userContextStr)) {
            return JSON.parseObject(userContextStr, UserContext.class);
        }
    }
    return null;
}

public String getUserToken(HttpServletRequest request, String cookieName) {
    Cookie[] cookies = request.getCookies();
    if (null != cookies) {
        for (Cookie cookie : cookies) {
            if (Objects.equals(cookie.getName(), cookieName)) {
                return cookie.getValue();
            }
        }
    }
    return null;
}

Cookie 是客戶端技術,也是很多人實現 Session 會話的選型,服務端可以讓客戶端將一些資訊寫入本地 Cookie 中,來達到會話跟蹤的目的。不過要注意瀏覽器本地禁用 Cookie 的情況。

說到 Cookie,就不得不說很多廣告商,網站等採用我們個人隱私進行跟蹤,分析我們的行為,進行個性化推薦。很多網站利用第三方 Cookie 獲取使用者資訊,傳送到服務端記錄使用者的行為軌跡。你肯定也遇到在其他應用討論到防脫髮,然後你開啟淘寶驚奇的發現給你推薦各種防脫髮洗髮液。不過,目前有些瀏覽器已經禁用第三方 Cookie 或者進行優化處理了,例如 Safari,Mozilla 等。

我們可以手動將一些資訊設定到 Cookie 中,這樣客戶端不僅可以使用到這些資訊,在後續的請求中,服務端也能根據此資訊做相應的處理。

public void saveUserContext(HttpServletResponse response, String key, String value) {
    // 設定cookie
    Cookie cookie = new Cookie(key, value);
    cookie.setPath("/");
    // 設定有限期,負數例如-1代表Web瀏覽器關閉的時候刪除,如果不設定就預設-1
    cookie.setMaxAge(12 * 60 * 60);
    response.addCookie(cookie);
}

我們可以通過瀏覽器檢視儲存在本地的 Cookie 資訊,而且其他網站也可以掃描使用我們儲存的 Cookie,所以一些安全性或者保密的資訊儘量不要儲存在 Cookie 中,因為資料安全性比較低。正常情況下,使用者登入資訊等比較重要資訊儲存在服務端 Session 中,其他資訊例如會話 ID 可以儲存在 Cookie 中。

而且單個 Cookie 的大小也是有限制的,不同瀏覽器限制規則不一樣,一般大小是幾 Kb。 不同瀏覽器對於一個域名下的 Cookie 數量也是有限制的,一般就幾十個,而且也有數量飽和時淘汰策略,所以使用要注意這些情況,儘量不要超過瀏覽器的限制。

相關文章