本篇摘要
- 本篇將承接上篇
HTTP
講解(戳我查看
)遺留的關于Cookie與Session的介紹,在本篇,將會介紹Cookie
的由來,作用,以及缺點等,進而引出Session
,最后介紹一下它們的性質等,以接我們上文模擬實現的http服務器加上這兩個功能
做測試,來驗證上述的成立性!
歡迎拜訪:
點擊進入博主主頁
本篇主題:解密Cookie與Session的不解之緣!
制作日期: 2025.08.04
隸屬專欄:點擊進入所屬Linux專欄
一· Cookie
介紹
什么是 Cookie
?
- HTTP Cookie(也稱為 Web Cookie、 瀏覽器 Cookie 或簡稱
Cookie
) 是服務器發送到用戶瀏覽器并保存在瀏覽器上的一小塊數據, 它會在瀏覽器之后向同一服務器再次發起請求時被攜帶并發送到服務器上
。 通常, 它用于告知服務端兩個請求是否來自同一瀏覽器, 如保持用戶的登錄狀態、 記錄用戶偏好
等! - 簡單來說,就是記錄用戶的一個
標記
,便于服務器識別用戶!
Cookie
的原理
- 當用戶第一次訪問網站時, 服務器會在響應的 HTTP 頭中設置 Set-Cookie字段, 用于發送 Cookie 到用戶的瀏覽器。
- 瀏覽器在
接收到 Cookie
后, 會將其保存在本地
(通常是按照域名進行存儲
) 。 - 在之后的請求中, 瀏覽器會自動在 HTTP 請求頭中攜帶 Cookie 字段, 將之前保存的 Cookie 信息
發送給服務器
。
Cookie
的用途
- 用戶認證和會話管理(常用)。
- 跟蹤用戶行為。
- 緩存用戶偏好等。
單Cookie行為舉例
下面我們就拿單Cookie的用戶認證
舉個例子(因為會話管理等是結合Session的后續講):
比如我們登錄的時候,
由于http無狀態
,就需要它來記錄用戶登錄時候的賬號密碼等,當用戶第一次成功登錄,就把它記錄下來
,發給瀏覽器
,然后我們關斷這個網頁,下次再去訪問甚至很久或者關了瀏覽器在一段時間內都是可以進行直接登錄的
(這就利用了Cookie機制
,之后每次我們登錄,瀏覽器都會自動把對應域名下的Cookie攜帶上,但是這樣是有風險的
,我們后面談到,因此需要結合Session一起用
)。
Cookie
存儲分類
會話
Cookie(Session Cookie
): 在瀏覽器關閉
時失效。持久
Cookie(Persistent Cookie
): 帶有明確的過期日期或持續時間
,可以跨多個瀏覽器會話存在。
因此可以認為儲存的Cookie分為內存級別與文件級別,就是對應上面的兩種分類,內存中是見不到的,但是文件方式以二進制或 sqlite 格式
存儲,一般我們查看, 直接在瀏覽器對應的選項
中直接查看即可。
比如這種查看文件格式:
Cookie
應用的格式
首先,我們要知道Cookie是在瀏覽器和服務器之間交互的一個標記。
- HTTP 存在一個
報頭選項
:Set-Cookie
, 可以用來進行給瀏覽器設置 Cookie值。 - 在 HTTP
響應頭中添加
, 客戶端(如瀏覽器) 獲取并自行設置并保存Cookie。
具體用法
set-cookie: username=111; expires=sat,03 May 2025 10:34:45 UTC; path=/;
看一下例子:
服務端先Set-Cookie
:
客戶端再發送回來
:
下面我們就以這個例子,說一下下面Cookie的填充部分,我們就以Cookie對應的名稱
,expires
,還有path
講解:
-
對于服務端,需要建立Cookie也就是
Set-Cookie
,那么它后面跟的第一個名稱
就是將來瀏覽器要發送給服務器的(假設我們要進行登錄驗證,那么第一個值就可以設置成對應的關鍵詞
比如登錄,密碼等)。 -
由于下面我們測試的是
文件級別的Cookie
,因此給它設置上過期時間(expires),這個時間格式就是我們上面例子那樣,這里我們采取的是UTC
也就是協調世界時(UTC 是現在用的時間標準, 多數全球性的網絡和軟件系統將其作為標準時間),當然也可以是GMT(格林尼治標準時間),感興趣的可以去了解下,這里不是重點,這里如果設置了expires那么就文件級別
等到到時間就過期自動清除,否則就是內存級別
,瀏覽器關閉自動清除! -
對于path,其實就是對應的路徑,也就是
指定的path
后,只有我們訪問對應的瀏覽器下Cookie保存的路徑時,瀏覽器才會發送這個Cookie,比如保存的是/ 那么始終都會發送,但是如果是/a/b,那么只有訪問這個路徑才會發送
,后面我們驗證下! -
這里我們服務端只需要
按照指定格式
給瀏覽器發生即可,然后瀏覽器就會按照一定規則儲存起來,需要的時候在按照一定格式發回來
,這樣完成交互(這里他倆的解析方式我們不用管)!
如果有需要了解其他的拓展部分,可參考這張表:
屬性 | 值 | 描述 |
---|---|---|
username | peter | 這是 Cookie 的名稱和值,標識用戶名為 “peter”。 |
expires | Thu, 18 Dec 2024 12:00:00 UTC | 指定 Cookie 的過期時間。在這個例子中,Cookie 將在 2024 年 12 月 18 日 12:00:00 UTC 后過期。 |
path | / | 定義 Cookie 的作用范圍。這里設置為根路徑 /,意味著 Cookie 對.example.com 域名下的所有路徑都可用。 |
domain | .example.com | 指定哪些域名可以接收這個 Cookie。點前綴(.)表示包括所有子域名。 |
secure | - | 指示 Cookie 只能通過 HTTPS 協議發送,不能通過 HTTP 協議發送,增加安全性。 |
HttpOnly | - | 阻止客戶端腳本(如 JavaScript)訪問此 Cookie,有助于防止跨站腳本攻擊(XSS)。 |
注意事項:
-
每個 Cookie 屬性都以分號
(;)
和空格( )
分隔。 -
名稱和值
之間使用等號(=)
分隔。 -
如果
Cookie 的名稱或值包含特殊字符
(如空格、 分號、 逗號等) , 則要進行URL 編碼
(比如名稱如果包含了就需要自主進行編碼,否則發給瀏覽器識別不了就不會儲存這個Cookie,這也就要求雙方自主編碼處理了,這里一般還是服務器要注意,因為是它構建Cookie
)。 -
這里我們比如拿
登錄
為例子,此時我們不能一個Set-Cookie后面跟賬號密碼就完了,因為瀏覽器會拿Cookie的第一個名稱作為自己本地保存的名稱
,因此我們需要給它發兩個Cookie:一個username,一個password
,這樣它就會呈現兩個Cookie保存,發過來的時候合并成一個Cookie(如下圖所示)
就是上面我們看到的那樣(就把它當做規則記住)!
Cookie
安全性
-
使用
secure 標志
可以確保 Cookie 僅在 HTTPS 連接上發送, 從而提高安全性。 -
使用
HttpOnly 標志
可以防止客戶端腳本(如 JavaScript) 訪問 Cookie,從而防止 XSS 攻擊。 -
通過合理設置
Set-Cookie 的格式和屬性
, 可以確保 Cookie 的安全性、 有效性和可訪問性, 從而滿足 Web 應用程序的需求。
以上了解即可!
單獨使用Cookie
的風險
有利就有弊:
比如我們登錄賬號的時候,返回來的Cookie就是賬號與密碼然后進行保存
,那么此時如果瀏覽器被黑客監控,此時賬號就相當于被盜了,信息就都泄露了
!!!
- 由于
Cookie 是存儲在客戶端的
, 因此存在被篡改或竊取
的風險。
那么如何避免呢,也就是后續我們引入的Session
了,它雖然避免了盜號
的風險,但是也是會泄露個人數據信息等
!
基于模擬實現HTTP服務器
測試 Cookie
下面我們還是基于我們之前寫的http服務器的代碼進行改造,給它加上請求報頭返回Cookie
的功能即可,先說下思路:
首先,我們密碼設置的是
666
,也就是只要用戶輸入的密碼是666,無論用戶名是什么,此時第一次輸入正確密碼
,就建立Cookie然后給客戶端發送作為Cookie
保存,當后面無論輸入什么或者不輸入都能登錄進來
,然后我們給它加了個截止日期
,也就是到時間Cookie自動清除,還有指定路徑
等!
大致分成以下幾步:
- 構建
Cookie
: 此時我們按照一定的格式把對應的名稱,expires
,path
給它添加進去。
// 獲得時間:string get_mon(int month){vector<string> months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };return months[month];}string get_week(int day){vector<string> weekdays = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };return weekdays[day];}string expires_time(int te){time_t curtime = time(nullptr) + te;struct tm* pm = gmtime(&curtime); // 可以認為傳遞指針為了防止拷貝,返回指針可以認為是便于指針訪問char buff[1024];snprintf(buff, sizeof(buff), "%s, %02d %s %d %02d:%02d:%02d UTC", get_week(pm->tm_wday).c_str(),pm->tm_mday,get_mon(pm->tm_mon).c_str(),pm->tm_year + 1900,pm->tm_hour,pm->tm_min,pm->tm_sec);return buff;}void set_cookie_username(string username){string cookie;cookie = "Set-Cookie: username=" + username + "; expires=" + expires_time(300) + "; path=/;";_cookies.emplace_back(cookie);}void set_cookie_password(string password){string cookie;cookie = "Set-Cookie: password=" + password + "; expires=" + expires_time(300) + "; path=/;";_cookies.emplace_back(cookie);}
login界面
的時候進行提取對應的請求報頭中Cookie的password信息進行保存
,后面無論是get還是post方法拿到的都是一樣的,下面我們就判斷拿到的Cookie
信息是否為666
即可,如果是的話就直接給它返回對應登錄成功界面
,否則:那么看password
是否是666
,如果是就構建Cookie
返回,否則進行報錯處理!
1·驗證cookie是否存在:if (re.get_password() == "666"){ // 存在直接讓它訪問(無需驗證密碼)res.set_route("./wwwroot/video.html");goto again;}if (pw == "666"){ // 密碼正確,cookie不存在,第一次添加cookie:res.set_cookie_username(username);res.set_cookie_password(pw);res.set_route("./wwwroot/video.html");}else{res.set_route("./wwwroot/404.html");}
整體邏輯就這樣,下面我們測試下:
驗證Cookie
可以成功交互
- 首先第一次請求登錄輸入正確密碼:
- 服務器識別到密碼正確給瀏覽器發對應的
Cookie:
- 查看瀏覽器收到的對應的
Cookie:
- 下面我們嘗試錯誤密碼輸入:
- 也是可以成功登錄:
- 這里無密碼也是可以成功登錄的:
- 下面我們已經保存了正確的
Cookie
,上面是get
請求,下面我們驗證下post
請求:
也是成立的!
驗證Cookie
過期
- 因為我們設置的
5min
后過期,現在已經過期了:
- 找不到,被返回了
404
界面:
Cookie
被清空:
驗證 path
路徑
- 我們給對應的構建
Cookie
的時候修改路徑:
- 接著還是老樣子訪問發現進不去了:
驗證Cookie結束!
- 對于代碼 :我們從請求中提取
Cookie
的更改在deserialize
函數里面;然后對Cookie是如何構建的,這兩部分都在http.hpp
文件
里;其次就是處理時候是利用回調函數完成的在main.cc
文件
里;其他部分未做修改
,后續源碼處自提!
二· Session
介紹
上面講到了,單純的Cookie是不安全的
,下面就需要Cookie與Session達成的會話一起來維護
,而session只不過是一種形式,還是以Cookie為載體的,因此用法還走的Cookie那套,下面我們就介紹下Session
!
什么是 Session
?
-
HTTP Session
是服務器用來跟蹤用戶與服務器交互
期間用戶狀態的機制。 -
由于
HTTP協議是無狀態的
(每個請求都是獨立的),因此服務器需要通過Session
來記住用戶的信息。
Session
的原理
-
當用戶首次訪問網站時, 服務器會為用戶創建一個唯一的
Session ID
, 并通過Cookie
將其發送到客戶端
。 -
客戶端在之后的請求中會
攜帶這個 Session ID
, 服務器通過Session ID
來識別用戶, 從而獲取用戶的會話信息
。 -
對服務器而言,服務器通常會將
Session 信息
存儲在內存、 數據庫或緩存中
。
總之Session也是走的Cookie那一套,然后比Cookie更加加密一下
,對應的Cookie信息
不會直接暴露,但是可以客戶端可以拿著它訪問一定的曾經記錄的資源
,并不會像之前那樣因為賬號密碼等直接暴露導致的重大問題
發生!
Session
特性及用途
特性
Session 可以設置超時時間
, 當超過這個時間后, Session 會自動失效(類似Cookie的expires設置
,或者服務端自己記錄超時時間
),服務器也可以主動使 Session 失效, 例如當用戶退出時(服務端識別后去更改對應的Session信息
,當被標記后,再次請求,服務端就去檢查Session發現狀態不對就讓它過期
)!
用途
用戶認證和會話管理(重點)
- 存儲用戶的
臨時數據
(如購物車內容) - 實現
分布式系統的會話共享
(通過將會話數據存儲在共享數據庫或緩存中)
Cookie+Session機制舉例
下面我們就拿購物車內容作為session
工作的例子:
-
比如用戶訪問一個服務器,然后登錄上,此時服務器機會創建對應
唯一的session-id來標識對應資源
,然后作為Cookie返回給瀏覽器;接下來用戶在進行購物車添加的時候,對應的維護在服務端的session-id對應資源也在變化
(由服務器維護) -
如果此時用戶退出來,然后再登陸,就會給對應服務器發送那個
session-id
,然后服務器拿到后識別到已經登錄
,確定好身份
,然后用戶去訪問不同頁面,此時服務器就先去看session-id
資源里面保存的情況然后呈現出來。 -
如果黑客拿到
對應session-id去訪問
,此時服務器也會把它當成真正用戶處理
,和上面過程一樣,訪問暢通無阻
,只不多session都會設置過期
,這就某種程度限制了黑客行為,但是也是不安全的
。
Session
安全性
-
與
Cookie
相似, 由于Session ID
是在客戶端和服務器
之間傳遞的, 因此也存在被竊取的風險,但是一般雖然 Cookie 被盜取了, 但是用戶只泄漏了一個Session ID(以及和它相關的記錄)
, 私密信息暫時沒有全部被泄露的風險(比如密碼等,而且session一般會過期,如果是單Cookie的話就是永久泄露,但是session就是暫時了)。 -
Session ID
便于服務端進行客戶端有效性的管理
, 比如異地登錄。
通俗易懂解釋:
也就是如果用的是
session
,被泄露后,比如:黑客拿到對應的session,它就能夠直接訪問到用戶的資源,也就是被黑客盜取session后
,服務端就把黑客當成原用戶對待
了(只不過黑客拿不到密碼
,而且session只是暫時有效),這樣避免了上面的問題,但是還是有漏洞
,因此還是需要解決方案來防范的
!
安全解決方法:
- 可以通過
HTTPS
和設置合適的Cookie
屬性(如HttpOnly
和Secure
) 來增強安全性。
更詳細版:
-
使用HTTPS防止中間人攻擊來竊聽
session ID
。 -
設置
HttpOnly
和Secure
標志來保護cookie
,防止通過JavaScript訪問和確保只通過HTTPS
傳輸。 -
實施session過期機制,減少
session ID
被盜用的風險。 -
采用更復雜的
token機制
(如OAuth2.0
)代替簡單的session ID。 -
對敏感操作實施額外驗證步驟,如二次確認、短信驗證碼等。
基于模擬實現HTTP
服務器測試 Session
下面我們驗證一下關于Session的正確使用以及過期是如何處理的:
先說下流程:
首先第一次發送正確密碼
666
.然后被服務端識別到是第一次
,然后構建對應的唯一的session并且和資源綁定
,然后作為Cookie
發給瀏覽器,瀏覽器拿到后進行保存,當第二次
,只要訪問同一個服務端就會發送這個session
,此時就隨意登錄
了。
代碼實現大致可以分成以下幾步:
- 先確定好對應
Session-id
和對應資源
的映射
關系,這里我們把對應屬性,狀態,資源
封裝成一個類,利用哈希完成映射
即可,其次就是因為我們用的處理函數是回調,跑到main里面執行,因此為了避免回調傳參
,我們把管理資源的類也就是這個哈希表
初始化在全局
。
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <ctime>
#include <unistd.h>
#include <unordered_map>
using namespace std;
class session
{public:session(const string& username, const string& status): _username(username), _status(status){_create_time = time(nullptr);_terminatal_time = _create_time + 300; // 默認300秒后過期}~session(){}string _username;string _status;uint64_t _create_time;uint64_t _terminatal_time;// 用戶其他狀態可以后續添加
};using session_ptr = shared_ptr<session>;class session_manager
{public:session_manager(){srand(time(nullptr) ^ getpid());}string add_session(session_ptr s){// 保證sessionid為正數uint32_t randomid = rand() + time(nullptr); // 隨機數+時間戳,實際有形成sessionid的庫,比如boost uuid庫,或者其他第三方庫等string sessionid = std::to_string(randomid);_sessions.insert(std::make_pair(sessionid, s));return sessionid;}session_ptr get_session(string sessionid){if (_sessions.find(sessionid) == _sessions.end()){return nullptr;}return _sessions[sessionid];}~session_manager() {}private:unordered_map<string, session_ptr> _sessions;
};
- 解析請求的時候利用和上面
Cookie一樣的機制
,其次就是建立Cookie變一下
,這里雖然我們不寫截止日期,可以在服務端內部session管理的資源內設置
。
void set_cookie_session(string session_id) {string cookie;cookie = "Set-Cookie: sessionid=" + session_id + ";";_cookies.emplace_back(cookie);}
- 下面就是如何響應這個session,第一次先檢查有沒有
session
被檢測出來,如果有那么就找到它的資源看是否為空
(有可能瀏覽器保存了,但是服務端重啟了,導致服務端數據沒了)其次就是檢查下對應的session資源里有沒有超時標記
,都沒有的話就直接把資源給它即讓它登錄
;否則再看密碼是否666
,是的話就建立session
作為Cookie
寫回去,反之就報錯!
// 2·驗證session是否存在(也就是cookie——session共同使用):if (!re.get_password().empty()){// 非第一次套用session:session_ptr sn = ursm->get_session(re.get_password());// cookie沒有過期if (sn && sn->_terminatal_time > time(nullptr)){use_log(loglevel::DEBUG) << sn->_username << " --- " << sn->_status;res.set_route("./wwwroot/video.html");goto again;}else{ // 可能服務器程序關閉了,此時瀏覽器還保存了對應的之前的cookie-sessionid,這樣下次如果服務器訪問就會找不到:use_log(loglevel::DEBUG) << "用戶的cookie已經過期, 需要清理!";res.set_route("./wwwroot/404.html");}}else{// 第一次建立session:if (pw == "666"){string user = "user-" + std::to_string(number++);session_ptr sn = make_shared<session>(user, "login");string sessionid = ursm->add_session(sn);res.set_cookie_session(sessionid);use_log(loglevel::DEBUG) << sn->_username << " 的cookie-session成功被添加,id是:" << sessionid;res.set_route("./wwwroot/video.html");}else{res.set_route("./wwwroot/404.html");}}again: 1;
驗證Session環節:
- 老樣子還是以正確密碼登錄:
- 然后我們會發現服務器給瀏覽器寫回一個Cookie:
- 這個Cookie就綁定了一些狀態,資源等,下面我們隨機密碼訪問:
- 也是成功返回理想界面:
- 因為這里我們Session設置額是有截止日期,五分鐘后過期:
- 因此如果過期后我們在進行訪問:
- 就會放回404頁面:
- 服務器這邊日志也顯示過期了:
驗證Session結束!
- 對于代碼:
Session-id
對于資源等放在session.hpp
文件中,然后就是在tcpserver.hpp
處理任務改成單進程
就可以驗證對應session功能,剩下的改動就是對應的main.cc
中對session的處理邏輯,以及http.hpp
中構建session-cookie邏輯
了!
關于測試Session
時出現的bug
總結
-
BUG-1:
-
就是因為我們自己
服務器就是個程序
,測試的時候肯定會關閉重啟
之類的,因此之前儲存的session-id對應資源也會丟失
,如果之前被瀏覽器保存了對應的session
但是服務器自己重啟了,然后瀏覽器即便發回來正確session-id
也無法被服務器識別
(這里我們就認為是過期了,需要清理),因此服務器處理session-id
的時候判斷一下
即可! -
BUG-2:
-
也是一直
困擾了博主半天的bug
,當時由于沒看底層tcp是如何實現的
,導致了一直就是一開始成功發送session,而且瀏覽器也發給服務器了,但是服務器卻查找資源沒找到
;找了幾個小時,把那些頭文件都看了個遍,最后仔細看tcp實現
,發現是多進程
實現的,而且每次都是派出孫子進程去執行執行的,那么這樣就寫實拷貝,因此每次看到的session
由于哈希映射的資源都會被清空
(即使服務器不重啟)。
解決方案1:
- 只為了
追求出測試效果
,可以考慮單進程,但是這樣就會出現服務器壓力過大,反應慢,甚至加載不出來等
(比如加載一個動圖需要服務器那邊的進程一直執行,而當用戶在瀏覽器繼續點擊的時候又會發送請求,此時服務器就無法應答,一直卡著)當然實際這種方法是絕對不可行的,這里我們使用的單進程這種!
解決方案2:
- 可以考慮
多線程
或者線程池
去實現http
這塊請求處理與答復
!
三·基于Cookie
與Session
的總結
-
這里我們只需要記住單純
Cookie機制
不結合Session
來完成對http
無狀態的記錄是十分不安全的,因此需要session
與cookie
共同完成會話的記錄,但是這樣也不是絕對安全
的,因此在此基礎上又會加上一些安全措施來保證用戶安全上網
! -
因此,記住,
Cookie
通過記錄某些信息來使得服務端由狀態性而Session
是在Cookie
基礎上一種相對保密的措施,以id
映射對應用戶曾經訪問資源,狀態的一種手段
,但都是不安全的
,還需要另加保護防范!
四· 源碼匯總
點擊這里跳轉my-gitee
查看測試源代碼
五· 本篇小結
-
在本篇中深入學習了
Session原理
,用法,不足等等,Cookie和此外還進行了基于自己實現的HTTP服務器
模擬測試這些功能,在書寫代碼的過程中同樣遇到了好多bug
,也浪費了很多時間
,上面也總結了,因此之后在學習的時候一定要把整個代碼運行過程的輪廓(包括上層和底層都考慮清楚
),拒絕類似bug
再次發生,向下一次的網絡學習進擊! -
太年輕的人,他總是不滿足!