开篇介绍:
hello 大家,那么在之前的博客,我便说会对cookie、session进行详细解析,因为它们是解决HTTP无状态的关键,所以,今天,它就来了。
引言:Web 世界中的 "身份识别" 难题
当第一次访问 B 站时,浏览器会向服务器发送一个请求。服务器接收到请求后,会返回一个包含视频列表的 HTML 页面。当刷新页面时,浏览器再次发送请求,服务器依然能准确地返回相同的视频列表。
这一切看似简单,但背后隐藏着一个深刻的网络协议特性:HTTP 协议本身是 "无状态" 和 "无连接" 的。
HTTP 的无状态性意味着服务器不会记住任何关于客户端的信息。每一次请求都是一个全新的开始,服务器无法知道这是第一次访问,还是已经访问过很多次。就像一个完全不认识你的服务员,每次都需要重新介绍自己。
然而,在现代 Web 应用中,我们期望服务器能够记住我们的身份:
- B 站能记住你是已登录用户
- 购物网站能记住你的购物车内容
- 论坛网站能记住你的用户名和密码
那么,Web 开发者是如何解决这个 "记住用户" 的难题的呢?答案就是今天我们要深入探讨的两个核心技术:Cookie 和 Session。
一、HTTP Cookie:客户端的 "身份小纸条"
1.1 Cookie 的定义与诞生背景
HTTP Cookie(也称为 Web Cookie、浏览器 Cookie 或简称 Cookie)是服务器发送到用户浏览器并保存在浏览器上的一小块数据。它会在浏览器之后向同一服务器再次发起请求时被携带并发送到服务器上。
Cookie 的诞生,正是为了解决 HTTP 协议的无状态性带来的问题。想象一下,如果没有 Cookie,每次访问 B 站都需要您重新输入用户名和密码,这将是极其不便的用户体验。
Cookie 的本质是一种客户端存储机制,它让服务器能够 "认识" 同一个用户。当浏览器向服务器发送请求时,会自动附上之前保存的 Cookie 数据,服务器通过这些数据就能识别出用户身份。
1.2 Cookie 的工作原理:三步完成 "身份识别"
Cookie 的工作原理可以分为三个简单的步骤:
首次访问(建立连接) :当您第一次访问 B 站时,浏览器向服务器发送一个 HTTP 请求。服务器在返回响应时,会在 HTTP 头中添加一个特殊的字段:Set-Cookie。
这个Set-Cookie字段相当于服务器给您的一张 "身份小纸条"。例如:
HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: username=zhangsan; expires=Thu, 18 Dec 2024 12:00:00 UTC; path=/; domain=.bilibili.com
浏览器接收到这个Set-Cookie响应头后,会将 Cookie 数据保存在本地。具体存储位置和方式取决于 Cookie 的类型:
- 会话 Cookie(Session Cookie):保存在内存中,浏览器关闭时失效
- 持久 Cookie(Persistent Cookie):保存在浏览器的文件系统中,带有过期时间
本地保存 :浏览器会按照域名(例如bilibili.com)来组织和存储 Cookie。这意味着不同网站的 Cookie 是相互隔离的,不会互相干扰。
对于持久 Cookie,它们通常以二进制或 SQLite 数据库格式存储在浏览器的特定目录中。虽然可以直接查看这些文件,但内容通常是加密或编码的,直接阅读会看到乱码。
再次访问(识别用户) :当您刷新页面或再次访问 B 站时,浏览器会自动检查本地保存的 Cookie,并在 HTTP 请求头中添加Cookie字段,将之前保存的 Cookie 数据发送给服务器。
例如,当浏览器再次请求视频页面时,会发送类似以下的请求头:
GET /videos HTTP/1.1
Host: www.bilibili.com
Cookie: username=zhangsan
服务器接收到包含Cookie字段的请求后,就能识别出这是之前访问过的用户,并根据 Cookie 中的信息(如用户名)来提供个性化的服务。
1.3 Cookie 的分类:临时与持久
Cookie 主要分为两种类型,它们在有效期和存储方式上有所不同:
会话 Cookie(Session Cookie):
- 没有设置
expires属性的 Cookie 默认为会话 Cookie - 存储在浏览器内存中,不会被持久化到硬盘
- 当用户关闭浏览器窗口时,Cookie 会被立即删除
- 适用于需要在会话期间保持用户状态的场景,如临时登录
持久 Cookie(Persistent Cookie):
- 通过设置
expires或max-age属性来指定有效期 - 会被持久化存储在用户的硬盘上
- 即使关闭浏览器或重启电脑,只要 Cookie 未过期,再次访问网站时仍然有效
- 适用于需要长期保持用户状态的场景,如 "记住我" 功能
通过设置expires属性,您可以控制 Cookie 的有效期。例如:
Set-Cookie: username=zhangsan; expires=Thu, 18 Dec 2024 12:00:00 UTC
这个 Cookie 将在 2024 年 12 月 18 日过期,在此之前,即使关闭浏览器,再次访问 B 站时 Cookie 依然有效。
1.4 Cookie 的核心用途:从用户认证到行为跟踪
Cookie 的应用场景非常广泛,主要包括以下几个方面:
用户认证与会话管理(最重要):这是 Cookie 最核心的用途。通过在 Cookie 中存储用户身份信息(如用户名、用户 ID 等),服务器可以在后续请求中识别用户身份,实现免登录功能。
例如,当您在 B 站登录后,服务器会设置一个 Cookie,包含您的用户 ID 和用户名。之后每次访问 B 站,浏览器都会自动发送这个 Cookie,服务器就能知道您是已登录用户,从而显示您的个人信息和观看历史。
行为跟踪与数据分析:网站可以通过 Cookie 来跟踪用户的浏览行为,收集用户偏好信息,用于个性化推荐和广告投放。
例如,您在 B 站观看某个视频后,网站可能会设置一个 Cookie 记录您的观看历史。当您再次访问时,网站可以根据这个 Cookie 来推荐类似的视频内容。
缓存控制:网站可以使用 Cookie 来控制浏览器的缓存行为,确保用户获取最新的内容。
个性化设置:网站可以通过 Cookie 来保存用户的个性化设置,如字体大小、主题颜色、语言偏好等。
购物车管理:在电商网站上,Cookie 可以用来存储用户添加到购物车但尚未结账的商品信息,即使关闭浏览器后再次访问,购物车内容依然保留。
1.5 Cookie 的格式规范:Set-Cookie 响应头详解
Cookie 的设置通过 HTTP 响应头中的Set-Cookie字段实现。这个字段的格式有严格的规范,正确的格式是:
Set-Cookie: <name>=<value>; <attribute>=<value>; ...
其中,<name>=<value>是 Cookie 的核心,后面可以跟多个属性(attribute),每个属性之间用分号加空格(; )分隔。
基本格式示例:
Set-Cookie: username=zhangsan
这个最简单的格式设置了一个名为username,值为zhangsan的 Cookie。由于没有设置expires属性,这是一个会话 Cookie,浏览器关闭时会被删除。
完整格式示例:
Set-Cookie: username=peter; expires=Thu, 18 Dec 2024 12:00:00 UTC; path=/; domain=.example.com; secure; HttpOnly
这个完整的格式包含了多个属性,每个属性都有特定的作用:
-
名称与值(Name=Value):
username=peter是 Cookie 的核心部分,标识用户名为 "peter"- 名称和值之间用等号(
=)分隔 - 如果名称或值包含特殊字符(如空格、分号、逗号等),需要进行 URL 编码
-
expires 属性:
- 指定 Cookie 的过期日期 / 时间
- 格式必须遵循 RFC 1123 标准,例如:
Thu, 18 Dec 2024 12:00:00 UTC - 如果未指定此属性,Cookie 默认为会话 Cookie,即当浏览器关闭时过期
-
path 属性:
- 限制 Cookie 发送到服务器的哪些路径
- 默认为设置它的路径
- 例如,
path=/表示 Cookie 对服务器上的所有路径都可用;path=/a/b表示 Cookie 只对/a/b路径及其子路径可用
-
domain 属性:
- 指定哪些主机可以接受该 Cookie
- 默认为设置它的主机
- 例如,
domain=.example.com表示www.example.com、video.example.com等所有子域名都可以使用这个 Cookie
-
secure 属性:
- 指示 Cookie 只能通过 HTTPS 协议发送,不能通过 HTTP 协议发送
- 这有助于防止 Cookie 在不安全的 HTTP 连接中被截获
- 当使用 HTTPS 时,应始终设置
secure属性以提高安全性
-
HttpOnly 属性:
- 标记 Cookie 为 HttpOnly,意味着该 Cookie 不能被客户端脚本(如 JavaScript)访问
- 这有助于防止跨站脚本攻击(XSS)
- 即使攻击者能够注入 JavaScript 代码,也无法访问带有
HttpOnly属性的 Cookie
1.6 Cookie 的生命周期:何时创建,何时失效
Cookie 的生命周期决定了它的有效期,主要有以下几种情况:
会话 Cookie(临时 Cookie):
- 没有设置
expires属性的 Cookie - 存储在浏览器内存中
- 当用户关闭浏览器窗口时,Cookie 会被立即删除
- 适用于需要在会话期间保持用户状态的场景
持久 Cookie(长期 Cookie):
- 通过设置
expires或max-age属性来指定有效期 - 存储在用户的硬盘上
- 在指定的过期日期之前,无论关闭多少次浏览器,Cookie 都有效
- 适用于需要长期保持用户状态的场景
手动删除:
- 用户可以在浏览器设置中手动删除特定网站的 Cookie
- 用户也可以清除浏览器的所有 Cookie
- 这些操作都会立即使相关 Cookie 失效
服务器端删除:
-
服务器可以通过设置一个过期时间在过去的 Cookie 来强制客户端删除
-
例如:
Set-Cookie: username=; expires=Thu, 01 Jan 1970 00:00:00 GMT -
这种方法通常用于用户登出时,清除相关的认证 Cookie
1.7 Cookie 的安全性考虑:风险与防护
尽管 Cookie 在 Web 应用中扮演着重要角色,但它们也存在一些安全风险。了解这些风险并采取相应的防护措施是非常重要的。
主要安全风险:
-
Cookie 篡改:
- 攻击者可能会尝试修改客户端存储的 Cookie 值
- 例如,攻击者可能会修改
username字段的值,将 "普通用户" 改为 "管理员" - 这种攻击可能导致未授权访问
-
Cookie 窃取:
- 通过 XSS(跨站脚本攻击)攻击,攻击者可以窃取用户的 Cookie
- 一旦获取了 Cookie,攻击者就可以冒充用户进行操作
- 即使 Cookie 只包含用户名,攻击者也可以尝试使用这个用户名进行登录
-
会话固定攻击:
- 攻击者预先设置一个特定的 Cookie 值,然后诱骗用户使用这个 Cookie 访问网站
- 如果服务器没有验证用户身份,攻击者就可以使用这个 Cookie 冒充用户
-
跨站请求伪造(CSRF):
- 攻击者利用用户已登录的身份,诱导用户访问恶意网站
- 恶意网站可以发送伪造的请求,执行未经授权的操作
安全防护措施:
-
使用 Secure 属性:
- 确保 Cookie 只通过 HTTPS 协议传输
- 这可以防止 Cookie 在 HTTP 连接中被窃听和篡改
- 只有在使用 HTTPS 时才设置
secure属性
-
使用 HttpOnly 属性:
- 防止客户端脚本(如 JavaScript)访问 Cookie
- 这可以有效防止 XSS 攻击窃取 Cookie
- 即使攻击者能够注入 JavaScript 代码,也无法访问带有
HttpOnly属性的 Cookie
-
使用 SameSite 属性:
- 控制 Cookie 在跨站请求中的发送行为
- 可以设置为
Strict、Lax或None Strict:仅在同站请求中发送 CookieLax:在一些跨站导航中发送 Cookie,但限制更严格None:允许跨站发送,但需要同时设置secure属性
-
Cookie 值加密:
- 对存储在 Cookie 中的敏感信息进行加密
- 例如,将用户 ID 加密后存储在 Cookie 中
- 这样即使 Cookie 被窃取,攻击者也无法直接理解其含义
-
定期轮换 Cookie:
- 定期生成新的 Cookie 值,替换旧的 Cookie
- 可以结合会话超时机制,定期要求用户重新认证
-
验证 Cookie 来源:
- 验证 Cookie 的来源,确保它来自可信的服务器
- 可以通过检查
domain属性和host头来实现
-
避免存储敏感信息:
- 不要在 Cookie 中存储敏感信息,如密码、完整的用户信息等
- 敏感信息应存储在服务器端的 Session 中
-
设置合理的过期时间:
- 不要设置过长的 Cookie 有效期
- 较短的有效期可以减少 Cookie 被盗用后的风险窗口
1.8 GMT 与 UTC 的区别:理解 Cookie 中的时间格式
在 Cookie 的expires属性中,时间格式必须遵循 RFC 1123 标准。这就涉及到 GMT 和 UTC 两个时间标准的区别。
GMT(格林威治标准时间):
- GMT 是格林威治标准时间的缩写,它是以英国伦敦的格林威治区为基准的世界时间标准
- GMT 不受夏令时或其他因素的影响,通常用于航海、航空、科学、天文等领域
- GMT 的计算方式是基于地球的自转和公转
- GMT 的时间格式示例:
Thu, 18 Dec 2024 12:00:00 GMT
UTC(协调世界时):
- UTC 全称为 "协调世界时",是国际电信联盟 (ITU) 制定和维护的标准时间
- UTC 的计算方式是基于原子钟,而不是地球的自转,因此它比 GMT 更准确
- 据称,世界上最精确的原子钟 50 亿年才会误差 1 秒
- UTC 是现在用的时间标准,多数全球性的网络和软件系统将其作为标准时间
- UTC 的时间格式示例:
Thu, 18 Dec 2024 12:00:00 UTC
GMT 与 UTC 的区别:
- 计算方式:GMT 基于地球的自转和公转,而 UTC 基于原子钟
- 准确度:由于 UTC 基于原子钟,它比基于地球自转的 GMT 更加精确
- 实际使用:在大多数情况下,GMT 和 UTC 的差别非常小,几乎可以忽略不计
- 推荐使用 :在设置 Cookie 的
expires属性时,推荐使用 UTC 格式,因为它更准确,并且是国际标准
时间格式解析 :让我们来解析一个典型的 UTC 时间格式:Thu, 18 Dec 2024 12:00:00 UTC
Thu:星期二(星期几的缩写)18:日期(两位数表示)Dec:一月(月份的缩写)2024:年份(四位数)12:00:00:时间(小时、分钟、秒)UTC:时区(协调世界时)
在代码中生成符合 RFC 1123 标准的时间字符串时,需要特别注意:
- 使用
gmtime而不是localtime,因为localtime会根据本地时区进行转换 gmtime会直接返回 UTC 时间,确保时间格式的准确性
cookie示例代码:
cpp
#include <ctime>
#include <iostream>
#include <cctype>
#include <string>
#include <unistd.h>
#include <vector>
//cookie的诞生,就是为了解决http协议的无状态的麻烦,这个我们在之前也有讲到过,所以这里也就不过叙述
//具体的会在博客中进行讲解,主要还是因为这个比较的简单啦
/*********************************************************************
一、Cookie 是什么?------ 解决 HTTP"记不住人" 的问题
背景:B 站能记住登录状态,但 HTTP 协议本身是 "无状态、无连接" 的
(服务器每次接请求都像第一次见客户端),
Cookie 就是为解决这个问题而生的 "小纸条"。
定义:HTTP Cookie(也叫 Web Cookie / 浏览器 Cookie)是服务器发给浏览器的一小块文本数据,
浏览器会把这张 "小纸条" 存起来,之后再访问同一服务器时,会自动带上这张 "纸条",
服务器通过 "纸条" 就能认出你(比如 B 站知道你是已登录的用户)。
*********************************************************************/
/*********************************************************************
二、Cookie 怎么工作?------ 三步完成 "认人" 流程
首次见面(用户第一次访问 B 站):
服务器在返回的 HTTP 响应头里加一个 "Set-Cookie" 字段,
相当于给浏览器发一张写有你信息的 "小纸条";
本地保存:
浏览器收到 "小纸条" 后,按域名(比如bilibili.com)存在本地 ------ 会话 Cookie 存在内存里,
持久 Cookie 存在浏览器的专属文件里(文件是二进制 / SQLite 格式,直接看是乱码,要在浏览器设置里看);
再次见面(用户刷新 / 再次打开 B 站):
浏览器自动在 HTTP 请求头里带上这张 "小纸条"(Cookie 字段),服务器一看就知道 "是刚才那个用户"。
*********************************************************************/
/*********************************************************************
三、Cookie 分哪两类?------ 临时的和持久的
会话 Cookie(临时纸条):
没有设置过期时间,浏览器一关就失效(比如没点 "记住登录" 时,B 站的登录状态只在当前浏览器窗口有效);
持久 Cookie(长期纸条):
带明确的过期时间,就算关浏览器、重启电脑,只要没过期,再次打开 B 站仍能保持登录,
它会被存在浏览器的文件里,跨多个浏览器会话都有效。
*********************************************************************/
/*********************************************************************
四、Cookie 能干嘛?------ 核心用途
用户认证 / 会话管理(最核心):记住登录状态、免重复输入账号密码(B 站登录的核心逻辑);
跟踪用户行为:记录你看了哪些视频、点了哪些按钮,用于个性化推荐;
保存用户偏好:比如你设置的 B 站页面主题、弹幕字号、播放倍速等。
(查看方式:Chrome 浏览器可访问 chrome://settings/cookies 直接看保存的 Cookie)
*********************************************************************/
/*********************************************************************
五、Cookie 的格式怎么写?------ Set-Cookie 响应头规范
最基础格式:Set-Cookie: 名称 = 值
例:Set-Cookie: username = 张三 (告诉浏览器存一个叫 username、值为张三的 Cookie)
完整格式(带所有属性):
Set-Cookie: 名称 = 值;expires = 过期时间;path = 作用路径;domain = 作用域名;secure; HttpOnly
例:Set-Cookie: username=peter; expires=Thu, 18 Dec 2024 12:00:00 UTC; path=/; domain=.example.com; secure; HttpOnly
【各属性详解】
名称 = 值:Cookie 的核心标识,
比如 username=peter(名称和值用 = 分隔,含空格 / 分号等特殊字符要 URL 编码);
expires = 过期时间:Cookie 的 "有效期",格式必须按 RFC 1123 标准写,
例:Thu, 18 Dec 2024 12:00:00 UTC
(过期时间格式拆解:
Thu → 星期缩写(Sun/Mon/Tue/Wed/Thu/Fri/Sat);
18 → 日期(1-31,不用补 0 也可以);
Dec → 月份缩写(Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Nov/Dec);
2024 → 四位数年份;
12:00:00 → 时分秒(必须两位,比如 9 点写 09:00:00);
UTC/GMT → 时区(推荐 UTC,比 GMT 更精准);
补充:GMT 是基于地球自转的老标准,UTC 是基于原子钟的新标准,Cookie 里二者可互换);
path = 作用路径:限制 Cookie 只在指定路径生效,比如 path=/video 只在视频页面带这个 Cookie,
path=/ 的话全站生效;
domain = 作用域名:指定哪些域名能用这个 Cookie
,比如 domain=.bilibili.com 表示www.bilibili.com、video.bilibili.com等子域名都能用;
secure:仅在 HTTPS 加密连接时才发送 Cookie,防止明文传输被窃取;
HttpOnly:禁止 JavaScript 访问这个 Cookie,防止黑客通过 XSS 攻击偷 Cookie;
【格式注意事项】
每个属性之间必须用 "分号 + 空格"(; )分隔;
没设置 expires 的 Cookie,默认是会话 Cookie,关浏览器就失效。
*********************************************************************/
/*********************************************************************
六、Cookie 的生命周期 ------ 什么时候失效?
设了 expires:到指定时间自动失效;
没设 expires:浏览器关闭时立刻失效(会话 Cookie);
手动清除:用户在浏览器设置里删除 Cookie,也会立刻失效。
*********************************************************************/
/*********************************************************************
七、Cookie 的安全性 ------ 要注意什么?
风险:Cookie 存在客户端,可能被篡改、窃取;
防护手段:
加 secure 属性:只走 HTTPS,避免 HTTP 明文传输被截获;
加 HttpOnly 属性:防 JS 偷取,降低 XSS 攻击风险;
合理设置 expires:不要设过长有效期,降低泄露风险;
特殊字符 URL 编码:避免格式错误或被篡改。
*********************************************************************/
/*********************************************************************
八、GMT 和 UTC 的区别
GMT(格林威治标准时间):
老标准,以英国格林威治天文台的时间为基准,靠地球自转计算,慢慢会有误差;
UTC(协调世界时):
新标准,靠原子钟计算(50 亿年误差 1 秒),比 GMT 精准,现在全球网络 / 软件都用 UTC;
实际使用:Cookie 里写 GMT 或 UTC 都可以,二者差别极小,浏览器都能识别。
*********************************************************************/
//那么在本文件就是简单的实现一下cookie的时间格式,至于cookie的设置,是比较简单的,
//本质上就是在http协议的响应报头中去返回SetCookie即可
//我们在这里看一下SetCookie的格式就完事了
//格式:
//Set-Cookie: key=value; expires=星期几, 日期 月份 年份 时:分:秒 GMT; path=/;
//Set-Cookie: username=peter; expires=Thu, 18 Dec 2024 12:00:00 UTC; path=/; domain=.example.com; secure; HttpOnly
static std::string GetMonthString(int month)
{
//因为struct tm结构体中,月份范围是0到11,即0表示1月份,所以这里直接传入tm结构体中的月份就能获取到正确的对应的月份
std::vector<std::string> months={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
return months[month];
}
static std::string GetWeekdayString(int weekday)
{
//西方用星期日代表一周的最开始,也就是0
//int tm_wday; // 星期,范围从0到6(0表示星期天)
std::vector<std::string> weekdays={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
return weekdays[weekday];
}
static std::string ExpireTimeUseRfc1123(int time_second)//传入秒数,表示要获取多少秒之后的时间戳,这是用来获取过期时间
{
//获取当前时间戳
time_t nowtime=time(nullptr);
//将当前时间戳加上我们想要多少秒后过期的秒数
nowtime += time_second;
//获取时间结构体,用于获取到年月日星期分时秒
//不过这里要注意,我们这里是要获取到UTC的时间的,所以不能用localtime,
//因为这个会自动加上当前所在地的时区
//这就不正确了,因为浏览器客户端是要获取到UTC的时间,至于时区什么的,浏览器客户端会自己操作
//所以我们就要使用gmtime函数。
struct tm* stutm=gmtime(&nowtime);
// struct tm {
// int tm_sec; // 秒,范围从0到59
// int tm_min; // 分,范围从0到59
// int tm_hour; // 小时,范围从0到23
// int tm_mday; // 一个月中的日期,范围从1到31
// int tm_mon; // 月份,范围从0到11(0表示一月)
// int tm_year; // 年份,其值等于实际年份减去1900
// int tm_wday; // 星期,范围从0到6(0表示星期天)
// int tm_yday; // 一年中的第几天,范围从0到365
// int tm_isdst; // 夏令时标识符
// };
//此时stutm结构体中就存放着time_second秒之后的年月日等等数据
//然后我们它们按照要求格式放进字符串中,然后再去将字符串返回,由于就能得到了
//Set-Cookie: username=peter; expires=Thu, 18 Dec 2024 12:00:00 GMT; path=/; domain=.example.com; secure; HttpOnly
char timebuff[1024];
snprintf(timebuff,sizeof(timebuff),"%s, %d %s %d %02d:%02d:%02d %s",
GetWeekdayString(stutm->tm_wday).c_str(),stutm->tm_mday,
GetMonthString(stutm->tm_mon).c_str(),
stutm->tm_year+1900,
stutm->tm_hour,
stutm->tm_min,
stutm->tm_sec,
"GMT");
return static_cast<std::string>(timebuff);//返回字符串!!!
}
std::string ProveCookieWrite() // 证明cookie能被写入浏览器
{
return "Set-Cookie: username=zhangsan;";
}
std::string ProveCookieTimeOut()
{
return "Set-Cookie: username=zhangsan; expires=" +
ExpireTimeUseRfc1123(60) + ";"; // 让cookie 1min后过期
}
std::string ProvePath()
{
return "Set-Cookie: username=zhangsan; path=/a/b;";
}
二、HTTP Session:服务器端的 "用户档案柜"
2.1 Session 的定义与诞生背景
如果说 Cookie 是客户端的 "身份小纸条",那么 Session 就是服务器端的 "用户档案柜"。Session 的出现,是为了解决 Cookie 在安全性上的缺陷。
HTTP Session 是服务器用来跟踪用户与服务器交互期间用户状态的机制。由于 HTTP 协议是无状态的(每个请求都是独立的),因此服务器需要通过 Session 来记住用户的信息。
Cookie 的主要问题在于,它将用户的身份信息直接存储在客户端。如果攻击者能够窃取 Cookie,就可能冒充用户身份。而 Session 将用户的敏感信息存储在服务器端,客户端只需要一个唯一的标识符(Session ID),从而大大提高了安全性。
2.2 Session 的工作原理:钥匙与档案柜的协作
Session 的工作原理可以概括为 "钥匙 + 档案柜" 的模型:
首次访问(建立连接):当用户第一次访问网站时,服务器会发现用户没有携带任何身份凭证。此时,服务器会:
- 自动生成一个唯一的 Session ID(相当于一把 "档案柜钥匙"),这个 ID 通常是一串随机字符,例如 "123abc456def789",确保全球唯一且难以被猜测;
- 在服务器端创建一个对应的 "档案柜"(Session 对象),这个档案柜专门用来存储该用户的相关信息,比如登录状态、购物车内容、浏览历史等;
- 通过 Cookie 将生成的 Session ID 发送给客户端浏览器,相当于把 "钥匙" 交给用户。
此时,服务器端的 "档案柜" 还是空的,因为用户还没有进行任何需要记录状态的操作(如登录、加购商品)。
登录与状态存储(填充档案):当用户在网站上完成登录操作时,服务器会:
- 验证用户输入的账号密码是否正确;
- 如果验证通过,将用户的基本信息(如用户名、用户 ID、权限等级等)存入对应的 "档案柜"(Session 对象)中;
- 此时,服务器端的 Session 对象就有了实际内容,成为该用户的专属档案。
后续请求(身份识别与状态读取):在用户登录后的所有请求中(如浏览商品、查看订单、发表评论),浏览器都会自动在 HTTP 请求头的 Cookie 字段中携带 Session ID。服务器收到请求后,会:
- 从 Cookie 中提取 Session ID;
- 根据 Session ID 在服务器端查找对应的 "档案柜"(Session 对象);
- 从档案柜中读取用户的状态信息,确认用户身份和权限;
- 根据用户的操作更新档案柜中的信息(如添加商品到购物车、记录最新浏览时间)。
整个过程中,用户的敏感信息始终存储在服务器端,客户端只持有一个无意义的 Session ID,即使这个 ID 被窃取,攻击者也无法直接获取用户的核心信息。
Session 失效(档案柜清理):当用户完成操作后,Session 不会一直存在,会通过以下方式失效:
- 超时失效:服务器会为每个 Session 设置一个超时时间(通常为 30 分钟),如果用户在超时时间内没有任何操作,服务器会自动清理对应的 Session 对象,释放资源;
- 主动登出:用户点击 "退出登录" 按钮时,服务器会主动删除对应的 Session 对象,同时通知客户端删除存储 Session ID 的 Cookie;
- 服务器重启:如果服务器重启,内存中的 Session 对象会全部丢失,用户需要重新登录以创建新的 Session。
2.3 Session 的核心组件:Session 对象与 Session 管理器
要实现 Session 机制,服务器端需要两个核心组件:Session 对象和 Session 管理器。
Session 对象(用户档案):Session 对象是存储单个用户状态信息的容器,相当于为每个用户单独建立的 "档案袋"。一个完整的 Session 对象通常包含以下信息:
- 唯一标识:与客户端 Cookie 中的 Session ID 一一对应;
- 用户基本信息:用户名、用户 ID、登录时间、最后活跃时间等;
- 用户状态数据:登录状态(已登录 / 未登录)、购物车列表、浏览历史、临时表单数据等;
- 扩展信息:用户的权限等级、当前会话的设备信息、IP 地址等。
这些信息会根据网站的需求进行增减,核心原则是只存储与当前会话相关的临时数据,不存储长期不变的静态数据(如用户的收货地址、联系方式,这类数据应存在数据库中)。
Session 管理器(档案管理员):当网站同时有大量用户访问时,服务器端会创建大量的 Session 对象。为了高效管理这些对象,需要一个专门的 "档案管理员"------Session 管理器。
Session 管理器的核心功能包括:
- 生成唯一 Session ID:确保每个用户的 Session ID 不重复,且难以被猜测。通常采用 "随机数 + 时间戳 + 服务器标识" 的组合方式生成,例如结合 rand () 函数生成随机数、time () 函数获取时间戳,必要时还会加入服务器 IP 或进程 ID,进一步提升唯一性;
- 存储 Session 对象:将 Session ID 与对应的 Session 对象关联存储,通常使用哈希表(如 unordered_map)作为存储结构,Key 为 Session ID 字符串,Value 为 Session 对象的指针或引用,这样可以实现 O (1) 时间复杂度的查找;
- 查找与获取:根据客户端传递的 Session ID,快速找到对应的 Session 对象,供业务逻辑使用;
- 超时清理:定期扫描所有 Session 对象,检查最后活跃时间是否超过预设的超时阈值,清理过期的 Session 对象,避免内存泄漏;
- 主动删除:提供接口支持主动删除指定的 Session 对象(如用户登出时)。
Session 管理器的实现需要兼顾效率和安全性:效率上要保证高并发场景下的查找和存储性能,安全性上要防止 Session ID 被伪造或猜测。
2.4 Session 与 Cookie 的核心区别:从存储位置到安全特性
Session 和 Cookie 都是解决 HTTP 无状态问题的技术,但二者在设计理念、存储位置、安全性等方面存在本质区别,理解这些区别是正确使用它们的关键。
| 对比维度 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端(浏览器) | 服务器端(内存 / 数据库 / 缓存) |
| 存储内容 | 少量文本数据(通常不超过 4KB),如用户名、状态标识 | 完整的用户状态信息,如账号、权限、购物车等 |
| 安全性 | 较低:数据存储在客户端,易被篡改、窃取 | 较高:敏感数据存储在服务器,客户端仅存 Session ID |
| 数据容量 | 有限制(每个 Cookie 不超过 4KB,每个域名 Cookie 数量有限) | 无明确限制,取决于服务器存储能力 |
| 生命周期 | 可设置过期时间(持久 Cookie)或浏览器关闭失效(会话 Cookie) | 通常为超时失效(如 30 分钟)或服务器主动删除 |
| 网络传输 | 每次请求都会携带所有 Cookie 数据,增加带宽消耗 | 仅传输 Session ID,数据量小,带宽消耗低 |
| 跨域支持 | 受同源策略限制,默认不支持跨域访问 | 本身不涉及跨域传输,跨域问题由 Session ID 的 Cookie 决定 |
| 服务器依赖 | 不依赖服务器状态,客户端自主存储 | 依赖服务器存储状态,服务器重启后内存中的 Session 丢失 |
为了更直观地理解,我们可以用一个生活中的例子来类比:
Cookie 的工作方式:就像你去一家咖啡店,店员给你一张印有会员号的卡片,你每次来都要主动出示这张卡片,店员通过卡片上的会员号识别你。卡片(Cookie)由你自己保管,容易丢失或被他人冒用,且卡片上只能印刷少量信息。
Session 的工作方式:同样是去咖啡店,你第一次光顾时店员给你一个编号(Session ID),并在店里的账本(服务器存储)上为你建立档案。你每次来只需要报出编号,店员就会从账本中找到你的档案,记录你的消费记录、积分情况。账本(Session 数据)由店员保管,你只需要记住编号,即使编号被他人知道,没有对应的消费场景也无法冒用你的身份。
session示例代码
cpp
#include <ctime>
#include <iostream>
#include <cctype>
#include <string>
#include <unistd.h>
#include <vector>
#include <unordered_map>
#include <memory>
// ok,那么在本文件中,我们就来学习一下Session,也就是会话
// 那么它的出现其实就是为了解决cookie的安全问题
// 它的原理也很简单,原本cookie的话,是把用户的账户密码等等都保存在浏览器客户端上
// 而seesion就不一样了,它是把用户的账号密码保存在服务端上
// 而浏览器客户端只要有sessionid就行,当浏览器把sessionid发送给服务端时
// 服务端会自动根据这个sessionid去找到所相对应的用户的账号密码(也就是先前的cookie)
// 那么再想想看,服务端要管理这些用户的账号密码吧,而这些账号密码又是在session中吧
// 那么怎么管理???先描述,再组织!!!!!!!
// 先创建普通的session类,那么这里面就是要存储用户的账号密码等各种参数
// 然后再创建管理session类变量的类,因为一个用户就有一个对应的session类!!!
// 但是这个管理session类变量的类,得用key、value的类型去存储
// 因为key就是sessionid,value就是对应的session类变量!!!!!!!
// 其实这个也是比较简单的,主要是为了帮助我们进一步理解web开发
// 【HTTP Session 核心解析 】
/*********************************************************************
一、Session 是什么?------ 服务器端的 "用户档案柜"
背景:Cookie 是把 "用户信息小纸条" 存在客户端(浏览器),
而 Session 是把 "用户完整档案" 存在服务器端,
只给客户端发一个 "档案柜钥匙"(Session ID),
核心解决 HTTP 无状态导致的 "服务器记不住用户" 问题。
定义:HTTP Session(会话)是服务器端专门用来记录用户交互状态的机制,
简单说就是服务器为每个用户单独开一个 "档案柜",存用户的登录信息、购物车、临时操作记录等,
靠唯一的 "钥匙"(Session ID)对应到具体用户。
*********************************************************************/
/*********************************************************************
二、Session 怎么工作?------ 钥匙 + 档案柜的协作流程(结合 Cookie)
首次访问(用户第一次打开 B 站):
服务器发现用户没有带 "钥匙"(Session ID),
就自动创建一个唯一的 Session ID(比如一串随机字符:123abc456def),
同时在服务器端新建一个对应这个 ID 的 "档案柜"(空的,后续存用户信息);
服务器通过 Cookie 把这个 Session ID 发给浏览器(相当于把 "钥匙" 交给用户)
;
后续请求(用户登录 / 加购商品):
浏览器每次发请求都会自动带上这个 Session ID(钥匙);
服务器拿到 Session ID 后,找到对应的 "档案柜",读取 / 写入用户信息(比如登录状态、购物车商品);
数据存储:
服务器的 "档案柜"(Session 信息)不是存在客户端,
而是存在服务器的内存、数据库(如 MySQL)或缓存(如 Redis)里,
客户端只拿 "钥匙",不碰 "档案内容"。
*********************************************************************/
/*********************************************************************
三、Session 和 Cookie 的核心区别(通俗版)
Cookie:"小纸条" 存在客户端,直接存用户信息(如 username = 张三);
Session:"档案" 存在服务器,客户端只存 "钥匙"(Session ID),真正的用户信息全在服务器里。
举例子:
就像去超市存包:
Cookie 是把包直接拎在手里(信息在客户端);
Session 是把包存进储物柜,手里只拿一张取包小票(Session ID),包(用户信息)全在超市(服务器)里。
*********************************************************************/
/*********************************************************************
四、Session 的安全性 ------ 比 Cookie 更 "护隐私"
风险点:
Session ID 是通过 Cookie 在客户端和服务器之间传递的,
一旦这个 "钥匙" 被窃取,黑客就能拿着钥匙打开对应的 "档案柜";
优势:
就算 Session ID 被盗,黑客也只能拿到 "钥匙",看不到 "档案柜" 里的具体信息(比如密码、手机号),
相比直接存用户信息的 Cookie,隐私泄露风险更低;
管理优势:
服务器能轻松管控 Session ID 的有效性,比如检测到异地登录时,
直接作废旧的 Session ID(相当于换储物柜钥匙),
黑客拿到旧钥匙也没用;
增强安全的方法:
用 HTTPS 传输:防止 Session ID 在网络中被明文窃取;
给存 Session ID 的 Cookie 加 HttpOnly 属性:禁止 JS 偷取;
给 Cookie 加 Secure 属性:只在 HTTPS 下传输;
定期更换 Session ID:降低被盗用的风险。
*********************************************************************/
/*********************************************************************
五、Session 的超时和失效 ------ 什么时候 "档案柜" 会被清?
超时失效(自动清):
服务器会给 Session 设置超时时间(比如 30 分钟),如果用户 30 分钟内没有任何操作,
服务器就会自动清空这个 "档案柜",Session ID 失效(相当于超市储物柜超时自动清柜);
主动失效(手动清):
用户点击 "退出登录" 时,服务器会主动删除对应的 Session 信息,
就算手里还有 Session ID(钥匙),也打不开空柜子了;
其他失效场景:
服务器重启、内存不足时,也可能清理过期的 Session(非活跃用户的档案柜优先清)。
*********************************************************************/
/*********************************************************************
六、Session 的核心用途 ------ 服务器端能做什么?
用户认证和会话管理(核心):
登录后把用户 ID、昵称、权限等信息存在 Session 里,后续请求不用反复验证账号密码,
比如 B 站登录后,服务器通过 Session 知道你是 VIP 用户、能看专属内容;
存储临时数据:
购物车内容(没下单前存在 Session 里,换页面也不会丢)、表单临时填写的内容、游戏临时积分等;
分布式系统会话共享:
大型网站(比如淘宝)有多个服务器,把 Session 信息存在共享数据库 / 缓存(如 Redis)里,
不管用户请求打到哪个服务器,都能拿到自己的 "档案柜",实现 "多服务器认同一个用户"。
*********************************************************************/
/*********************************************************************
七、Session 关键总结(通俗版)
Session 是服务器的 "用户专属档案柜",Cookie 是客户端的 "取柜钥匙";
信息存在服务器,比 Cookie 更安全(就算钥匙丢了,档案内容不会直接泄露);
服务器能主动管控(超时、作废),适合管理登录状态、临时数据;
核心依赖 Session ID 的传递,一定要做好 HTTPS、HttpOnly 等安全防护。
*********************************************************************/
// session类(先描述)
class Session
{
public:
// 构造函数
Session(std::string username, std::string password, bool isvip)
: _username(username), _password(password), _isvip(isvip), _createtime(time(nullptr))
{
}
// 析构函数
~Session()
{
}
private:
std::string _username;
std::string _password;
time_t _createtime;
bool _isvip;
// ......
};
// session管理类(再组织)
class SessionManager
{
public:
// 构造函数
SessionManager()
{
srand(time(nullptr) ^ getpid()); // 生成随机
}
// 析构函数
~SessionManager()
{
}
// 获取session类变量以及生成唯一sessionid
// 然后将二者插入记录sessionid和对应的session类变量的哈希map的函数
// 外界传参session类变量
std::string SetSessionIdAndInsert(std::shared_ptr<Session> sessptr)
{
// 随机数+时间戳,实际有形成sessionid的库,比如boost uuid库,或者其他第三方库等
uint32_t randomid = rand() + time(nullptr);
std::string sessionid = std::to_string(randomid);
_sessions.emplace(std::make_pair(sessionid, sessptr));
return sessionid;
}
// 然后就是获取到session类变量的函数了
// 通过该返回值去获取到session里所存储的用户数据参数
// 那么就需要外界传入sessionid
std::shared_ptr<Session> GetSession(const std::string &sessionid)
{
// 要是没找到,我们就返回nullptr
if (_sessions.find(sessionid) == _sessions.end())
{
return nullptr;
}
return _sessions[sessionid];
}
private:
// 使用哈希map去记录sessionid(字符串哦)和对应的session类变量
std::unordered_map<std::string, std::shared_ptr<Session>> _sessions;
};
// 至此就轻轻松松搞定了,剩下的就是从客户端发送的cookie中获取sessionid然后获取用户参数
// 以及用户第一次来访服务端时我们创建sessionid并将用户信息传入对应的session类变量中
// 再讲sessionid返回给浏览器客户端的操作罢了
// 查找cookie
// std::string prefix = "Cookie: ";
// for (auto &line : _req_header)
// {
// std::string cookie;
// if (strncmp(line.c_str(), prefix.c_str(), prefix.size()) == 0) //找到了
// {
// cookie = line.substr(prefix.size()); // 截取"Cookie: "之后的就行了
// _cookies.emplace_back(cookie);//存储的就是Cookie: sessionid=xxxxxxxxxx
// break;
// }
// }
// // 查找sessionid
// prefix = "sessionid=";
// for (const auto &cookie : _cookies)//存储的就是Cookie: sessionid=xxxxxxxxxx
// {
// if (strncmp(cookie.c_str(), prefix.c_str(), prefix.size()) == 0)
// {
// _sessionid = cookie.substr(prefix.size()); // 截
// 取 "sessionid="之后的就行了
// // std::cout << "_sessionid: " << _sessionid << std::endl;
// }
// }
// 如果出来这个循环都没找到sessionid的话,那么就说明浏览器客户端没有传入sessionid
// 换句话说也就是浏览器客户端那里没有该用户的cookie,也就是sessionid
// 所以就需要我们服务端去给这个用户设置一个sessionid
// std::string SessionId()
// {
// return _sessionid;
// }
// 下面的代码就用来测试,如果你想更优雅,可以回调出去处理
// static int number = 0;
// if (req.Url() == "/login") // 用/login path向指定浏览器写入sessionid,并在服务器维护对应的session对象
// {
// std::string sessionid = req.SessionId();
// 如果出来这个循环都没找到sessionid的话,那么就说明浏览器客户端没有传入sessionid
// 换句话说也就是浏览器客户端那里没有该用户的cookie,也就是sessionid
// 所以就需要我们服务端去给这个用户设置一个sessionid
// if (sessionid.empty()) // 说明历史没有登陆过
// {
// std::string user = "user-" + std::to_string(number++);
// session_ptr s = std::make_shared<Session>(user, "logined");
// std::string sessionid = _session_manager->AddSession(s);
// lg.LogMessage(Debug, "%s 被添加, sessionid是: %s\n",
// user.c_str(), sessionid.c_str());
// //响应报头中返回Set-Cookie:sessionid,使得浏览器客户端有了sessionid
// //下次该用户再访问,服务端就能获取到它的sessionid然后获取到参数
// resp.AddHeader(ProveSession(sessionid));
// }
// }
// else if(req.Url() != "/login")
// {
// // 当浏览器在本站点任何路径中活跃,都会自动提交sessionid, 我们就能知道谁活跃.
// std::string sessionid = req.SessionId();
// if (!sessionid.empty())
// {
// session_ptr s = _session_manager->GetSession(sessionid);
// // 这个地方有坑,一定要判断服务器端session对象是否存在,因为可能测试
// 的时候
// // 浏览器还有历史sessionid,但是服务器重启之后,session对象没有了.
// if (s != nullptr)
// lg.LogMessage(Debug, "%s 正在活跃.\n", s - > _username.c_str());
// else
// lg.LogMessage(Debug, "cookie : %s 已经过期, 需要清理\n",
// sessionid.c_str());
// }
// }
2.5 Session 的安全性:风险与防护措施
虽然 Session 的安全性远高于 Cookie,但并非绝对安全。Session 机制面临的核心风险集中在 Session ID 的传输和验证环节,常见风险及防护措施如下:
**风险 1:Session ID 被窃取(核心风险)**Session ID 是连接客户端和服务器 Session 的唯一桥梁,如果 Session ID 被攻击者窃取,攻击者就可以冒充用户身份访问网站。窃取 Session ID 的常见方式包括:
- 网络嗅探:在未加密的 HTTP 连接中,攻击者可以通过抓包工具捕获包含 Session ID 的 Cookie 数据;
- XSS 攻击:攻击者通过注入恶意 JavaScript 代码,获取客户端 Cookie 中的 Session ID;
- 钓鱼攻击:诱导用户访问伪造的网站,骗取用户的 Session ID。
防护措施:
- 强制使用 HTTPS:将网站所有通信改为 HTTPS 加密传输,防止网络嗅探和数据篡改,同时为存储 Session ID 的 Cookie 设置
secure属性,确保仅在 HTTPS 连接中传输; - 设置 HttpOnly 属性:为存储 Session ID 的 Cookie 添加
HttpOnly属性,禁止客户端 JavaScript 访问 Cookie,从根本上防止 XSS 攻击窃取 Session ID; - 定期更换 Session ID:在用户登录成功后、权限变更时,生成新的 Session ID 替换旧 ID,即使旧 ID 被窃取,也会很快失效;
- 限制 Session ID 的传输范围:通过
path和domain属性限制 Cookie 的作用范围,仅允许在网站的核心路径中传输 Session ID。
风险 2:Session 固定攻击Session 固定攻击是指攻击者预先设置一个 Session ID,诱骗用户使用该 ID 登录网站,从而获取用户的 Session 权限。具体流程如下:
- 攻击者访问网站,获取一个合法的 Session ID;
- 攻击者通过链接、邮件等方式诱骗用户使用该 Session ID 访问网站;
- 用户使用攻击者提供的 Session ID 登录网站,服务器将用户的身份信息与该 Session ID 关联;
- 攻击者使用相同的 Session ID 访问网站,即可冒充用户身份。
防护措施:
- 登录时重置 Session ID:用户登录成功后,服务器应生成新的 Session ID,替换登录前的旧 ID,使攻击者预先获取的 ID 失效;
- 验证用户环境信息:登录时记录用户的 IP 地址、浏览器类型、操作系统等环境信息,后续请求中校验环境信息是否一致,若差异较大则要求重新登录;
- 禁止 URL 携带 Session ID:部分网站会将 Session ID 放在 URL 中(如
http://example.com?sid=123abc),这种方式易被泄露和转发,应禁止使用,仅通过 Cookie 传输 Session ID。
风险 3:Session 超时设置不合理如果 Session 超时时间设置过长(如 24 小时),一旦 Session ID 被窃取,攻击者有充足的时间进行攻击;如果设置过短(如 5 分钟),会频繁要求用户重新登录,影响用户体验。
防护措施:
- 设置合理的超时时间:根据网站的安全等级和使用场景设置超时时间,一般网站推荐 30 分钟,金融类网站可缩短至 15 分钟;
- 动态调整超时时间:用户活跃期间自动延长 Session 有效期,如用户每操作一次,就将超时时间重置为 30 分钟;
- 敏感操作二次验证:对于转账、修改密码等敏感操作,即使 Session 有效,也要求用户再次验证身份(如输入验证码、密码)。
风险 4:服务器端 Session 存储安全如果服务器端的 Session 存储不安全,攻击者可能通过入侵服务器直接获取所有用户的 Session 数据。
防护措施:
- 加密存储 Session 数据:即使攻击者获取了服务器的存储文件,加密后的 Session 数据也无法直接使用;
- 限制 Session 存储访问权限:仅允许核心业务进程访问 Session 存储目录或数据库,防止未授权访问;
- 定期备份与审计:定期备份 Session 数据,同时记录 Session 的访问日志,便于发现异常访问行为。
2.6 Session 的存储方式:从内存到分布式存储
Session 的存储方式直接影响服务器的性能、可用性和扩展性,常见的存储方式有以下几种:
**内存存储(默认方式)**这是最基础的 Session 存储方式,将 Session 对象直接存储在服务器的内存中。
- 优点:读写速度极快,操作简单,无需额外的存储组件;
- 缺点:
- 服务器重启后,所有 Session 数据丢失,用户需要重新登录;
- 不支持分布式部署,当网站部署在多台服务器时,用户的请求可能被分发到不同服务器,导致 Session 无法共享;
- 内存资源有限,当并发用户较多时,大量 Session 对象会占用过多内存,影响服务器性能。
- 适用场景:小型网站、开发环境、单机部署的应用。
文件存储将 Session 数据序列化后存储在服务器的文件系统中,每个 Session 对应一个文件。
- 优点:相比内存存储,数据更持久,服务器重启后 Session 数据不会丢失;
- 缺点:
- 文件读写速度比内存慢,高并发场景下性能较差;
- 多服务器部署时,需要共享文件系统(如 NFS),配置复杂且存在单点故障风险;
- 随着 Session 数量增加,文件管理和查找效率下降。
- 适用场景:中小型网站,对性能要求不高,需要 Session 数据持久化的场景。
**数据库存储(如 MySQL)**将 Session 数据存储在关系型数据库中,通常创建专门的 Session 表,字段包括 Session ID、用户 ID、数据内容、创建时间、过期时间等。
- 优点:
- 支持分布式部署,多台服务器可以通过访问同一数据库共享 Session;
- 数据持久化,服务器重启或故障后,Session 数据不会丢失;
- 支持复杂的查询和统计,便于监控 Session 状态。
- 缺点:
- 数据库读写速度较慢,高并发场景下会成为性能瓶颈;
- 需要维护数据库连接,增加系统复杂度;
- 大量 Session 数据的插入、查询和删除会增加数据库负担。
- 适用场景:需要分布式部署,且对 Session 数据可靠性要求较高的中大型网站。
**缓存存储(如 Redis)**将 Session 数据存储在内存缓存数据库中(如 Redis、Memcached),结合了内存存储的高速和分布式存储的优势。
- 优点:
- 读写速度接近内存存储,性能优异,支持高并发;
- 支持分布式部署,多台服务器可以通过缓存集群共享 Session;
- 支持设置过期时间,自动清理过期 Session,无需手动维护;
- 支持数据持久化(如 Redis 的 RDB/AOF 机制),兼顾性能和可靠性。
- 缺点:
- 需要额外部署和维护缓存服务,增加系统复杂度;
- 缓存服务故障可能导致 Session 不可用,需要配置集群和容错机制。
- 适用场景:大型网站、高并发场景,对性能和分布式支持要求较高的应用。
2.7 Session 的实际应用场景:从登录认证到状态管理
Session 机制在 Web 应用中有着广泛的应用,核心场景集中在用户状态管理和身份认证,具体包括:
**用户登录认证(核心场景)**这是 Session 最常用的场景。用户登录成功后,服务器将用户的身份信息存入 Session,后续所有请求都通过 Session ID 验证身份,无需重复输入账号密码。
- 典型案例:B 站登录后,浏览视频、发表评论、查看个人中心时,服务器通过 Session 确认用户身份,显示对应的权限和个性化内容。
购物车管理用户在电商网站上添加商品到购物车时,服务器将购物车数据存入 Session。即使用户未登录,也可以通过临时 Session 记录购物车内容;登录后,临时购物车数据可以与用户账号绑定,实现跨设备同步。
- 典型案例:淘宝、京东等电商平台,用户未登录时添加的商品会保存在临时购物车,登录后自动合并到个人购物车。
临时状态存储存储用户在会话期间的临时操作状态,如表单填写进度、页面浏览位置、临时偏好设置等。
- 典型案例:在线考试系统,用户中途退出后,服务器通过 Session 保存用户已答的题目和答题进度,再次登录时可以恢复到之前的状态;论坛发帖时,用户填写的内容会临时存入 Session,防止页面刷新导致内容丢失。
权限控制在 Session 中存储用户的权限等级,服务器根据 Session 中的权限信息控制用户对资源的访问。
- 典型案例:后台管理系统,管理员登录后,Session 中存储 "管理员" 权限标识,服务器允许其访问用户管理、数据统计等敏感功能;普通用户登录后,Session 中存储 "普通用户" 权限标识,仅允许访问查询功能。
防重复提交通过 Session 存储表单提交的唯一标识,防止用户重复提交表单(如重复下单、重复评论)。
- 典型案例:用户提交订单时,服务器生成一个唯一的订单标识存入 Session,用户再次提交时,服务器检查 Session 中是否已有该标识,若有则拒绝重复提交。
三、Cookie 与 Session 的协同工作:Web 应用的身份认证体系
在实际的 Web 应用中,Cookie 和 Session 很少单独使用,而是协同工作,构成完整的身份认证和状态管理体系。它们的协同模式可以概括为 "Cookie 存钥匙,Session 存档案"。
3.1 协同工作流程:从访问到登出的完整链路
我们以用户访问 B 站并完成登录的流程为例,详细说明 Cookie 与 Session 的协同工作机制:
步骤 1:首次访问(无状态连接)
- 用户打开浏览器,输入
www.bilibili.com,发送 HTTP 请求; - 服务器接收到请求,发现请求头中没有 Cookie 或 Cookie 中没有有效的 Session ID;
- 服务器生成一个唯一的 Session ID(如
abc123def456),创建对应的 Session 对象(空档案); - 服务器在 HTTP 响应头中添加
Set-Cookie: sessionid=abc123def456; HttpOnly; Secure; path=/,将 Session ID 发送给浏览器; - 浏览器接收响应,将 Session ID 存入 Cookie(会话 Cookie,内存存储);
- 服务器返回 B 站首页,用户看到未登录状态的界面。
步骤 2:用户登录(身份验证与状态存储)
- 用户点击 "登录" 按钮,输入账号密码并提交;
- 浏览器发送登录请求,自动在 Cookie 中携带 Session ID;
- 服务器提取 Cookie 中的 Session ID,找到对应的 Session 对象;
- 服务器验证账号密码的正确性,验证通过后,将用户信息(用户名、用户 ID、会员等级等)存入 Session 对象;
- 服务器返回登录成功的响应,并重定向到首页;
- 浏览器接收响应,保持 Session ID 的 Cookie 不变。
步骤 3:浏览与操作(身份识别与状态更新)
- 用户登录后浏览视频、发表弹幕、收藏内容,每一次请求都会携带 Session ID;
- 服务器通过 Session ID 找到对应的 Session 对象,确认用户身份和权限;
- 服务器根据用户的操作更新 Session 中的状态(如记录最新浏览的视频 ID、更新弹幕发布时间);
- 服务器返回与用户身份匹配的内容(如显示用户名、会员标识、个性化推荐)。
步骤 4:关闭浏览器与再次访问(会话保持)
- 用户关闭浏览器,会话 Cookie(存储 Session ID)被删除;
- 一段时间后,用户再次打开浏览器访问 B 站,此时请求中没有 Session ID;
- 服务器生成新的 Session ID,创建新的空 Session 对象,用户看到未登录状态;
- 若用户勾选了 "记住我" 选项,服务器会设置一个持久 Cookie(带
expires属性)存储 Session ID,关闭浏览器后 Cookie 不会删除,再次访问时仍能识别身份。
步骤 5:登出操作(状态清除)
- 用户点击 "退出登录" 按钮,发送登出请求;
- 服务器接收请求,提取 Session ID,找到对应的 Session 对象并删除;
- 服务器在响应头中添加
Set-Cookie: sessionid=; expires=Thu, 01 Jan 1970 00:00:00 UTC,通知浏览器删除存储 Session ID 的 Cookie; - 浏览器删除 Cookie,用户回到未登录状态。
3.2 关键配置:确保协同工作的安全性与稳定性
要让 Cookie 与 Session 协同工作顺畅,需要进行合理的配置,核心配置项包括:
Cookie 的安全配置
HttpOnly: true:禁止 JavaScript 访问 Session ID 的 Cookie,防止 XSS 攻击;Secure: true:仅在 HTTPS 连接中传输 Cookie,防止网络嗅探;SameSite: Lax:限制 Cookie 在跨站请求中的传输,防止 CSRF 攻击;Path: /:设置 Cookie 的作用路径为网站根目录,确保所有请求都能携带 Session ID;Domain: .bilibili.com:设置 Cookie 的作用域为顶级域名及所有子域名,支持跨子域名访问(如www.bilibili.com和video.bilibili.com共享 Session ID);- 合理的过期时间:会话 Cookie 无需设置
expires,持久 Cookie 根据安全需求设置(如 7 天)。
Session 的核心配置
- 超时时间:推荐设置为 30 分钟,平衡用户体验和安全性;
- Session ID 生成规则:采用 "随机数 + 时间戳 + 服务器标识" 的组合,确保唯一性和不可猜测性;
- 存储方式:中小型网站可使用内存或文件存储,大型网站推荐使用 Redis 缓存存储,支持分布式部署;
- 超时清理机制:定期扫描并删除过期 Session,避免内存泄漏。
3.3 常见问题与解决方案:协同工作中的坑与避坑指南
在 Cookie 与 Session 的协同工作中,容易遇到一些问题,以下是常见问题及解决方案:
问题 1:用户登录后仍显示未登录
- 可能原因:
- Cookie 未成功存储(浏览器禁用 Cookie、Cookie 存储满了);
- Session ID 传输错误(Cookie 路径或域名配置错误);
- Session 存储异常(如 Redis 缓存服务故障);
- 解决方案:
- 提示用户启用 Cookie,检查浏览器 Cookie 存储容量;
- 核对 Cookie 的
path和domain配置,确保与网站访问地址一致; - 检查 Session 存储服务状态,确保正常运行。
问题 2:多服务器部署时 Session 不共享
- 可能原因:Session 存储在单台服务器的内存中,用户请求被分发到不同服务器时,找不到对应的 Session;
- 解决方案:
- 使用分布式存储(如 Redis 集群)存储 Session,所有服务器共享同一 Session 数据源;
- 配置负载均衡的会话保持(Session Sticky),确保同一用户的请求始终分发到同一台服务器(不推荐,影响扩展性)。
问题 3:Session 超时时间过短,用户频繁登出
- 可能原因:超时时间设置不合理(如 5 分钟),用户操作间隔超过超时时间;
- 解决方案:
- 延长超时时间至 30 分钟,兼顾安全性和用户体验;
- 实现动态超时:用户有操作时,自动重置 Session 的过期时间;
- 对于核心功能页面(如购物车、订单页),添加活动检测,定期发送异步请求刷新 Session。
问题 4:Cookie 被浏览器拦截
- 可能原因:
- 网站使用 HTTP 协议,而 Cookie 设置了
Secure: true属性; - 浏览器开启了严格的 Cookie 拦截策略;
- Cookie 的
SameSite属性设置为Strict,跨站请求时被拦截;
- 网站使用 HTTP 协议,而 Cookie 设置了
- 解决方案:
- 网站全面启用 HTTPS,确保
Secure属性正常生效; - 提示用户调整浏览器 Cookie 设置,允许网站存储 Cookie;
- 根据业务需求调整
SameSite属性,跨站场景下设置为Lax或None(需配合Secure: true)。
- 网站全面启用 HTTPS,确保
四、Cookie 与 Session 的替代方案:现代 Web 的身份认证技术
随着 Web 技术的发展,出现了一些新的身份认证技术,在某些场景下可以替代传统的 Cookie+Session 模式,或作为补充。
4.1 Token 认证(JWT):无状态的身份凭证
Token 认证是一种无状态的身份认证技术,核心是服务器生成一个加密的 Token(令牌),客户端存储 Token(通常在 localStorage 或 Cookie 中),每次请求时携带 Token 进行身份验证。
JWT(JSON Web Token)是最常用的 Token 标准,由三部分组成:
- 头部(Header):指定加密算法(如 HS256);
- 载荷(Payload):存储用户的基本信息(如用户 ID、过期时间),明文传输但不可篡改;
- 签名(Signature):使用服务器密钥对头部和载荷进行加密,确保 Token 不被篡改。
Token 认证与 Cookie+Session 的区别:
- 无状态:服务器不需要存储 Token 对应的状态信息,Token 本身包含了所有必要的用户信息,减轻服务器存储压力;
- 跨域支持:Token 可以存储在 localStorage 中,通过 AJAX 请求的 Header 字段(如
Authorization: Bearer <token>)传输,不受同源策略限制,适合跨域应用; - 扩展性:支持分布式部署,无需共享 Session 存储,服务器只需验证 Token 的有效性即可;
- 安全性:Token 一旦生成无法修改,过期时间由 Token 本身指定,服务器无法主动失效 Token(除非更改密钥)。
适用场景:
- 前后端分离项目:前端(Vue、React)与后端通过 API 通信,无需依赖 Cookie;
- 跨域应用:多个域名的应用共享同一身份认证系统;
- 移动端应用:APP 端不支持 Cookie,可存储 Token 进行身份验证。
4.2 OAuth2.0:第三方登录认证
OAuth2.0 是一种授权框架,允许用户通过第三方平台(如微信、QQ、GitHub)的身份认证来登录当前应用,无需直接输入账号密码。
核心原理:
- 用户点击 "使用微信登录" 按钮,跳转至微信的授权页面;
- 用户同意授权后,微信返回一个授权码给当前应用;
- 当前应用使用授权码向微信服务器请求访问令牌(Access Token);
- 应用使用 Access Token 向微信服务器获取用户的基本信息(如昵称、头像);
- 应用根据用户的微信信息创建本地账号或关联已有账号,完成登录。
与 Cookie+Session 的关系:OAuth2.0 仅解决身份认证问题,登录成功后,应用仍会创建 Session 并通过 Cookie 存储 Session ID,维持用户的会话状态。
适用场景:
- 希望简化用户登录流程,提高注册转化率的应用;
- 需要接入第三方平台生态的应用(如电商网站接入微信支付,同时支持微信登录)。
4.3 本地存储(localStorage/sessionStorage):Cookie 的补充
HTML5 提供了 localStorage 和 sessionStorage 两种客户端存储方式,可用于存储少量文本数据,在某些场景下可以替代 Cookie:
- localStorage:持久化存储,数据不会随浏览器关闭而丢失,容量约 5MB;
- sessionStorage:会话级存储,仅在当前浏览器窗口有效,关闭窗口后数据丢失,容量约 5MB。
与 Cookie 的区别:
- 容量更大:Cookie 仅支持 4KB,localStorage/sessionStorage 支持 5MB;
- 无过期时间:localStorage 数据永久存储(除非手动删除),Cookie 需设置过期时间;
- 不自动传输:localStorage/sessionStorage 中的数据不会自动随 HTTP 请求发送到服务器,需手动通过 AJAX 传输;
- 安全性:与 Cookie 类似,数据存储在客户端,易被篡改,不适合存储敏感信息。
适用场景:
- 存储非敏感的客户端状态数据,如用户界面偏好、本地缓存的商品列表;
- 配合 Token 认证,存储 JWT 令牌(需注意 Token 的安全问题)。
五、总结:Cookie 与 Session 的核心价值与最佳实践
5.1 核心价值回顾
Cookie 和 Session 是 Web 开发中解决 HTTP 无状态问题的基础技术,它们的核心价值在于:
- 实现用户身份识别:让服务器能够 "记住" 用户,提供个性化服务;
- 维持会话状态:记录用户的操作历史和临时数据,提升用户体验;
- 保障安全性:通过合理的配置和协同,在便捷性和安全性之间取得平衡。
Cookie 的核心价值是 "客户端身份凭证的存储与自动传输",它解决了 "如何将用户标识传递给服务器" 的问题;Session 的核心价值是 "服务器端用户状态的安全存储",它解决了 "如何安全存储用户敏感信息" 的问题。二者相辅相成,构成了 Web 应用身份认证的基础。
5.2 最佳实践指南
Cookie 使用最佳实践
- 仅存储必要数据:Cookie 容量有限且传输会消耗带宽,仅存储身份标识(如 Session ID)等核心数据;
- 敏感数据加密:如果必须存储敏感信息,需进行加密处理,防止篡改和窃取;
- 启用安全属性:为所有 Cookie 设置
HttpOnly和Secure属性(HTTPS 环境),合理设置SameSite属性; - 避免持久 Cookie 滥用:仅在用户明确勾选 "记住我" 时使用持久 Cookie,且设置合理的过期时间;
- 处理 Cookie 禁用场景:提供 Cookie 禁用时的替代方案(如 URL 参数传递 Session ID,不推荐在生产环境使用)。
Session 使用最佳实践
- 选择合适的存储方式:小型应用用内存存储,中大型应用用 Redis 分布式存储;
- 合理设置超时时间:推荐 30 分钟,平衡用户体验和安全性;
- 确保 Session ID 的唯一性:采用安全的随机数生成算法,避免 Session ID 被猜测;
- 定期清理过期 Session:避免内存泄漏,提高服务器资源利用率;
- 登录后重置 Session ID:防止 Session 固定攻击,提升安全性。
协同工作最佳实践
- 坚持 "Cookie 存钥匙,Session 存档案" 的原则:客户端仅存储 Session ID,敏感信息全部存储在服务器端;
- 全面启用 HTTPS:加密所有通信,防止 Cookie 和 Session ID 被窃取;
- 统一域名和路径配置:确保 Cookie 的
domain和path设置正确,避免 Session ID 传输失败; - 处理分布式部署场景:使用 Redis 等分布式存储,确保多服务器间 Session 共享;
- 提供完善的登出机制:登出时同时删除服务器端 Session 和客户端 Cookie,避免身份残留。
5.3 技术选型建议
在实际开发中,应根据项目的规模、场景和安全需求选择合适的身份认证方案:
| 项目类型 | 推荐方案 | 核心优势 |
|---|---|---|
| 小型网站(如个人博客) | Cookie+Session(内存存储) | 实现简单,无需额外组件 |
| 中小型 Web 应用(如企业官网) | Cookie+Session(文件 / 数据库存储) | 兼顾稳定性和开发效率 |
| 大型分布式应用(如电商平台) | Cookie+Session(Redis 存储) | 支持高并发和分布式部署 |
| 前后端分离项目(如 Vue+SpringBoot) | Token 认证(JWT)+ 局部 Session | 无状态,支持跨域,便于扩展 |
| 移动端应用(APP) | Token 认证(JWT) | 不依赖 Cookie,适配移动端特性 |
| 第三方登录场景(如微信登录) | OAuth2.0 + Cookie+Session | 简化登录流程,提升用户体验 |
后面这一大堆,大家就简单了解,以后有用的到了,再来看看
结语:解密身份的密码本 ------ 从 Cookie 到未来认证的征途
亲爱的读者,当你合上这篇关于 Cookie 与 Session 的长文时,你刚刚完成了一场从 HTTP 协议底层出发,深入 Web 身份认证核心的探索之旅。我们从 HTTP "无状态" 这个看似简单的特性切入,一步步揭开了 Cookie 和 Session 这两个 Web 世界中最基础、也最关键的 "身份识别" 技术的神秘面纱。
这不仅仅是一篇技术文档,更是一份关于如何在数字世界中构建信任与安全的思考笔记。我们了解到,Cookie 作为 "客户端的身份小纸条",用其轻量、便捷的特性,为无数 Web 应用奠定了基础。然而,随着用户隐私保护意识的增强和安全威胁的日益复杂,我们也看到了它在安全性上的天然短板。于是,Session 作为 "服务器端的用户档案柜" 应运而生,它将敏感信息从客户端转移到了更安全的服务器端,通过一个唯一的 "钥匙"------Session ID------ 来实现身份的精准识别,极大地提升了安全性。
但技术的发展永无止境。我们在文章的最后,也提及了 JWT、OAuth2.0 等新兴技术,它们代表了无状态、分布式、更灵活的认证方向。这意味着,我们的知识版图永远需要更新,我们的安全意识永远需要保持警惕。
那么,这一切对于我们,作为开发者,又意味着什么?
首先,这是一份责任。我们是数字世界的建筑师,我们所构建的每一个登录页面、每一次数据交互,都承载着用户的信任。理解 Cookie 和 Session 的工作原理,不仅仅是为了实现一个功能,更是为了构建一个安全、可靠的数字环境。当我们为用户存储信息时,我们必须时刻问自己:这些数据是否敏感?存储位置是否安全?传输方式是否加密?这是一种职业操守,也是一种对用户的承诺。
其次,这是一次思维的拓展。我们从 "如何让服务器记住用户" 这个问题出发,探索了从客户端到服务器端,从有状态到无状态的技术演进。这个过程告诉我们,技术的发展始终围绕着 "效率" 与 "安全" 这对核心矛盾展开。我们需要在两者之间找到最佳平衡点。对于初学者而言,这是理解 Web 应用工作机制的绝佳切入点;对于资深开发者,则是审视现有系统、寻求优化和创新的重要视角。
再者,这是一扇通往更深层次知识的大门。我们今天所讨论的,只是冰山一角。在真实的大型分布式系统中,Session 的共享问题(如使用 Redis 集群)、Cookie 的精细化管理(如 GDPR 合规)、以及各种高级安全防护措施(如 CSRF、XSS 的深度防御),都是需要深入研究和实践的领域。我们的知识体系,就像一个不断生长的森林,而 Cookie 和 Session,正是这片森林的基石。
最后,这是一种持续学习的动力。技术更新换代的速度之快,常常让我们感到应接不暇。但正是这种挑战,让我们保持着对新技术的好奇心和学习的热情。当我们理解了底层原理,面对任何新的认证方案,我们都能快速抓住其核心,判断其优劣,从而做出明智的技术选型。
亲爱的读者,你手中的这篇文章,不仅仅是关于 Cookie 和 Session 的知识点汇编,它更是一个起点,一个让你能够自信地走进更广阔的 Web 开发世界的起点。从这里出发,你可以去探索 JWT 的签发与验证,去设计 OAuth2.0 的授权流程,去思考如何构建一个真正安全的身份认证体系。
所以,请将这份知识内化于心,外化于行。在你的下一个项目中,无论是为一个简单的个人博客,还是为一个复杂的企业级应用,都请带着今天所学,做出最安全、最用户友好的选择。
愿你在数字世界的探索之路上,始终保持这份对技术的敬畏与热爱,不断解锁新的技能,构建更美好的未来。因为,我们每个人,都是这个时代的 "数字工匠"。