HTTP Cookie深入解析:Web会话追踪的秘密

HTTP Cookie深入解析:Web会话追踪的秘密

🍑个人主页:Jupiter.🚀 所属专栏:Linux从入门到进阶欢迎大家点赞收藏评论😊

在这里插入图片描述
在这里插入图片描述

目录


当我们登录了B站过后,为什么下次访问B站就不需要登陆了?

  • 问题:B 站是如何认识我这个登录用户的?
  • 问题:HTTP 是无状态,无连接的,怎么能够记住我?

定义

HTTP Cookie(也称为 Web Cookie、浏览器 Cookie 或简称 Cookie)是服务器发送到用户浏览器并保存在浏览器上的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态、记录用户偏好等。

工作原理

  • 当用户第一次访问网站时,服务器会在响应的 HTTP 头中设置 Set-Cookie字段(如Set-Cookie : user = zhangsan),用于发送 Cookie 到用户的浏览器。
  • 浏览器在接收到 Cookie 后,会将其保存在本地(通常是按照域名进行存储)。
  • 在之后的请求中,浏览器会自动在 HTTP 请求头中携带 Cookie 字段,将之前保存的 Cookie 信息发送给服务器。

分类

  • 会话 Cookie(Session Cookie):在浏览器关闭时失效。
  • 持久 Cookie(Persistent Cookie):带有明确的过期日期或持续时间,可以跨多个浏览器会话存在。
  • 如果 cookie 是一个持久性的 cookie,那么它其实就是浏览器相关的,特定目录下的一个文件。但直接查看这些文件可能会看到乱码或无法读取的内容,因为 cookie 文件通常以二进制或 sqlite 格式存储。一般我们查看,直接在浏览器对应的选项中直接查看即可。
  • 类似于下面这种方式:

安全性

  • 由于 Cookie 是存储在客户端的,因此存在被篡改或窃取的风险。

用途

  • 用户认证和会话管理(最重要)
  • 跟踪用户行为
  • 缓存用户偏好等
  • 比如在 chrome 浏览器下,可以直接访问:link
  • HTTP 存在一个报头选项:Set-Cookie, 可以用来进行给浏览器设置 Cookie值。
  • HTTP 响应报头中添加,客户端(如浏览器)获取并自行设置并保存Cookie。

服务器发送Cookie

  • 当客户端(如浏览器)首次请求服务器资源时,服务器可能会在HTTP响应中包含一个或多个Set-Cookie头部。这些Set-Cookie头部指示客户端存储特定的信息(即Cookie)。
  • 每个Set-Cookie头部都包含了Cookie的名称、值以及可选的属性,如过期时间(Expires/Max-Age)、作用域(Path)、安全性要求(Secure)、跨站策略(SameSite)以及是否只能通过HTTP接口访问(HttpOnly)等。

客户端接收并保存Cookie

  • 浏览器接收到包含Set-Cookie头部的HTTP响应后,会解析这些头部,并根据其中的指令将Cookie存储到本地。
  • 存储的Cookie会包含名称、值以及所有相关的属性。
  • 浏览器会根据Cookie的过期时间和其他属性来决定何时删除这些Cookie。

客户端发送Cookie

  • 当浏览器再次向同一服务器(或符合Cookie作用域的其他服务器)发送请求时,它会自动检查是否有与该请求相关的Cookie。如果有,浏览器会将这些Cookie附加到HTTP请求的Cookie头部,并发送给服务器。服务器接收到请求后,可以从Cookie头部中读取这些Cookie,并根据需要处理它们。

基本格式

在这里插入图片描述


完整的 Set-Cookie 示例

在这里插入图片描述


时间格式必须遵守 RFC 1123 标准,具体格式样例:Tue, 01 Jan 2030 12:34:56 GMT 或者 UTC(推荐)。
关于时间解释

  • Tue: 星期二(星期几的缩写)
  • , : 逗号分隔符
  • 18: 日期(两位数表示)
  • Thu: 月份的缩写
  • 2024: 年份(四位数)
  • 12:34:56: 时间(小时、分钟、秒)
  • GMT: 格林威治标准时间(时区缩写)

