HTTP Cookie与Session

目录

[一. 引入Cookie](#一. 引入Cookie)

[1.1 定义](#1.1 定义)

[1.2 工作原理](#1.2 工作原理)

[1.3 分类](#1.3 分类)

[二. 认识Cookie](#二. 认识Cookie)

[三. 测试Cookie](#三. 测试Cookie)

[五. 引入Session](#五. 引入Session)

[六. 测试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值。


基本格式

cpp 复制代码
Set-Cookie: <name>=<value>

其中 <name> 是 Cookie 的名称, 是 <value> Cookie 的值。也是一个KV结构。


完整的Cookie示例

cpp 复制代码
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 是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义为通过那里的经线。理论上来说,格林尼治标准时间的正午是 指当太阳横穿格林尼治子午线时的时间。

但值得注意的是,地球的自转是有些不规则的,且正在缓慢减速。因此, 格林尼治时间已经不再被作为标准时间使用。

    1. 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写入浏览器:

cpp 复制代码
string ProveCookieWrite()//证明cookie能被写入浏览器
{
    return "Set-Cookie:username=zhangsan;\r\n";
}

在处理业务函数内部调用测试:

cpp 复制代码
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。


测试写入过期时间:

cpp 复制代码
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;
}

我们手动定义过期时间。

cpp 复制代码
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:

cpp 复制代码
string ProvePath()
{
    return "Set-Cookie:username=zhangsan; path=/a/b;\r\n";
}

string HandlerHttpRequest(string req)
{
#ifdef TEST
    cout << "-----------------" << 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管理:

cpp 复制代码
#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;
};

上层处理业务函数:

cpp 复制代码
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-Type
    
    response->AddHeader("Content-Type",_mime_type[suffix]);
    response->AddHeader("Location","https://www.qq.com/");
    return response->Serialize();
}

访问/login,模拟登录:

总结:

HTTP Cookie 和 Session 都是用于在 Web 应用中跟踪用户状态的机制。Cookie 是存 储在客户端的,而 Session 是存储在服务器端的。它们各有优缺点,通常在实际应用 中会结合使用,以达到最佳的用户体验和安全性。


总结:

好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。

祝大家越来越好,不用关注我(疯狂暗示)

相关推荐
F_D_Z几秒前
【解决办法】git clone报错unable to access ‘xxx‘: SSL certificate problem:
网络·git·网络协议·ssl
加载中loading...34 分钟前
Linux的多线程(线程的创建,退出,取消请求,取消处理例程,线程属性的设置)
linux·运维·服务器·c语言
技术无疆36 分钟前
【Python】Uvicorn:Python 异步 ASGI 服务器详解
运维·服务器·开发语言·网络·python·pygame·python3.11
BUG制造机.44 分钟前
TCP --- 确认应答机制以及三次握手四次挥手
网络·网络协议·tcp/ip
无聊看看天T^T1 小时前
网络基础:TCP/IP五层模型、数据在局域网传输和跨网络传输的基本流程、IP地址与MAC地址的简单解析
网络·数据结构·c++·网络协议·tcp/ip·算法
陈序缘1 小时前
Go语言实现长连接并发框架 - 任务管理器
linux·服务器·开发语言·后端·golang
原机小子2 小时前
大学生就业桥梁:基于Spring Boot的招聘系统
服务器·数据库·spring boot
读心悦2 小时前
在 ArkTS 网络请求中,重新封装一下 http 模块
网络·网络协议·http
running_bug_Hu2 小时前
僵尸进程、孤儿进程和守护进程
linux·服务器·网络