你真的了解 Cookie 和 Session 吗?

Cookie 和 Session

cookie

HTTP cookie(web cookie、browser cookie)是服务器发送给用户 web 浏览器的一小段数据。浏览器可能会存储 cookie,并在以后的请求中将其发送回同一台服务器。通常,HTTP cookie 用于判断两个请求是否来自同一个浏览器 - 例如,保持用户登录。它为无状态 HTTP 协议记住有状态信息。

cookie 主要用于三个目的:

  • 会话管理:登录、购物车、游戏分数或其它服务器需要保存的数据。
  • 个性化:用户首选项、主体和其它设置。
  • 跟踪:记录和分析用户行为。

示例:当你登录一次哔哩哔哩软件时,使用完成之后关闭掉该网站,下次开启电脑再次访问该网站时,你的账号依然是已经登录的状态。这实际上就是使用 cookie 实现的,如下所示,点击浏览器地址栏左侧的锁标志,你就可以看到对于网站的各种 cookie 数据了。

在这里插入图片描述

用户在通过身份验证并且服务器成功设置了 cookie 之后,服务器会将该 cookie 作为 HTTP 响应的一部分发送给浏览器。浏览器收到响应后会自动解析 cookie 的值,并将其保存在浏览器的 cookie 存储中。这样,用户的身份信息就会以加密的形式是保存在浏览器的 cookie 文件中。

当用户在之后的请i去中访问同一网站时,浏览器会自动将响应的 cookie 信息加到请求中的 cookie 标头中,并发送给服务器。服务器可以通过这些 cookie 信息来识别用户并提供个性化的服务,例如保持用户的登录状态。

在这里插入图片描述

浏览器会自动管理 cookie 的生命周期和安全性。如果你删除该网站对应的 cookie 信息,那么下次再访问时,就需要重新进行账号登录了。

注意:要查看存储的 cookie(以及网页可以使用的其它存储),你可以开启开发人员哦工具中的存储检测器并从存储树中选择 cookie。

cookie 被盗取

如果你存储在浏览器中的 cookie 信息被未经授权的用户盗取,那么该用户就可以使用你的 cookie 信息以你的身份来访问你之前访问过的网站。(例如:QQ被盗,身份泄密等)这种情况都称为 cookie 被盗取。

恶意链接是一种常见的网络攻击方式,黑客可以通过欺骗用户点击恶意链接来窃取其Cookie。以下是一个示例:假设您收到一封看似来自银行的电子邮件,邮件内容称您的账户存在异常,需要您点击链接以确认身份。然而,该链接实际上是一个恶意链接,当您点击时,黑客将会收到您的 Cookie 信息。一旦黑客获取了您的 Cookie,他们可以使用它来伪装成您,访问您的账户,进行各种恶意活动,如盗取个人信息、进行非法转账等。

什么是 Session

  • Session 代表了服务器和客户端之间的一次会话过程。在 Web 应用程序中,当用户与服务器建立会话时,服务器会为该用户创建一个唯一的 Session 对象。该 Session 对象用于存储特定用户会话所需的属性和配置信息。
  • 在整个用户会话中,当用户在应用程序的不同 Web 页面之间跳转时,存储在 Session 对象中的变量和数据不会丢失,而是持续存在。这使得应用程序能够跟踪和管理用户的状态和数据,以提供个性化的服务和功能。
  • 当客户端关闭会话(例如关闭浏览器)或者 Session 超时失效时,会话结束,Session 对象中的数据也随之销毁。会话超时时间通常由应用程序的配置决定,以平衡用户体验和服务器资源的利用。
  • 通过使用 Session,应用程序可以在用户会话期间保持持久性数据,并确保用户的状态和信息不会再页面跳转或重新加载时丢失。这对于实现登录认证、购物车功能、个性化设置都非常有用。

