­

你真的了解 Session 和 Cookie 嗎?

我是陳皮,一個在互聯網 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 即可。

//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 數量也是有限制的,一般就幾十個,而且也有數量飽和時淘汰策略,所以使用要注意這些情況,盡量不要超過瀏覽器的限制。