GMT 和 UTC 都曾是或现在是国际上重要的时间标准,但由于地球自转的不规则性和原子钟的精确性,UTC 已经成为了全球性的标准时间,而 GMT 则更多被用作历史和地理上的参考。

关于其他可选属性的解释

  • expires=<date>:设置 Cookie 的过期日期/时间。如果未指定此属性,则 Cookie 默认为会话 Cookie,即当浏览器关闭时过期。
  • path=<some_path>:限制 Cookie 发送到服务器的哪些路径。默认为设置它的路径。
  • domain=<domain_name>:指定哪些主机可以接受该 Cookie。默认为设置它的主机。
  • secure:仅当使用 HTTPS 协议时才发送 Cookie。这有助于防止Cookie 在不安全的 HTTP 连接中被截获。
  • HttpOnly:标记 Cookie 为 HttpOnly,意味着该 Cookie 不能被客户端脚本(如 JavaScript)访问。这有助于防止跨站脚本攻击(XSS)。

以下是对 Set-Cookie 头部字段的简洁介绍

注意事项

  • 每个 Cookie 属性都以分号(;)和空格( )分隔。
  • 名称和值之间使用等号(=)分隔。
  • 如果 Cookie 的名称或值包含特殊字符(如空格、分号、逗号等),则需要进行 URL 编码。

Cookie 的生命周期

  • 如果设置了 expires 属性,则 Cookie 将在指定的日期/时间后过期。
  • 如果没有设置 expires 属性,则 Cookie 默认为会话 Cookie,即当浏览器关闭时过期。

安全性考虑

  • 使用 secure 标志可以确保 Cookie 仅在 HTTPS 连接上发送,从而提高安全性。
  • 使用 HttpOnly 标志可以防止客户端脚本(如 JavaScript)访问 Cookie,从而防止 XSS 攻击。
  • 通过合理设置 Set-Cookie 的格式和属性,可以确保 Cookie 的安全性、有效性和可访问性,从而满足 Web 应用程序的需求。

测试 cookie 的关键性完整代码全部附在最后。

  • 测试 cookie 写入到浏览器
 resp.AddHeader("Set-Cookie: username=zhangsan;");//响应中添加一行报头即可
  • 测试自动提交
  • 测试写入过期时间
    • 这里要由我们自己形成 UTC 统一标准时间:
//时间格式如: expires=Thu, 18 Dec 2024 12:00:00 UTC std::string GetMonthName(int month){ std::vector<std::string> months ={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};return months[month];} std::string GetWeekDayName(int day){ std::vector<std::string> weekdays ={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};return weekdays[day];} std::string ExpireTimeUseRfc1123(int t)// 秒级别的未来UTC时间{ time_t timeout =time(nullptr)+ t;structtm*tm =gmtime(&timeout);// 这里不能用localtime,因为localtime是默认带了时区的. gmtime获取的就是UTC统一时间char timebuffer[1024];//时间格式如: expires=Thu, 18 Dec 2024 12:00:00 UTCsnprintf(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;}
在这里插入图片描述
  • 测试路径 path

提交到非/a/b 路径下

  • 比如:http://8.137.19.140:8888/a/x
  • 比如:http://8.137.19.140:8888/
  • 比如:http://8.137.19.140:8888/x/y
在这里插入图片描述


单独使用 Cookie,有什么问题?

  • 我们写入的是测试数据,如果写入的是用户的私密数据呢?比如,用户名密码,浏览痕迹等。
  • 本质问题在于这些用户私密数据在浏览器(用户端)保存,非常容易被人盗取,更重要的是,除了被盗取,还有就是用户私密数据也就泄漏了。

Cookie测试代码

