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

Vivado 2019.2安装破解教程:零基础手把手指南

从零搭建Vivado 2019.2开发环境:不只是“破解”,更是理解FPGA工具链的开始 你是否曾在尝试启动 Vivado 的时候,被一个弹窗拦住去路:“License required for synthesis”? 或者刚下载完庞大的安装包,面对一堆补丁文件却不知从何下手? 这并不是你技术能力的问题。事实上, 每一个 FPGA 开发者都曾经历过这个阶段 ——在官方授权门槛和学习成本之间挣扎。而 Vivado 2019.2,作为 Xilinx 工具链中最后一个稳定、功能完整且社区支持广泛的经典版本,至今仍是高校实验、个人项目甚至部分企业原型验证的首选。 但它的安装与授权配置过程,远比点几下“下一步”复杂得多。本文不打算教你如何“绕过法律”,而是带你 真正搞懂整个系统是怎么运作的 :为什么需要许可证?补丁到底改了什么?Xilinx License Manager 背后又是怎样的机制? 更重要的是,我会手把手带你走完一条清晰、可复现、稳定性高的部署路径,

Clawdbot整合Qwen3:32B的低代码工作流:拖拽式Agent编排与条件分支

Clawdbot整合Qwen3:32B的低代码工作流:拖拽式Agent编排与条件分支 1. 为什么需要这个工作流:从“写代码”到“搭积木” 你有没有遇到过这样的情况:想让大模型帮自己自动处理一批客户咨询,但每次都要改Python脚本、调API参数、写if-else逻辑,改完还要测试、部署、查日志?或者想让AI根据用户提问类型自动走不同流程——比如问价格走报价分支,问售后走工单分支,问教程走知识库分支——可一想到要写状态机、维护路由表、处理异常跳转,就直接放弃了? Clawdbot + Qwen3:32B 的这套低代码工作流,就是为解决这类问题而生的。它不让你写一行后端逻辑,也不要求你懂FastAPI或LangChain内部机制。你只需要在界面上拖拽几个模块,连几条线,设几个判断条件,就能把一个320亿参数的大模型变成真正能干活的智能体(Agent)。 这不是概念演示,而是已经跑在生产环境里的真实配置:Qwen3:32B 模型私有部署在本地服务器,通过 Ollama 统一提供 API;Clawdbot 作为前端编排层,不碰模型推理,只负责“

基于指数预定义时间控制的受未知干扰和输入饱和的固定翼无人机的时空轨迹跟踪控制研究(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭:行百里者,半于九十。 📋📋📋本文内容如下:🎁🎁🎁  ⛳️赠与读者 👨‍💻做科研,涉及到一个深在的思想系统,需要科研者逻辑缜密,踏实认真,但是不能只是努力,很多时候借力比努力更重要,然后还要有仰望星空的创新点和启发点。建议读者按目录次序逐一浏览,免得骤然跌入幽暗的迷宫找不到来时的路,它不足为你揭示全部问题的答案,但若能解答你胸中升起的一朵朵疑云,也未尝不会酿成晚霞斑斓的别一番景致,万一它给你带来了一场精神世界的苦雨,那就借机洗刷一下原来存放在那儿的“躺平”上的尘埃吧。      或许,雨过云收,神驰的天地更清朗.......🔎🔎🔎 💥第一部分——内容介绍 基于指数预定义时间控制的受未知干扰和输入饱和的固定翼无人机时空轨迹跟踪控制研究 摘要 针对固定翼无人机在复杂动态环境中面临的未知干扰和执行机构输入饱和问题,本文提出一种基于指数预定义时间控制(EPTC)的时空轨迹跟踪控

FPGA数字电路基础:ego1开发板大作业vivado入门必看

FPGA开发实战入门:从零玩转ego1开发板与Vivado设计流程 你是不是正为数字逻辑课的大作业发愁? 面对一堆LED、按键和数码管,却不知道如何下手写Verilog代码? 明明仿真波形都对了,下载到板子上却毫无反应? 别急。这正是每个FPGA初学者都会经历的“第一次”——从软件思维转向硬件思维的阵痛期。 本文不讲空泛理论,也不堆砌术语,而是带你 以一个真实大作业项目为主线 ,手把手走通从Vivado建工程、写代码、加约束,到烧录验证的完整流程。我们用的是高校实验室常见的 ego1开发板 + Xilinx Vivado 工具链 ,目标是让你在两天内搞定课程大作业,并真正理解每一步背后的“为什么”。 为什么选 ego1 开发板做教学实践? 市面上的教学FPGA板不少,但 ego1(Digilent出品)之所以被众多高校采用,不是因为它最强大,而是因为它的“刚刚好”: * 够用 :Artix-7 XC7A35T 芯片提供超过3万个逻辑单元,足以实现状态机、计数器、ALU甚至简易CPU; * 开放 :所有IO引脚均可自由映射,不像某些简化板只预留固定功能接口; * 便宜