[网络]HTTP协议 Cookie与Session

16 篇文章 0 订阅

一、Cookie

1.1 定义

HTTP Cookie(也称为 Web Cookie、浏览器 Cookie 或简称 Cookie)是服务器发送到 用户浏览器并保存在浏览器上的一小块数据,它会在浏览器之后向同一服务器再次发 起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态、记录用户偏好等。

1.2 工作原理

  • 当用户第一次访问网站时,服务器会在响应的 HTTP 头中设置 Set-Cookie 字段,用于发送 Cookie 到用户的浏览器。
  • 浏览器在接收到 Cookie 后,会将其保存在本地(通常是按照域名进行存储)。
  • 在之后的请求中,浏览器会自动在 HTTP 请求头中携带 Cookie 字段,将之前保存的 Cookie 信息发送给服务器。

Cookie一般分为两种,一个是内存级的Cookie,只要关闭浏览器Cookie就会自动销毁,另一个是文件级的Cookie,即关闭浏览器Cookie仍然有效

  • 会话 Cookie(Session Cookie):在浏览器关闭时失效。
  • 持久 Cookie(Persistent Cookie):带有明确的过期日期或持续时间, 可以跨多个浏览器会话存在。
  • 如果 cookie 是一个持久性的 cookie,那么它其实就是浏览器相关的,特 定目录下的一个文件。但直接查看这些文件可能会看到乱码或无法读取的内容, 因为 cookie 文件通常以二进制或 sqlite 格式存储。一般我们查看,直接在浏览器对应的选项中直接查看即可。

1.3 认识Cookie

  • HTTP 存在一个报头选项:Set-Cookie, 可以用来进行给浏览器设置 Cookie 值。
  • 在 HTTP 响应头中添加,客户端(如浏览器)获取并自行设置并保存 Cookie。
1.3.1基本格式
//简单示例
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

时间格式必须遵守 RFC 1123 标准,具体格式样例:

Tue, 01 Jan 2030 12:34:56 GMT 或者 UTC(推荐)

GMT和UTC的区别

1. 计算方式:GMT 基于地球的自转和公转,而 UTC 基于原子钟。

2. 准确度:由于 UTC 基于原子钟,它比基于地球自转的 GMT 更加精确。 在实际使用中,GMT 和 UTC 之间的差别通常很小,大多数情况下可以互换使用。但 在需要高精度时间计量的场合,如科学研究、网络通信等,UTC 是更为准确的选择。

关于其他可选属性的解释

  • expires=[要验证]:设置 Cookie 的过期日期/时间。如果未指定此属 性,则 Cookie 默认为会话 Cookie,即当浏览器关闭时过期。
  • path=[要验证]:限制 Cookie 发送到服务器的哪些路径。默认 为设置它的路径。
  • domain=[了解即可]:指定哪些主机可以接受该 Cookie。默 认为设置它的主机。
  • secure[了解即可]:仅当使用 HTTPS 协议时才发送 Cookie。这有助于防止 Cookie 在不安全的 HTTP 连接中被截获。
  • HttpOnly[了解即可]:标记 Cookie 为 HttpOnly,意味着该 Cookie 不能被客户端脚本(如 JavaScript)访问。这有助于防止跨站脚本攻击(XSS)。

注意事项

  • 每个 Cookie 属性都以分号(;)和空格( )分隔。
  • 名称和值之间使用等号(=)分隔。
  • 如果 Cookie 的名称或值包含特殊字符(如空格、分号、逗号等),则需要 进行 URL 编码。
  • 如果设置了 expires 属性,则 Cookie 将在指定的日期/时间后过期。
  • 如果没有设置 expires 属性,则 Cookie 默认为会话 Cookie,即当浏览器关闭时过期。
1.3.2 测试浏览器自动写入Cookie

当我们第一次使用浏览器访问服务器时,浏览器会将Cookie信息自动保存起来,当我们再次访问,浏览器会将保存的Cookie信息添加到请求报头中返回给服务器,服务器接收到请求后,会解析HTTP请求头中的cookie字段,根据Cookie中的信息(如用户ID、会话ID等),服务器可以识别出用户的身份和状态,从而提供相应的服务。