#pragmaonce#include<iostream>#include<string>#include<sstream>#include<vector>#include<memory>#include<ctime>#include"TcpServer.hpp"const std::string HttpSep ="\r\n";// 可以配置的const std::string homepage ="index.html";const std::string wwwroot ="./wwwroot";classHttpRequest{public:HttpRequest():_req_blank(HttpSep),_path(wwwroot){}boolGetLine(std::string &str, std::string *line){auto pos = str.find(HttpSep);if(pos == std::string::npos)returnfalse;*line = str.substr(0, pos);// \r\n str.erase(0, pos + HttpSep.size());returntrue;}boolDeserialize(std::string &request){ std::string line;bool ok =GetLine(request,&line);if(!ok)returnfalse; _req_line = line;while(true){bool ok =GetLine(request,&line);if(ok && line.empty()){ _req_content = request;break;}elseif(ok &&!line.empty()){ _req_header.push_back(line);}else{break;}}returntrue;}~HttpRequest(){}private:// http报文自动 std::string _req_line;// method url http_version std::vector<std::string> _req_header; std::string _req_blank; std::string _req_content;// 解析之后的内容 std::string _method; std::string _url;// /dira/dirb/x.html /dira/dirb/XX?usrname=100&&password=1234 /dira/dirb std::string _http_version; std::string _path;// "./wwwroot" std::string _suffix;// 请求资源的后缀};const std::string BlankSep =" ";const std::string LineSep ="\r\n";classHttpResponse{public:HttpResponse():_http_version("HTTP/1.0"),_status_code(200),_status_code_desc("OK"),_resp_blank(LineSep){}voidSetCode(int code){ _status_code = code;}voidSetDesc(const std::string &desc){ _status_code_desc = desc;}voidMakeStatusLine(){ _status_line = _http_version + BlankSep + std::to_string(_status_code)+ BlankSep + _status_code_desc + LineSep;}voidAddHeader(const std::string &header){ _resp_header.push_back(header+LineSep);}voidAddContent(const std::string &content){ _resp_content = content;} std::string Serialize(){MakeStatusLine(); std::string response_str = _status_line;for(auto&header : _resp_header){ response_str += header;} response_str += _resp_blank; response_str += _resp_content;return response_str;}~HttpResponse(){}private: std::string _status_line; std::vector<std::string> _resp_header; std::string _resp_blank; std::string _resp_content;// body// httpversion StatusCode StatusCodeDesc std::string _http_version;int _status_code; std::string _status_code_desc;};classHttp{private: std::string GetMonthName(int month){ std::vector<std::string> months ={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};return months[month];} std::string GetWeekDayName(int day){ std::vector<std::string> weekdays ={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};return weekdays[day];} std::string ExpireTimeUseRfc1123(int t)// 秒级别的未来UTC时间{ time_t timeout =time(nullptr)+ t;structtm*tm =gmtime(&timeout);// 这里不能用localtime,因为localtime是默认带了时区的. gmtime获取的就是UTC统一时间char timebuffer[1024];//时间格式如: expires=Thu, 18 Dec 2024 12:00:00 UTCsnprintf(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;}public:Http(uint16_t port){ _tsvr = std::make_unique<TcpServer>(port, std::bind(&Http::HandlerHttp,this, std::placeholders::_1)); _tsvr->Init();} std::string ProveCookieWrite()// 证明cookie能被写入浏览器{return"Set-Cookie: username=zhangsan;";}// resp.AddHeader(ProveCookieWrite()); //测试cookie被写入与自动提交 std::string ProveCookieTimeOut(){return"Set-Cookie: username=zhangsan; expires="+ExpireTimeUseRfc1123(60)+";";// 让cookie 1min后过期} std::string ProvePath(){return"Set-Cookie: username=zhangsan; path=/a/b;";} std::string ProveOtherCookie(){return"Set-Cookie: passwd=1234567890; path=/a/b;";} std::string HandlerHttp(std::string request){ HttpRequest req; req.Deserialize(request); req.DebugHttp(); lg.LogMessage(Debug,"%s\n",ExpireTimeUseRfc1123(60).c_str()); HttpResponse resp; resp.SetCode(200); resp.SetDesc("OK"); resp.AddHeader("Content-Type: text/html");// resp.AddHeader(ProveCookieWrite()); //测试cookie被写入与自动提交// resp.AddHeader(ProveCookieTimeOut()); //测试过期时间的写入// resp.AddHeader(ProvePath()); // 测试路径 resp.AddHeader(ProvePath()); resp.AddHeader(ProveOtherCookie()); resp.AddContent("<html><h1>helloworld</h1></html>");return resp.Serialize();}voidRun(){ _tsvr->Start();}~Http(){}private: std::unique_ptr<TcpServer> _tsvr;};

Read more

火山引擎AI大模型计费规则与GLM-4.6V-Flash-WEB成本对比

