目录
一. 引入Cookie
1.1 定义
1.2 工作原理
1.3 分类
二. 认识Cookie
三. 测试Cookie
五. 引入Session
六. 测试Session
这篇博客,我们来看看Cookie与Session,内容干货满满。
一. 引入Cookie
1.1 定义
HTTP Cookie(也称为 Web Cookie、浏览器 Cookie 或简称 Cookie)是服务器发送到 用户浏览器并保存在浏览器上的一小块数据,它会在浏览器之后向同一服务器再次发 起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一 浏览器,如保持用户的登录状态、记录用户偏好等。
1.2 工作原理
当用户第一次访问网站时,服务器会在响应的 HTTP 头中设置 Set-Cookie字段,用于发送 Cookie 到用户的浏览器。
浏览器在接收到 Cookie 后,会将其保存在本地(通常是按照域名进行存储)。
在之后的请求中,浏览器会自动在 HTTP 请求头中携带 Cookie 字段,将之 前保存的 Cookie 信息发送给服务器。
1.3 分类
- 会话 Cookie(Session Cookie):在浏览器关闭时失效。
- 持久 Cookie(Persistent Cookie):带有明确的过期日期或持续时间, 可以跨多个浏览器会话存在。
如果 cookie 是一个持久性的 cookie,那么它其实就是浏览器相关的,特定目录下的一个文件。但直接查看这些文件可能会看到乱码或无法读取的内容, 因为 cookie 文件通常以二进制或 sqlite 格式存储。一般我们查看,直接在浏览器对应的选项中直接查看即可。类似下面这样:
此处就是包含的当前站点含有的Cookie和站点数据。
需要注意的是:由于Cookie存储在客户端(如浏览器),是存在泄露的安全问题的。
二. 认识Cookie
Cookie是报头中的一个报头选项,可以用来给客户端设置Cookie值。
基本格式
Set-Cookie: <name>=<value>
其中 <name> 是 Cookie 的名称, 是 <value> Cookie 的值。也是一个KV结构。
完整的Cookie示例
Set-Cookie: username=peter; expires=Thu, 18 Dec 2024 12:00:00
UTC; path=/; domain=.example.com; secure; HttpOnly
时间格式必须遵守 RFC 1123 标准,具体格式样例:Tue, 01 Jan 2030 12:34:56 GMT 或者 UTC(推荐)。
关于时间解释:
- Tue: 星期二(星期几的缩写)
- , : 逗号分隔符
- 01: 日期(两位数表示)
- Jan: 一月(月份的缩写)
- 2030: 年份(四位数)
- 12:34:56: 时间(小时、分钟、秒)
- GMT: 格林威治标准时间(时区缩写)
GMT vs UTC(了解即可):
GMT(格林威治标准时间)和 UTC(协调世界时)是两个不同的时间标准,但它们在大多数情况下非常接近,常常被混淆。以下是两者的简单解释和区别:
1. GMT(格林威治标准时间):
- GMT 是格林威治标准时间的缩写,它是以英国伦敦的格林威治区为基准的世界时间标准。
- GMT 不受夏令时或其他因素的影响,通常用于航海、航空、科学、天文等领域。
- GMT 的计算方式是基于地球的自转和公转。
2. UTC(协调世界时):
- UTC 全称为“协调世界时”,是国际电信联盟(ITU)制定和维护的标准时 间。
- UTC 的计算方式是基于原子钟,而不是地球的自转,因此它比 GMT 更准确。据称,世界上最精确的原子钟 50 亿年才会误差 1 秒。
- UTC 是现在用的时间标准,多数全球性的网络和软件系统将其作为标准时间。
GMT 和 UTC 的英文全称以及相关信息如下:
- 1. GMT(格林尼治标准时间)
英文全称:Greenwich Mean Time
GMT 是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义为通过那里的经线。理论上来说,格林尼治标准时间的正午是 指当太阳横穿格林尼治子午线时的时间。
但值得注意的是,地球的自转是有些不规则的,且正在缓慢减速。因此, 格林尼治时间已经不再被作为标准时间使用。
- 2. UTC(协调世界时)
英文全称:Coordinated Universal Time
UTC 是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量 接近于格林尼治标准时间。
UTC 被广泛使用在计算机网络、航空航天等领域,因为它提供了非常准确和可靠的时间参考。 总结来说,GMT 和 UTC 都曾是或现在是国际上重要的时间标准,但由于地球自转 的不规则性和原子钟的精确性,UTC 已经成为了全球性的标准时间,而 GMT 则更 多被用作历史和地理上的参考。
区别:
• 计算方式:GMT 基于地球的自转和公转,而 UTC 基于原子钟。
• 准确度:由于 UTC 基于原子钟,它比基于地球自转的 GMT 更加精确。
关于其他可选属性的介绍:
- expires:设置 Cookie 的过期日期/时间。如果未指定此属性,则 Cookie 默认为会话 Cookie,即当浏览器关闭时过期。
- path:限制 Cookie 发送到服务器的哪些路径。默认为设置它的路径。
- domain:指定哪些主机可以接受该 Cookie。默认为设置它的主机。了解即可
- secure:仅当使用 HTTPS 协议时才发送 Cookie。这有助于防止Cookie 在不安全的 HTTP 连接中被截获。了解即可
- HttpOnly:标记 Cookie 为 HttpOnly,意味着该 Cookie 不能被客户端脚本(如 JavaScript)访问。这有助于防止跨站脚本攻击(XSS)。了解即可
注意事项:
(1)每个 Cookie 属性都以分号(;)和空格( )分隔。
(2)名称和值之间使用等号(=)分隔。
(3)如果 Cookie 的名称或值包含特殊字符(如空格、分号、逗号等),则需要 进行 URL 编码。
Cookie的生命周期:
如果设置了 expires 属性,则 Cookie 将在指定的日期/时间后过期。
如果没有设置 expires 属性,则 Cookie 默认为会话 Cookie,即当浏览器关闭时过期。
三. 测试Cookie
我们编码来实现Cookie的验证,此处我们采用http形式,所以在前面http的代码上修改,不知道的读者可查看。(点此查看)
测试Cookie写入浏览器:
string ProveCookieWrite()//证明cookie能被写入浏览器
{return "Set-Cookie:username=zhangsan;\r\n";
}
在处理业务函数内部调用测试:
string HandlerHttpRequest(string req)
{cout << "-----------------" << endl;cout << req << endl;string response = "HTTP/1.0 200 OK\r\n";response += "\r\n";response+=ProveCookieWrite();//测试cookie被写入与自动提交response += "<html><body><h1>hello world !</h1></body></html>";return response;
}
运行一下看看结果:
可以看到最后一行确实有Cookie。
测试写入过期时间:
string GetMonthName(int month)
{vector<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];
}string ExpireTimeUseRfc1123(int t)
{time_t timeout=time(nullptr)+t;struct tm *tm=gmtime(&timeout);//这里不能用localtime,因为localtime是默认带了时区的,gmtime获取的就是UTC统一时间char inbuffer[1024];snprintf(inbuffer,sizeof(inbuffer),"%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 inbuffer;
}
我们手动定义过期时间。
string ProvePath()
{return "Set-Cookie:username=zhangsan; path=/a/b;\r\n";
}string HandlerHttpRequest(string req)
{cout << "-----------------" << endl;cout << req << endl;string response = "HTTP/1.0 200 OK\r\n";response += "\r\n";response+=ProveCookieTimeout();//测试过期时间的写入response += "<html><body><h1>hello world !</h1>/body></html>";return response;
}
运行一下看看效果:
确实是一分钟的过期时间。
测试路径 Path:
string ProvePath()
{return "Set-Cookie:username=zhangsan; path=/a/b;\r\n";
}string HandlerHttpRequest(string req)
{
#ifdef TESTcout << "-----------------" << endl;cout << req << endl;string response = "HTTP/1.0 200 OK\r\n";response += "\r\n";response+=ProvePath();response += "<html><body><h1>hello world !</h1>/body></html>";return response;
}
测试效果:
如果提交非/a/b路径下
比如:http://8.137.19.140:8888/a/x
比如:http://8.137.19.140:8888/
比如:http://8.137.19.140:8888/x/y
结果:
提交到/a/b路径下时:
单独使用Cookie存在的问题
前面说过,Cookie是被客户端知晓的,所以如果我们单独使用Cookie,那么很有可能造成数据的泄露。
本质问题就在于这些用户私密数据在浏览器(用户端)保存,非常容易被人盗取,更重要的是,除了被盗取,还有就是用户私密数据也就泄漏了。
这就需要用到我们的Session了。往下继续看看吧。
五. 引入Session
定义
HTTP Session是服务器用来跟踪用户与服务器交互期间用户状态的机制。由于HTTP协议是无状态的(每个请求都是独立的),因此服务器需要通过Session来记住用户的信息。
工作原理
当用户首次访问网站时,服务器会为用户创建一个唯一的Session ID,并通过Cookie将其发送到客户端。
客户端在之后的请求中会携带这个Session ID,服务器通过Session ID来识别用户,从而获取用户的会话信息。服务器通常会将Session信息存储在内存、数据库或缓存中。
安全性
与Cookie相似,由于Session ID是在客户端和服务器之间传递的,因此也存在被窃取的风险。
虽然Session ID也有泄露的风险,但是用户只泄漏了一个Session ID,私密信息暂时没有被泄露的风险。Session ID便于服务端进行客户端有效性的管理,比如异地登录。可以通过HTTPS和设置合适的Cookie属性(如HttpOnly和Secure)来增强安全性。
超时与失效
Session可以设置超时时间,当超过这个时间后,Session会自动失效。服务器也可以主动使Session失效,例如当用户退出时。
用途
(1)用户认证和会话管理。
(2)存储用户的临时数据(如购物车内容)。
(3)实现分布式系统的会话共享(通过将会话数据存储在共享数据库或缓存中)。
六. 测试Session
我们封装成两个类,一个类定义Session,另一个类实现Session管理:
#pragma once#include<iostream>
#include<string>
#include<unordered_map>
#include<ctime>
#include<memory>
#include<unistd.h>using namespace std;class Session
{
public:Session(const string& username,const string& status):_username(username),_status(status){_create_time=time(nullptr);}~Session(){}
public:string _username;string _status;uint64_t _create_time;
};using session_ptr=shared_ptr<Session>;class SessionManager
{
public:SessionManager(){srand(time(nullptr));}string AddSession(session_ptr s){uint32_t randtime=rand()+time(nullptr);//随机数+时间戳,实际有形成sessionid的库,比如boost uuid库,或者其他第三方库等string sessionid=to_string(randtime);_sessions.insert(make_pair(sessionid,s));return sessionid;}session_ptr GetSession(const string sessionid){if(_sessions.find(sessionid)==_sessions.end()) return nullptr;return _sessions[sessionid];}~SessionManager(){}
private:unordered_map<string,session_ptr> _sessions;
};
上层处理业务函数:
string HandlerHttpRequest(string req)
{auto request = Factory::BuildHttpRequest();request->Deserialize(req);int contentsize=0;string text=ReadFileContent(request->Path(),&contentsize);string suffix=request->Suffix();int code=302;auto response=Factory::BuildHttpResponse();static int number=0;if(request->Url()=="/login")//用/login path向指定浏览器写入sessionid,并在服务器维护对应的session对象{string sessionid=request->Session();if(sessionid.empty())//说明历史没有登陆过{string user="user-"+to_string(number++);session_ptr s=make_shared<Session>(user,"logined");string sessionid=_session_manager->AddSession(s);LOG(DEBUG,"%s 被添加,sessionid是:%s\n",user.c_str(),sessionid.c_str());response->AddHeader("Set-Cookie: sessionid",sessionid);}}else{//当浏览器在本站点任何路径中活跃,都会自动提交sessionid,我们就能知道谁活跃了string sessionid=request->Session();if(!sessionid.empty()){session_ptr s=_session_manager->GetSession(sessionid);//这个地方有坑,一定要判断服务器端session对象是否存在,因为可能测试的时候//浏览器还有历史sessionid,但是服务器重启之后,session对象没有了if(s!=nullptr){LOG(DEBUG,"%s 正在活跃\n",s->_username.c_str());}else{LOG(DEBUG,"cookie : %s 已经到期,需要清理\n",sessionid.c_str());}}}response->AddStatusLine(code,_code_to_desc[code]);//http协议已经给我们规定好了不同文件后缀对应的Content-Typeresponse->AddHeader("Content-Type",_mime_type[suffix]);response->AddHeader("Location","https://www.qq.com/");return response->Serialize();
}
访问/login,模拟登录:
总结:
HTTP Cookie 和 Session 都是用于在 Web 应用中跟踪用户状态的机制。Cookie 是存 储在客户端的,而 Session 是存储在服务器端的。它们各有优缺点,通常在实际应用 中会结合使用,以达到最佳的用户体验和安全性。
总结:
好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。
祝大家越来越好,不用关注我(疯狂暗示)