Cookie 和 Session 之间有什么不同?

  • 存储位置:Cookie 保存在客户端(浏览器)中,而 Session 保存在服务器端。
  • 存储内容:Cookie 只能保存 ASCII 字符,而 Session 可以存储任意数据类。通常情况下,我们可以在 Session 中保存一些常用的变量信息,如用户ID 等。
  • 有效期:Cookie 可以设置长时间保持,比如经常使用的默认登录功能。Session 的有效时间相对较短,通常在客户端关闭或 Session 超时后会失效。
  • 隐私策略:由于 Cookie 存储在客户端,相对容易收到非法获取的风险。在早期,有些网站将个人信息存储在 Cookie 中,导致信息被盗取。而 Session 数据存储在服务器中,相对来说安全性更高。
  • 存储大小:单个 Cookie 的存储容量有限,一般不超过 4KB。而 Session 可以存储的数据量远远超过 Cookie 的限制,因为它存储在服务器的内存或数据库中。

为什么需要 cookie 和 session 配合使用,它们之间的关联。

为什么需要使用 Cookie,这就要从浏览器说起,我们知道浏览器是没有状态的(HTTP 协议无状态),这意味着浏览器无法记住用户信息,这时就需要使用一个机制来告诉服务器,本次操作用户是否登录以及是哪一个用户等。而仅仅使用 Cookie 来存储用户数据存在安全风险,一旦 Cookie 文件泄漏,用户的隐私数据也会泄漏。因此,为了增加安全性,那这套机制的实现就需要 Cookie 和 Session 的配合。

在这里插入图片描述

在用户首次请求服务器时,服务器会根据用户提交的相关信息生成一个对应的 Session,并将 Session 的唯一标识信息(SessionID)返回给浏览器,浏览器收到 SessionID 之后,会将其存储在 Cookie 中并记录此 SessionID 属于哪个域名。

当用户再次访问服务器时,浏览器会自动判断此域名是否存在 Cookie 信息,并在请求中自动发送 Cookie 信息给服务器。服务器会从 Cookie 中获取 SessionID,并根据 SessionID 查找对应的 Session 信息。如果没有找到 Session,说明用户没有登录或登录失效;如果找到了 Session,就标识用户已登录,服务器可以根据 Session 中的信息执行后续的操作。

无论使用哪一种方案,安全都是相对的。

安全是相对的,没有绝对的安全性。无论是使用账号密码直接发送到网络中,还是使用 SessionID 进行身份认证,都存在一定的风险。即使使用加密等安全措施,也无法完全消除所有的潜在威胁。

安全性评估通常会考虑破解成本与收益之间的关系。如果破解某个信息的成本非常高,远远大于攻击获取信息带来的收益,那么这个信息就是相对安全的。因此,在设计安全系统时,我们尽量提高攻击者获取有价值信息的成本,以增加安全性。

对于使用 Session 的方式,尽管 SessionID 可能会被盗取,但相比直接在每次请求中发送账号和密码信息,它降低了账号密码被泄漏的风险。

HTTP 响应中设置 Cookie 字段

当浏览器访问服务器时,若服务器给浏览器的 HTTP 响应中包含 Set-Cookie 字段,那么浏览器再次访问该服务器时就会携带上该 Cookie 字段。

如下所示,当浏览器访问服务器时,我们给响应的报头中添加上一个 Set-Cookie 字段,测试当浏览器第二次访问该服务器时会不会携带上该 Cookie 字段。

#define CRLF "\r\n"
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define HOME_PAGE "index.html"
#define ROOT_PATH "wwwroot"

string getPath(string http_request)
{
    size_t pos = http_request.find(CRLF);
    if (pos == string::npos)
        return "";
    string request_line = http_request.substr(0, pos);
    // GET /a/b/c http/1.1
    size_t first = request_line.find(SPACE);
    if (first == string::npos)
        return "";
    size_t second = request_line.rfind(SPACE);
    if (second == string::npos)
        return "";

    string path = request_line.substr(first + SPACE_LEN, second - (first + SPACE_LEN));
    if (path.size() == 1 && path[0] == '/')
        path += HOME_PAGE;

    return path;
}