火山引擎AI大模型计费规则与GLM-4.6V-Flash-WEB成本对比 在当前多模态AI应用加速落地的背景下,图像理解、视觉问答和图文推理正逐步嵌入客服系统、内容审核、智能终端等核心业务场景。然而,一个现实问题摆在开发者面前:是选择开箱即用的商业API,还是自建轻量化模型服务?这不仅关乎技术架构的灵活性,更直接影响系统的长期运营成本与数据安全边界。 以火山引擎为代表的云厂商提供了便捷的大模型调用接口,而智谱推出的 GLM-4.6V-Flash-WEB 则代表了另一条路径——开源、可本地部署、面向Web实时交互优化的轻量级多模态模型。两者看似功能相似,但在性能表现、成本结构和适用场景上存在本质差异。本文将从工程实践角度出发,深入拆解这两种方案的技术内核与经济账本,帮助团队做出更具前瞻性的技术选型决策。 一、从“能用”到“好用”:为什么轻量化视觉模型正在崛起? 传统多模态大模型如GPT-4V或Qwen-VL-Max虽然能力强大,但其千亿参数规模决定了它们必须依赖高性能GPU集群进行推理,单次请求延迟常超过500ms,且部署成本动辄数十万元起。这种高门槛使得许多中小企业和边缘场景难

WebGIS开发实战:WKT转GeoJSON的多种技巧与Leaflet加载应用详解

WebGIS开发实战:WKT转GeoJSON的多种技巧与Leaflet加载应用详解

目录 前言 一、WKT后台转换实现 1、基于PostGIS实现 2、GeoTools实现 二、wellknown.js转换 1、wellknown.js是什么? 2、wellknown.js的方法 三、在Leaflet.js中集成wellknow.js 1、资源引入 2、将wkt转为geojson 四、总结 前言         在当今数字化浪潮中,地理信息系统(GIS)技术正以前所未有的速度融入我们的生活与工作。从城市规划到环境监测,从物流配送到旅游出行,地理空间数据的价值日益凸显。而 WebGIS,作为 GIS 技术与 Web 技术的深度融合,更是为地理信息的共享与交互开辟了广阔天地。它让地理数据能够通过网络在各种终端设备上轻松呈现,极大地拓展了 GIS 的应用场景和受众群体。然而,在 WebGIS

OpenClaw Web Search 完全指南(2026年3月最新)

OpenClaw Web Search 完全指南(2026年3月最新) 本文详细介绍 OpenClaw 内置 web_search 工具的 5 个官方搜索渠道,以及 Tavily 技能的使用方法。帮助你选择最适合的免费/付费方案。 目录 * OpenClaw 搜索功能概述 * 5 个官方搜索渠道详解 * 1. Brave Search API * 2. Google Gemini * 3. Grok (xAI) * 4. Kimi (Moonshot) * 5. Perplexity * 免费额度对比表 * 推荐配置方案 * Tavily Web Search 技能 * 配置步骤详解 * 常见问题 OpenClaw 搜索功能概述 OpenClaw 提供两种搜索能力:

前端CI/CD流程:自动化部署的正确打开方式

前端CI/CD流程:自动化部署的正确打开方式 毒舌时刻 CI/CD?听起来就像是前端工程师为了显得自己很专业而特意搞的一套复杂流程。你以为配置了CI/CD就能解决所有部署问题?别做梦了!到时候你会发现,CI/CD配置出错的概率比手动部署还高。 你以为随便找个CI/CD工具就能用?别天真了!不同的工具配置方式不同,坑也不同。比如Jenkins的配置文件就像是天书,GitLab CI的YAML语法也能让你崩溃。 为什么你需要这个 1. 自动化部署:CI/CD可以自动完成代码测试、构建和部署,减少手动操作,提高部署效率。 2. 减少人为错误:自动化部署可以避免手动部署时的人为错误,提高部署的可靠性。 3. 快速反馈:CI/CD可以在代码提交后立即进行测试和构建,及时发现问题,提供快速反馈。 4. 持续集成:CI/CD可以确保代码的持续集成,避免代码冲突和集成问题。 5. 环境一致性:CI/CD可以确保不同环境的配置一致,避免环境差异导致的问题。 反面教材