今天我们来为之前说的http进行一个补充与延伸。
请大家思考一下,我们之前说过,浏览器与服务器之间是依靠HTTP协议建立连接的。
而HTTP本身是==“无状态“,“无连接”== 的,也就是说服务器并不会记录上一次的请求是谁发出的,但是我们最后的结果却是跳转网页后可以记住我们用户的信息。以登录B站为例,当我们登录成功B站账号后,在主页任意的进行跳转界面,所呈现出的页面效果都是带着登录状态的,那么B站服务器是如何认识我这个登录用户的呢?
这一切的原因,就是我们进行的主角:Cookie与Session。
HTTP Cookie(也被称为Web Cookie,浏览器 Cookie或简称为Cookie),这是由服务器发送到用户浏览器并保存在用户浏览器上面的一小块数据。它会在浏览器之后向同一服务器再次发起请求时被携带在请求报头中,并被一起发送到服务器上。通常,它用于告知服务端两个请求是否来自同一个浏览器,这样就会保持用户的状态(在点击跳转网页时)、记录好用户的偏好并用于大数据等。
我们这里是一个登录的状态,如果我们把存储登录信息的cookie进行删除,那么我们的浏览器将不会再认识我们,也就是未登录的状态。
更新一下,或者随便进行页面点击跳转操作:
将直接退出账号登录。
所以有些视频,限制了会员观看,就是通过cookie记录用户的信息,从而进行权限的判断的。
而我们的Cookie通常分为以下几类:
由于 Cookie 是存储在客户端的,因此存在被篡改或窃取的风险。
Set-Cookie: ·name>=<value>
//其中,<name>是该Cookie的名称,<value>是Cookie的值。
Set-Cookie: username=peter; expires=Thu, 18 Dec 2024 12:00:00 UTC; path=/; domain=.example.com; secure; HttpOnly
我们的示例响应头指示浏览器创建一个具有以下属性的 Cookie:
username=peter
username
的 Cookie,其值为 peter
。
expires=Thu, 18 Dec 2024 12:00:00 UTC
expires
或 max-age
,这个 Cookie 就是“会话Cookie”,只在当前浏览器窗口关闭前有效。
path=/
www.example.com
根路径 /
及其子路径(如 /about
, /users/profile
)下的请求。如果设置为 /api
,那么只有访问 www.example.com/api/...
时才会携带这个 Cookie,访问 www.example.com/blog/...
则不会。
domain=.example.com
.
)是可选的,但具有特殊含义。它表示这个 Cookie 可以被发送给 example.com
及其任何子域名(如 www.example.com
, api.example.com
, store.example.com
)。如果只设置 domain=example.com
(没有点),效果通常是一样的。但设置 .example.com
是明确指示浏览器将其应用于所有子域。
secure
HttpOnly
document.cookie
API)访问这个 Cookie。这是一个至关重要的安全特性,可以有效地缓解跨站脚本(XSS)攻击,防止攻击者窃取用户的身份认证 Cookie。
这里值得一说的是时间:expires=Thu, 18 Dec 2024 12:00:00 UTC
我们cookie的时间格式必须遵守 RFC 1123 标准,最后面的UTC是协调时间时,我们也可以写成GMT。GMT(格林威治标准时间)和 UTC(协调世界时)是两个不同的时间标准,但它们在大多数情况下非常接近,常常被混淆。
一般来说,我们只需要使用UTC就行了,因为这个更加精确。
我们用我们之前写的代码测试一下我们的cookie是什么。
我们只需要将我们的login函数改成:
void login(HttpRequest&req,HttpResponse&resp)
{
LOG(LogLevel::DEBUG)<<"进入登录功能逻辑";
//1、解析参数格式
//2、fangwenshujuku1
//3、登陆成功
resp.SetHeader("Set-Cookie","username=zhangsan;");
//设置我们的Cookie信息,他会在第一次设置后放到客户端本地文件中(浏览器)
}
随后我们点击我们的登录页面,随便进行一个登录操作:
这样子我们的网页中就会设定一个Cookie了。我们可以看见这个是保存在我们浏览器本地的。
我们需要注意的是,如果我们要设置多个字段,那么以分号加空格跟在后面就行了。另外,我们的字段其实可以分为两类,一类是键值对,比如用户名字,会话令牌等,另外一类就是属性。
如果一个Set-Cookie只有属性,那么浏览器就会因为格式不对而将其丢弃,而只有键值对则不会进行丢弃。所以,Set-Cookie中属性只能跟在键值对后面,起到一个补充作用而不能单独存在。
我们之前介绍了Cookie的各种属性,接下来我们就详细的测试一下各个属性的使用吧。
首先就是Cookie的生命周期,expires属性,它规定了一个Cookie的过期时间。
如果我们手动expires属性,那么它就会在指定时间后过期。如果我们没有指定Cookie,就会默认为会话Cookie,在当前浏览器关闭时自动过期。
另外一个是path,是,它定义了Cookie的作用范围,如果设置为根目录就意味着在我们该域名下的所有路径都可以使用这个Cookie。
我们这里来设置一下:
void login(HttpRequest &req, HttpResponse &resp)
{
LOG(LogLevel::DEBUG) << "进入登录功能逻辑";
// 1、解析参数格式
// 2、访问数据库查找用户信息
// 3、登陆成功
resp.SetHeader("Set-Cookie", "username=zhangsan; path=/;expires=Thu,21 Dec 2025 20:00:00 GMT");
LOG(LogLevel::DEBUG) << "报头设置成功";
}
这样就代表我们进行到这一步时就已经把Cookie信息设置进行了响应报头,当浏览器接手后会在本地生成一个Cookie文件,就保存着我们刚刚设置的信息!
在了解了 Cookie 的基本作用后,我们发现,如果直接将用户的敏感信息(如用户名、ID 等)存储在客户端的 Cookie 中,会存在巨大的安全隐患。一旦 Cookie 被窃取,攻击者就能直接获得用户的隐私数据。
为了解决这个问题,我们引入了 Session(会话) 机制。其核心思想是:将用户的隐私数据保存在服务器端,而只通过 Cookie 将一个唯一的、无意义的“钥匙”——Session ID
——发送给客户端。
HTTP Session 是服务器用来在无状态的 HTTP 协议之上跟踪用户状态的一种机制。你可以把它想象成服务器为每个用户开辟的一个“私人保险箱”。
Session ID
)。
其工作流程可以概括为以下几步:
Session ID
。这个过程通常是由服务器上的一个内置模块(如 HttpSession
)完成的。
Set-Cookie
的响应头,将这个 Session ID
发送给客户端浏览器。这个 Cookie 通常被命名为 JSESSIONID
(Java)、PHPSESSID
(PHP) 等。
HTTP/1.1 200 OK
Set-Cookie: JSESSIONID=abcDeFghIjKlMnOp123456; Path=/; HttpOnly
Cookie
请求头将这个 Session ID
带回给服务器。GET /user/profile HTTP/1.1
Cookie: JSESSIONID=abcDeFghIjKlMnOp123456
Session ID
,然后在其存储空间(内存、数据库或分布式缓存中)查找与之匹配的 Session 数据。找到后,业务代码就能使用该 Session 中的数据来处理请求,从而识别用户身份和状态。与在 Cookie 中直接存储敏感信息相比,Session 机制显著提高了安全性:
Session ID
,而非用户的明文密码或个人信息。只要服务器端的 Session 数据保管妥当,用户的核心隐私就是安全的。
Session ID
)的权限。这是纯客户端 Cookie 无法轻易做到的。
Session ID
突然在另一个地区被使用,可以强制使其失效。
尽管 Session 更安全,但 Session ID
本身仍然是安全链条上薄弱的一环。必须采取措施保护它:
Session ID
在传输过程中被加密,防止被中间人窃听。
HttpOnly
:防止 JavaScript 通过 document.cookie
API 读取 Session ID
,这能有效抵御大多数 XSS(跨站脚本)攻击。
Secure
:强制要求 Cookie 只能通过 HTTPS 协议发送。
SameSite=Strict/Lax
:限制第三方网站发起请求时携带 Cookie,能有效抵御 CSRF(跨站请求伪造)攻击。
Session ID
有效窗口期。
总结:Session 机制通过“数据存服务器,钥匙发客户端”的方式,巧妙地平衡了 HTTP 无状态特性和状态管理的需求,并在安全性上提供了比纯客户端 Cookie 存储更优的解决方案。