string readFile(const string &recource)
{
    ifstream in(recource, std::ifstream::binary);

    if (!in.is_open())
        return "404";
    string content;
    string line;
    while (getline(in, line))
        content += line;
    in.close();

    return content;
}

void handlerHttpRequest(int sock)
{
    cout << "---------------------------------------------------" << endl;
    char buffer[10240];
    ssize_t s = read(sock, buffer, sizeof buffer);
    if (s > 0)
        cout << buffer;

    string path = getPath(buffer);
    std::string recource = ROOT_PATH;
    recource += path;

    string html = readFile(recource);
    size_t pos = recource.rfind(".");
    string suffix = recource.substr(pos);
  
    // 开始响应
    std::string response;
    response = "HTTP/1.0 200 OK\r\n";
    if (suffix == ".jpg")
        response += "Content-Type: image/jpeg\r\n";
    else
        response += "Content-Type: text/html\r\n";
    response += ("Content-Length: " + to_string(html.size()) + "\r\n");
    response += "Set-Cookie: This is my cookie content;\r\n";
    response += "\r\n";
    response += html;

    send(sock, response.c_str(), response.size(), 0);
}

class ServerTcp
{
public:
    ServerTcp(uint16_t port, const std::string &ip = "")
        : port_(port), ip_(ip), listenSock_(-1)
    {
        quit_ = false;
    }

    ~ServerTcp()
    {
        if (listenSock_ >= 0)
            close(listenSock_);
    }

public:
    void init()
    {
        // 1. 创建socket
        listenSock_ = socket(PF_INET, SOCK_STREAM, 0);
        if (listenSock_ < 0)
        {
            exit(1);
        }

        // 2. bind
        // 2.1 填充服务器信息
        struct sockaddr_in local; // 用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = PF_INET;
        local.sin_port = htons(port_);
        ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));
        // 2.2 本地socket信息,写入sock_对应的内核区域
        if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0)
        {
            exit(2);
        }

        // 3. 监听socket,为何要监听呢?tcp是面向连接的!
        if (listen(listenSock_, 5) < 0)
        {
            exit(3);
        }
    }

    void loop()
    {
        signal(SIGCHLD, SIG_IGN);
        while (!quit_)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4. 获取连接,accept的返回值是一个新的socket fd
            int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
            if (quit_)
                break;
            if (serviceSock < 0)
            {
                cerr << "accept error..." << endl;
                // 获取连接失败,继续获取
                continue;
            }
            // 4.1 获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            std::string peerIp = inet_ntoa(peer.sin_addr);
            
            pid_t id = fork();
            assert(id != -1);
            if (id == 0)
            {
                close(listenSock_); // 建议
                if (fork() > 0)
                    exit(0);
                handlerHttpRequest(serviceSock);
                exit(0);
            }
            close(serviceSock);
            wait();
        }
    }

    bool quitServer()
    {
        quit_ = true;
        return true;
    }

private:
    int listenSock_;
    uint16_t port_;
    std::string ip_;
    bool quit_; // 安全退出
};

运行服务器并使用浏览器访问,此时我们通过 Fiddler 可以看到服务器给浏览器的 HTTP 响应报头中共包含了 Set-Cookie 字段。

在这里插入图片描述

总结

Cookie 和 Session 是 Web 开发中常用的数据存储和传递技术。Cookie 将数据存储在客户端浏览器,通过 HTTP 请求自动发送给服务器;而 Session 将数据信息存储在服务器中,通过 Cookie 或 URL 重写将 SessionID 发送给客户端。它们存储位置、数据容量、安全性、传输方式、生命周期和应用场景等方面都具有明显差异。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

风&57

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

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

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

打赏作者

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

抵扣说明:

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

余额充值