class HttpServer
{
public:
    std::string GetWeekDayName(int day)
    {
        std::vector<std::string> Week = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
        return Week[day];
    }

    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 ExpireTimeUseRfc1123(int t)
    {
        time_t timeout = time(nullptr) + t; // 获取未来t秒的时间戳
        struct tm *tm = gmtime(&timeout);
        char timebuffer[1024];
        // 时间格式如: expires=Thu, 18 Dec 2024 12:00:00 UTC
        snprintf(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;
    }
    std::string HandlerHttpRequest(std::string &reqstr)
    {
        std::cout << "---------------------------------------" << std::endl;
        std::cout << reqstr;
        std::cout << "---------------------------------------" << std::endl;

        HttpRequest req;
        req.DeSerialize(reqstr);

        HttpResponse resp;
        resp.AddCode(200, _code_to_desc[200]);

        //测试Cookie自动被写入
        resp.AddHeader("Set-Cookie","username=zhangsan;");

        resp.AddBodyText("<html><h1>helloworld</h1></html>");
        std::string respstr = resp.Serialize();
        return respstr;
    }

private:
    std::unordered_map<std::string, std::string> _mine_type;
    std::unordered_map<int, std::string> _code_to_desc;
    std::unordered_map<std::string, func_t> _service_list;
};

1.3.3 测试写入Cookie过期时间
    std::string HandlerHttpRequest(std::string &reqstr)
    {
        std::cout << "---------------------------------------" << std::endl;
        std::cout << reqstr;
        std::cout << "---------------------------------------" << std::endl;

        HttpRequest req;
        req.DeSerialize(reqstr);

        HttpResponse resp;
        resp.AddCode(200, _code_to_desc[200]);

        // 测试写入Cookie过期时间
        //设置Cookie在30秒后过期
        resp.AddHeader("Set-Cookie", "username=zhangsan; expires="+ExpireTimeUseRfc1123(30)+";");  

        resp.AddBodyText("<html><h1>helloworld</h1></html>");
        std::string respstr = resp.Serialize();
        return respstr;
    }

1.3.4 测试路径path

我们将Cookie的可用路径设置为 /a/b ,则当浏览器第一次访问服务器时浏览器会自动保存Cookie,在之后的访问中,只有我们访问的路径设置为/a/b,浏览器才会将Cookie信息返回给服务器

   std::string HandlerHttpRequest(std::string &reqstr)
    {
        std::cout << "---------------------------------------" << std::endl;
        std::cout << reqstr;
        std::cout << "---------------------------------------" << std::endl;

        HttpRequest req;
        req.DeSerialize(reqstr);

        HttpResponse resp;
        resp.AddCode(200, _code_to_desc[200]);

        //测试路径path
        resp.AddHeader("Set-Cookie","username=zhangsan; path=/a/b");

        resp.AddBodyText("<html><h1>helloworld</h1></html>");
        std::string respstr = resp.Serialize();
        return respstr;
    }

单独使用Cookie的安全问题

