网络编程—HTTP Cookie与Session

📅 2026/7/24 6:45:07
网络编程—HTTP Cookie与Session
前言在日常的Web开发与网络通信中有几个问题困扰网站是如何记住我的登录状态的HTTP协议明明是无状态的为什么我刷新页面之后依然保持着登录这些问题背后隐藏着Web开发中三个最核心的技术概念——Cookie、Session和HTTPS。本文将系统梳理HTTP Cookie与Session的工作机制以及HTTPS协议的加密原理与证书体系。一、登录场景1.1 熟悉的场景打开B站输入账号密码登录。关闭浏览器第二天再次打开B站发现依然处于登录状态——不需要重新输入密码。问题来了B站是如何“认识”我这个登录用户的答案要从HTTP协议的特性说起。1.2 HTTP的无状态特性HTTP协议被设计为无状态的——这意味着每次客户端与服务器之间的请求-响应都是独立的服务器不会自动记住两次请求是否来自同一个客户端。就像去一家咖啡馆每次点单店员都不记得你之前来过每次都要从头开始交流。无状态设计的好处是简化了协议降低了服务器的负担。但也带来了一个明显的问题服务器无法自动识别连续请求是否来自同一用户。试想一下如果没有额外的机制用户登录后访问个人中心服务器无法识别“该用户已登录”连续两次添加商品到购物车服务器无法关联为同一用户的操作。1.3 会话管理为了解决HTTP无状态带来的问题Web应用需要实现以下能力目标说明身份识别确定请求来自哪个客户端状态保持记录用户的登录状态、操作历史等安全性防止身份伪造和信息泄露为了实现这些目标Cookie和Session应运而生。二、HTTP Cookie2.1 什么是CookieHTTP Cookie是服务器发送到用户浏览器并保存在浏览器上的一小块数据它会在浏览器之后向同一服务器再次发起请求时被携带并发送到服务器上。简单来说Cookie就像是服务器贴在用户浏览器上的一张“小纸条”上面写着一些信息。浏览器在后续访问同一网站时会自动把这张纸条展示给服务器看。2.2 原理Cookie的工作流程可以分为三个步骤第一步服务器下发Cookie当用户第一次访问网站时服务器会在HTTP响应头中设置Set-Cookie字段将Cookie发送到用户的浏览器。第二步浏览器保存Cookie浏览器接收到Cookie后会将其保存在本地。第三步浏览器自动携带Cookie在后续的请求中浏览器会自动在HTTP请求头中携带Cookie字段将之前保存的Cookie信息发送给服务器。2.3 格式HTTP响应头中的Set-Cookie字段用于给浏览器设置Cookie值。一个完整的示例如下Set-Cookie: usernamepeter; expiresThu, 18 Dec 2024 12:00:00 UTC; path/; domain.example.com; secure; HttpOnly2.4 属性属性说明示例NameValueCookie的名称与值usernamepeterExpires过期时间RFC 1123格式Thu, 18 Dec 2024 12:00:00 UTCPath作用路径path/表示全站生效Domain作用域名点前缀表示包含子域名domain.example.comSecure仅HTTPS协议发送增加安全性HttpOnly禁止JavaScript访问防止XSS攻击过期时间如果设置了expires属性Cookie将在指定的时间后过期如果没有设置则Cookie默认为会话Cookie浏览器关闭即失效。GMT与UTCGMT基于地球自转而UTC基于原子钟更加精确。在实际使用中两者差别很小多数情况下可以互换使用。2.5 分类类型特点存储位置会话Cookie浏览器关闭时失效浏览器内存持久Cookie带有明确过期时间浏览器本地文件如果Cookie是持久性的它实际上是浏览器特定目录下的一个文件。但直接查看这些文件会看到乱码因为Cookie文件通常以二进制或SQLite格式存储。2.6 用途Cookie在实际应用中有三大用途用户认证和会话管理跟踪用户行为缓存用户偏好2.7 例子以下是一个用C实现的简易HTTP服务器中设置Cookie的示例// 生成RFC 1123格式的过期时间 std::string GetMonthName(int month) { std::vectorstd::string months {Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec}; return months[month]; } std::string GetWeekDayName(int day) { std::vectorstd::string weekdays {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; return weekdays[day]; } std::string ExpireTimeUseRfc1123(int t) { // t为秒级别的未来UTC时间 time_t timeout time(nullptr) t; struct tm* tm gmtime(timeout); // gmtime获取UTC时间不能用localtime带时区 char timebuffer[1024]; snprintf(timebuffer, sizeof(timebuffer), %s, %02d %s %d %02d:%02d:%02d UTC, GetWeekDayName(tm-tm_wday).c_str(), tm-tm_mday, GetMonthName(tm-tm_mon).c_str(), tm-tm_year 1900, tm-tm_hour, tm-tm_min, tm-tm_sec); return timebuffer; } // 设置Cookie std::string ProveCookieWrite() { return Set-Cookie: usernamezhangsan;; } // 设置带过期时间的Cookie1分钟后过期 std::string ProveCookieTimeOut() { return Set-Cookie: usernamezhangsan; expires ExpireTimeUseRfc1123(60) ;; } // 设置路径限制 std::string ProvePath() { return Set-Cookie: usernamezhangsan; path/a/b;; }测试结果当浏览器访问服务器时服务器通过Set-Cookie响应头写入Cookie。刷新浏览器后Cookie会自动在HTTP请求头中被提交回服务器。2.8 隐患Cookie存储在客户端存在两个风险被篡改用户或攻击者可以修改Cookie的内容被窃取如果传输过程未加密Cookie可能被中间人截获如果Cookie中直接存放了用户名、密码等敏感信息一旦泄露后果不堪设想。为了缓解这些风险以下措施Secure标志确保Cookie仅在HTTPS连接上传输HttpOnly标志防止JavaScript读取Cookie抵御XSS攻击三、HTTP Session3.1 为什么需要Session既然Cookie存在安全隐患——用户的私密数据保存在浏览器端容易被盗取或泄露那能不能把敏感数据放到服务器端存储呢Session就是为了解决这个问题而生的。3.2 什么是SessionHTTP Session是服务器用来跟踪用户与服务器交互期间用户状态的机制。由于HTTP协议是无状态的服务器需要通过Session来“记住”用户的信息。如果说Cookie是“会员卡”客户端持有那么Session就是“保险柜”服务器持有。3.3 原理Session的工作流程如下第一步创建Session当用户首次访问网站时服务器会为用户创建一个唯一的Session对象。第二步生成Session ID服务器为这个Session对象生成一个唯一的Session ID并通过Cookie将其发送到客户端。第三步客户端携带Session ID客户端在后续的请求中会自动携带这个Session ID。第四步服务器识别用户服务器通过Session ID来识别用户从而获取该用户的会话信息。3.4 区别维度CookieSession存储位置客户端服务器端安全性较低较高数据大小有限制无限制生命周期可长期存储默认30分钟超时资源消耗客户端承担服务器承担3.5 安全性与Cookie相比Session在安全性上有天然的优势——用户的状态信息存储在服务器端客户端只持有一个Session ID。即使Session ID被窃取攻击者获得的也只是一个标识符而不是用户的敏感数据。此外服务器可以主动管理Session的有效性比如检测异地登录、强制Session失效等。当然Session ID本身在传输过程中仍有可能被截获因此仍需要配合HTTPS和合适的Cookie属性来增强安全性。3.6 超时与失效Session可以设置超时时间超过这个时间后Session会自动失效。服务器也可以主动使Session失效例如当用户登出时。默认超时时间通常为30分钟每次请求都会“续期”。3.7 示例以下是一个用C模拟Session管理的实现Session.hpp——Session对象定义与管理器// Session对象存储用户状态信息 class Session { public: Session(const std::string username, const std::string status) : _username(username), _status(status) { _create_time time(nullptr); } std::string _username; std::string _status; uint64_t _create_time; }; using session_ptr std::shared_ptrSession; // Session管理器管理所有Session对象 class SessionManager { public: SessionManager() { srand(time(nullptr) ^ getpid()); } // 添加Session生成Session ID std::string AddSession(session_ptr s) { uint32_t randomid rand() time(nullptr); std::string sessionid std::to_string(randomid); _sessions.insert(std::make_pair(sessionid, s)); return sessionid; } // 根据Session ID获取Session session_ptr GetSession(const std::string sessionid) { if (_sessions.find(sessionid) _sessions.end()) return nullptr; return _sessions[sessionid]; } private: std::unordered_mapstd::string, session_ptr _sessions; };HttpProtocol.hpp——HTTP请求处理中的Session集成class Http { public: Http(uint16_t port) { _tsvr std::make_uniqueTcpServer(port, std::bind(Http::HandlerHttp, this, std::placeholders::_1)); _tsvr-Init(); _session_manager std::make_uniqueSessionManager(); } // 生成Set-Cookie响应头写入Session ID std::string ProveSession(const std::string session_id) { return Set-Cookie: sessionid session_id ;; } std::string HandlerHttp(std::string request) { HttpRequest req; HttpResponse resp; req.Deserialize(request); req.Parse(); static int number 0; // /login路径模拟登录创建Session if (req.Url() /login) { std::string sessionid req.SessionId(); if (sessionid.empty()) { // 首次登录 std::string user user- std::to_string(number); session_ptr s std::make_sharedSession(user, logged); std::string sessionid _session_manager-AddSession(s); resp.AddHeader(ProveSession(sessionid)); } else { // 已登录验证Session session_ptr s _session_manager-GetSession(sessionid); if (s ! nullptr) { // Session有效用户活跃中 } } } resp.SetCode(200); resp.SetDesc(OK); resp.AddHeader(Content-Type: text/html); resp.AddContent(htmlh1helloworld/h1/html); return resp.Serialize(); } private: std::unique_ptrTcpServer _tsvr; std::unique_ptrSessionManager _session_manager; };3.8 测试用两个不同的浏览器如Chrome和Edge分别访问服务器的/login路径删除浏览器中指定服务器的所有历史Cookie浏览器A访问/login服务器创建Session并写入Cookie浏览器B访问/login服务器为其创建新的Session两个浏览器访问任意站点资源服务器能够区分它们测试结果服务器端日志显示不同浏览器携带不同的Session ID服务器能准确识别每一个客户端。3.9 小结Cookie和Session不是“二选一”的对立关系而是“好搭档”Cookie负责“传递钥匙”在客户端存储Session IDSession负责“保管物品”在服务器端存储用户状态数据Session ID是桥梁通过Cookie传递的Session ID将客户端与服务器端的Session对象关联起来四、HTTPS协议4.1 为什么需要HTTPS前文提到Cookie和Session解决了“识别用户”的问题但传输过程中的安全性问题依然存在。HTTP协议的内容是以明文方式传输的。这些明文数据会经过路由器、WiFi热点、通信运营商、代理服务器等多个物理节点。如果信息在传输过程中被劫持传输的内容就完全暴露了。更严重的是劫持者还可以篡改传输的信息且不被双方察觉——这就是中间人攻击。一个经典的例子是运营商劫持用户点击下载某个APP的按钮结果下载下来的却是另一个推广软件。这就是因为运营商在传输过程中篡改了HTTP响应内容。HTTPS正是为了解决这些问题而生的——它在HTTP协议的基础上引入了一个加密层。4.2 概念4.2.1 什么是加密加密把明文进行一系列变换生成密文。解密把密文再进行一系列变换还原成明文。密钥在加密和解密过程中辅助完成变换的数据。历史小知识加密解密已经发展成一门独立的学科——密码学。密码学的奠基人之一正是计算机科学的祖师爷艾伦·麦席森·图灵。4.2.2 对称加密对称加密采用单钥密码系统加密和解密使用同一个密钥。特点算法公开计算量小加密速度快加密效率高常见算法DES、3DES、AES等。简单示例明文a 1234密钥key 8888加密a ^ key得到密文9834。解密时9834 ^ key还原为1234。4.2.3 非对称加密非对称加密需要两个密钥公钥和私钥。特点算法强度复杂安全性依赖于算法与密钥加密解密速度比对称加密慢很多常见算法RSA、DSA、ECDSA等。核心机制用公钥加密的密文只有对应的私钥能解密反过来用私钥加密的密文也只有对应的公钥能解密生活类比B给A一把锁公钥A把文件锁进盒子只有B拿着钥匙私钥才能打开。4.2.4 数据摘要数据摘要利用单向散列函数对信息进行运算生成一串固定长度的数字摘要。特点不是加密从摘要很难反推原文常用于判断数据是否被篡改常见算法MD5、SHA1、SHA256、SHA512等。4.2.5 数字签名数字签名 摘要经过加密。4.3 HTTPS的加密方案方案一仅使用对称加密如果通信双方持有同一个密钥且没有别人知道通信是安全的。问题服务器要为大量客户端提供服务每个客户端都需要不同的密钥。密钥的传输本身也需要加密——这就成了“先有鸡还是先有蛋”的循环问题。方案二仅使用非对称加密服务器将公钥以明文传输给浏览器浏览器用公钥加密数据发送给服务器。问题1服务器到浏览器的数据传输如何保障安全如果服务器用私钥加密数据浏览器用公钥解密——但公钥一开始是明文传输的中间人也能获取。问题2非对称加密速度太慢不适合大量数据传输。方案三双方都使用非对称加密客户端和服务端各自拥有公钥和私钥并交换公钥。问题效率太低且仍然存在中间人攻击的风险。方案四非对称加密 对称加密这是最接近正确答案的方案服务端具有非对称公钥S和私钥S客户端发起HTTPS请求获取服务端公钥S客户端在本地生成对称密钥C用公钥S加密后发送给服务器服务器用私钥S解密获得对称密钥C后续通信全部使用对称密钥C进行加密优点兼顾了非对称加密的安全性和对称加密的效率。但是这个方案仍然存在一个致命漏洞——中间人攻击。4.4 中间人攻击中间人攻击是HTTPS需要解决的核心威胁。攻击过程如下服务器拥有公钥S和私钥S中间人拥有公钥M和私钥M服务器明文传送公钥S给客户端中间人劫持报文提取公钥S保存将自己的公钥M替换进去客户端收到公钥M生成对称密钥X用M加密后发送中间人劫持报文用私钥M解密获得X再用公钥S加密后转发给服务器服务器用私钥S解密获得X双方开始用X进行对称加密通信——但一切都在中间人的掌控之中问题本质客户端无法确认收到的公钥是否真的来自目标服务器。4.5 CA证书与数字签名4.5.1 什么是CA证书为了解决“如何确认公钥的来源可信”这个问题引入了CA证书颁发机构。服务端在使用HTTPS前需要向CA机构申领一份数字证书。证书就像网络世界的“身份证”包含以下信息信息项说明证书发布机构签发该证书的CA证书有效期证书的起止时间公钥服务端的公钥证书所有者域名等身份信息数字签名CA对证书内容的签名4.5.2 数字签名的形成CA机构签发证书时形成数字签名的过程CA机构拥有非对称加密的私钥A和公钥ACA对服务端申请的证书明文数据进行Hash形成数据摘要CA用私钥A对数据摘要进行加密得到数字签名S证书明文 数字签名S 数字证书4.5.3 客户端验证证书客户端收到证书后进行校验第一步检查基本信息证书有效期是否过期证书发布机构是否受信任第二步验证证书是否被篡改从操作系统中获取该CA的公钥A用公钥A对数字签名解密得到一个Hash值计算整个证书明文的Hash值对比hash1和hash2是否相等相等则说明证书未被篡改4.6 HTTPS工作流程综合以上所有机制HTTPS的完整工作流程如下第一阶段证书验证客户端向服务器发起HTTPS请求服务器返回CA签发的数字证书客户端验证证书有效验证通过后客户端信任证书中的公钥第二阶段密钥协商客户端生成一个随机对称密钥客户端用证书中的服务器公钥加密这个对称密钥服务器用自己的私钥解密获得对称密钥第三阶段数据传输客户端和服务器使用这个对称密钥进行加密通信所有后续数据都经过对称加密传输五、总结5.1 Cookie与SessionCookie是存储在客户端的小型文本数据用于在浏览器和服务器之间传递状态信息。它解决了“客户端如何向服务器证明身份”的问题但数据存储在客户端存在安全风险。Session是存储在服务器端的用户状态数据通过Session ID与客户端关联。它将敏感数据保留在服务器客户端只持有一把“钥匙”。两者协作Session ID通常通过Cookie传递Cookie负责“传递钥匙”Session负责“保管物品”。5.2 HTTPSHTTPS HTTP SSL/TLS在HTTP基础上增加了加密层。对称加密效率高但密钥分发困难非对称加密安全性高但速度慢。HTTPS采用混合加密非对称加密用于证书验证和密钥协商对称加密用于实际数据传输。CA证书解决了“公钥来源可信”的问题数字签名确保证书未被篡改。