HTTP Cookie(也称为 Web Cookie、 浏览器 Cookie 或简称 Cookie) 是服务器发送到用户浏览器并保存在浏览器上的一小块数据, 它会在浏览器之后向同一服务器再次发起请求时被携带并发送到服务器上。 通常, 它用于告知服务端两个请求是否来自同一浏览器, 如保持用户的登录状态、 记录用户偏好等
HTTP协议(超文本传输协议)被设计为无状态(Stateless)和无连接(Connectionless)的:
总结来说,HTTP协议的无状态性意味着它不保存交互历史,无连接性(在HTTP/1.0中)意味着每个请求/响应后都会关闭连接。这些设计决策使得HTTP协议简单、轻量,但同时也带来了一些复杂性,尤其是在需要维护状态和性能优化方面。
访问网站时,服务端和客户端不会记录访问的这个网站。每次都是一次重新的请求,这样一般是没有什么问题的。但是当访问一些需要账号的网站(B站 , 爱奇艺),如果每次都重新请求是不是就意味着每次都要进行一次登录,但实际上每次访问时,我们会发现只需要登录一次,再次访问时就不需要输入账号和密码了,并且可以识别是否是VIP!这就是cookie的作用,可以标识用户状态,对用户的登录状态进行保持,方便随时验证用户身份!
我们在我们的服务器中可以进行cookie的设置,在http应答中加入cookie的报头
resp.AddHeader("Set-Cookie", "username=zhansan");
resp.AddHeader("Set-Cookie", "passwd=123456");
这时我们就能够在我们的网站上看到我们设置的cookie!
cookie是储存在浏览器上的,当浏览器进行登录操作发送请求时,服务器会进行cookie的处理。然后通过应答返回给浏览器,下一次浏览器再次申请时,就可以带着这个cookie进行请求,服务器就可以直接识别账号等信息了!
对于cookie的储存有两种:
每次浏览器发送的请求都带着cookie,发送给服务器,就能自动识别,不再需要重复进行登录等操作!
我们在添加报头时,可以加入set-cookie
,把用户名进行设置!
//...
resp.AddHeader("Content-Length", std::to_string(content.size()));
resp.AddHeader(suffix, _mini_type[suffix]);
resp.AddHeader("Set-Cookie", "username=zhansan");
//...
这样应答中就会返回给浏览器set-cookie
,浏览器就会报存cookie!再次发送请求时,http请求中就会带有cookie 的报头!
cookie的完整格式是很丰富的(服务器可以设置很多条cookie):
Set-Cookie: username=peter; expires=Thu, 18 Dec 2024 12:00:00UTC; path=/; domain=.example.com; secure; HttpOnly
其中包含很多信息:
当服务器发送一个Set-Cookie
响应头时,它会在客户端的浏览器中创建一个cookie。这个cookie会在随后的请求中由浏览器自动发送回服务器。下面是Set-Cookie
头中各个属性的解释:
username=peter
:这是cookie的名称和值,用于存储用户名。expires=Thu, 18 Dec 2024 12:00:00 UTC
:这个属性指定了cookie的过期时间(UTC时间),即在这个时间点之后cookie将不再被浏览器发送。在这个例子中,cookie将在2024年12月18日UTC时间12:00过期。path=/
:这个属性定义了cookie的路径。在这里,路径是根目录(/
),意味着cookie将在这个域的所有路径下发送。domain=.example.com
:这个属性指定了cookie有效的域。这里使用了一个点号(.
)作为前缀,表示cookie对于example.com
及其所有子域都有效。secure
:这个属性没有值,它指示浏览器仅在HTTPS请求中发送cookie,而在HTTP请求中不发送。HttpOnly
:这个属性没有值,它指示cookie不能通过客户端脚本(如JavaScript)访问,这有助于减少跨站脚本攻击(XSS)的风险。我们可以在服务器中加入这些信息,来深入理解cookie!首先我们实现一下加入过期时间,注意这里是过期时间是要严格按照格式进行打印!
std::string GetMonth(int day)
{
std::vector<std::string> Month = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
return Month[day];
}
std::string GetWeekday(int day)
{
std::vector<std::string> Weekday = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
return Weekday[day];
}
// 设置cookie属性
std::string ExpireTimeUseRFC1123(int t) // 设置过期时间
{
// 获取当前过期时间戳
time_t timeout = time(nullptr) + t;
// 通过gmtime函数将时间戳转换成UTC时间
struct tm *tmp = gmtime(&timeout);
// 将时间写入到时间缓冲区
char timebuffer[256];
// expires=Thu, 18 Dec 2024 12:00:00 UTC
snprintf(timebuffer, sizeof(timebuffer), "%s, %02d %s %04d %02d:%02d:%02d UTC",
GetWeekday(tmp->tm_wday).c_str(),
tmp->tm_mday,
GetMonth(tmp->tm_mon).c_str(),
tmp->tm_year + 1900,
tmp->tm_hour,
tmp->tm_min,
tmp->tm_sec);
return timebuffer;
}
这样我们在设置这样的cookie:
//...
std::string cookie = "username=zhansan";
cookie += "; expires=" + ExpireTimeUseRFC1123(60) ;//过期时间设置为一分钟后
resp.AddHeader("Set-Cookie", cookie);
//...
这样就可以得到具有过期时间的cookie!
接下来我们在来实现一下cookie的路径,这个直接加入就可以:
cookie += "; path=";//设置cookie路径
cookie += "/a/b";
这就意味着只有在/a/b
的路径下才会发送这个cookie!
cookie的属性是很丰富和健全的,但是只使用cookie是由风险的,只使用cookie并不是当前的主流形式,cookie+session是更加完善的方法!接下来我们来看cookie到底有什么风险以及如何理解session
单独使用cookie时,http应答会传送回来用户的信息,储存在用户的内存或者文件中。如果cookie被他人盗取了,那么其他就能够通过这个cookie知道用户的个人信息,这样就造成了信息泄露!为了更好保证安全性产生了cookie+session的形式
与 Cookie 相似, 由于 Session ID 是在客户端和服务器之间传递的, 因此也存在被窃取的风险。但是一般虽然 Cookie 被盗取了, 但是用户只泄漏了一个 Session ID, 私密信息暂时没有被泄露的风险。Session ID 便于服务端进行客户端有效性的管理, 比如异地登录。可以通过 HTTPS 和设置合适的 Cookie 属性(如 HttpOnly 和 Secure) 来增强安全性。
根据session的特性,我们需要设计出session结构体。这个结构体储存着真正的cookie信息:
// 用来进行测试说明
class Session
{
public:
Session(const std::string &username, const std::string &status)
: _username(username), _status(status)
{
_create_time = time(nullptr); // 获取时间戳就行了,后面实际需要,就转化就转换一下
}
~Session()
{
}
public:
std::string _username = "";
std::string _status = "";
uint64_t _create_time = 0;
uint64_t _time_out = 0; // 60*5过期时间
std::string vip = ""; // 是否为vip
int active = 0; //
std::string pos = "";
// 当然还可以再加任何其他信息,看你的需求
//...
};
session里可以带有很多信息,根据具体业务需求添加即可!!! 我们描述好了session结构,接下来就来进行组织,这里可以使用map,unordered_map等映射来进行。SessionManager需要支持添加sessionid与session的映射。
using session_ptr = std::shared_ptr<Session>;
class SessionManager
{
public:
SessionManager()
{
srand(time(nullptr) ^ getpid());
}
std::string AddSession(session_ptr s)
{
LOG(DEBUG, "AddSession\n");
uint32_t randomid = rand(); // 随机数+时间戳,实际有形成sessionid的库,比如boost uuid库,或者其他第三方库等
std::string sessionid = std::to_string(randomid);
LOG(DEBUG, "randomid:%s ptr:%s %s\n", sessionid.c_str(), s->_username.c_str(), s->_status.c_str());
_sessions.insert(std::make_pair(sessionid , s));
LOG(DEBUG, "AddSession success:%s\n", _sessions[sessionid]);
return sessionid;
}
session_ptr GetSession(const std::string sessionid)
{
if (_sessions.find(sessionid) == _sessions.end())
return nullptr;
return _sessions[sessionid];
}
~SessionManager()
{
}
private:
std::map<std::string, session_ptr> _sessions;
};
这样我们的session管理的基础就设计好了。我们可以在应答中加入我们的session:
//...
static int number = 0;
if (hreq.Url() == "/login.html") // 用/login path向指定浏览器写入sessionid,并在服务器维护对应的session对象
{
LOG(DEBUG , "进入session判断\n");
std::string sessionid = hreq.SessionId();
LOG(DEBUG , "获取到session :%s\n" , sessionid .c_str());
if (sessionid.empty()) // 说明历史没有登陆过
{
LOG(DEBUG , "说明历史没有登陆过\n");
std::string user = "user-" + std::to_string(number++);
session_ptr s = std::make_shared<Session>(user, "logined");
LOG(DEBUG , "session user:%s status:%s\n" , s->_username.c_str() ,s->_status.c_str());
std::string sessionid = _session_manager->AddSession(s);
LOG(DEBUG, "%s 被添加, sessionid是: %s\n", user.c_str(), sessionid.c_str());
resp.AddHeader("Set-Cookie", "sessionid=" + sessionid);
}
}
else
{
// 当浏览器在本站点任何路径中活跃,都会自动提交sessionid, 我们就能知道谁活跃了.
std::string sessionid = hreq.SessionId();
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());
}
}
//...
记住了一定要将httpsever底层的sessionmanager初始化!
这样我们就能成功的通过sessionid获取信息了:
我们使用多个客户端来试试:
可以发现,我们的服务器可以根据cookie中的sessionid找到对应的session!非常nice!!! OK!这样就是cookie+session的通信版本!
总结: HTTP Cookie 和 Session 都是用于在 Web 应用中跟踪用户状态的机制。 Cookie 是存储在客户端的, 而 Session 是存储在服务器端的。 它们各有优缺点, 通常在实际应用中会结合使用, 以达到最佳的用户体验和安全性!