        现在浏览器保存的是我们的测试数据,如果写入的数据是用户的私密数据,例如用户名密码的等,由于Cookie是保存在客户端的,那这些数据就有可能被别人非法获取,造成用户的私密信息泄露,那如何才能解决这个问题呢?于是就引入了Session

二、Session

2.1 定义

HTTP Session 是服务器用来跟踪用户与服务器交互期间用户状态的机制。由于 HTTP 协议是无状态的(每个请求都是独立的),因此服务器需要通过 Session 来记住用户的信息。

2.2 工作原理

1. 当用户首次访问网站时,服务器会为用户创建一个唯一的 Session ID,并通过 Cookie 将其发送到客户端。

2. 客户端在之后的请求中会携带这个 Session ID,服务器通过 Session ID 来识 别用户,从而获取用户的会话信息。

3. 服务器通常会将 Session 信息存储在内存、数据库或缓存中。

2.3 安全性

单独使用cookie不安全的原因是将用户的隐私信息直接保存在了客户端,容易被盗取,与 Cookie 相似,由于 Session ID 是在客户端和服务器之间传递的,因此也存在被窃取的风险。 但是一般虽然 Cookie 被盗取了,但是用户只泄漏了一个 Session ID,私密信息暂时没有被泄露的风险 Session ID 便于服务端进行客户端有效性的管理,比如异地登录。 可以通过 HTTPS 和设置合适的 Cookie 属性(如 HttpOnly 和 Secure)来增强安全性

2.4 用途
  • 用户认证和会话管理
  • 存储用户的临时数据(如购物车内容)
  • 实现分布式系统的会话共享(通过将会话数据存储在共享数据库或缓存中)
2.5 模拟Session行为

session.hpp

Session对象用来保存用户的基本信息,SessionManager类维护了当前所有的session对象,可以通过sessionid来索引session对象

#pragma once
#include <string>
#include <time.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <memory>
#include <unordered_map>
#include "Log.hpp"

class Session
{
public:
    Session(const std::string &username, const std::string &passward)
        : _username(username), _passward(passward)
    {
        _create_time = time(nullptr); // 获取时间戳就行了,后面实际需要,就转化就转换一下
    }
    std::string Username()
    {
        return _username;
    }

private:
    // 用户的信息
    std::string _username;
    std::string _passward;
    std::string _create_time;
    // 还可以定义其他的用户信息,例如浏览记录等等
};

using session_ptr = std::shared_ptr<Session>;
class SessionManager
{
public:
    SessionManager()
    {
        srand(time(nullptr));
    }
    ~SessionManager()
    {
    }
    std::string AddSession(session_ptr s)
    {
        uint32_t randomid = rand() + time(nullptr); // 随机数+时间戳,实际有形成sessionid的库,比如boost uuid库,或者其他第三方库等
        std::string sessionid = std::to_string(randomid);
        _sessions.insert(std::make_pair(sessionid, s));
        return sessionid;
    }

    session_ptr GetSession(std::string sessionid)
    {
        if (_sessions.find(sessionid) == _sessions.end())
            return nullptr;
        else
            return _sessions[sessionid];
    }

private:
    std::unordered_map<std::string, session_ptr> _sessions; // sessionid索引session对象
};

 如果检测到sessionid是空的话,说明这个用户是第一次访问服务器,通过登录的信息生成一个session对象,并将其插入到sessionmanager中维护起来获取一个sessionid,通过cookie的方式发送给客户端

当该用户后再次访问时,通过它的sessionid就可以识别他的身份进行业务处理,假设别人在另一台设备上登陆了该用户的账号,那就可以结合具体业务,例如跟据用户的使用偏好、是否异地登录等等,检测是否为非法登录,若是,我们在进行人脸识别登验证,不通过则直接在后台删除它的sessionid,让其失效即可

    std::string HandlerHttpRequest(std::string &reqstr)
    {
        std::cout << "---------------------------------------" << std::endl;
        std::cout << reqstr;
        std::cout << "---------------------------------------" << std::endl;

        HttpRequest req;
        req.DeSerialize(reqstr);

        HttpResponse resp;
        static int number = 0;
        if (req.Url() == "/login") // 模拟登录
        {
            std::string sessionid = req.Sessionid();
            if (sessionid.empty()) // 说明客户端第一次访问服务器,那就给客户端形成一个sessionid发送给他
            {
                std::string user = "user-" + std::to_string(number++);
                session_ptr s = std::make_shared<Session>(user, "123456");
                sessionid = _session_manager->AddSession(s);
                LOG(DEBUG, "%s 被添加, sessionid是: %s\n", user.c_str(), sessionid.c_str());
                resp.AddHeader("Set-Cookie", "sessionid=" + sessionid);
            }
        }
        else
        {
            std::string sessionid = req.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());
                }
            }
        }

        resp.AddCode(200, _code_to_desc[200]);
        resp.AddBodyText("<html><h1>helloworld</h1></html>");
        std::string respstr = resp.Serialize();
        return respstr;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张呱呱_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值