超文本传输协议HTTP

HTTP协议

在网络通信中,我们可以自己进行定制协议,但是也有许多已经十分成熟的应用层协议,比如我们下面说的HTTP协议。

HTTP协议简介

HTTP(Hyper Text Transfer Protocol)协议又叫做超文本传输协议,是一个简单的请求-响应协议,HTTP通常运行在TCP之上,它是一个应用层协议。

URL

URL(Uniform Resource Lacator)叫做统一资源定位符,也就是我们通常所说的网址,是因特网的万维网服务程序上用于指定信息位置的表示方法。

一个URL由一下几部分组成。

http://user:pass@www.example.jp:80/dir/index.htm?uid=1#ch1
  • [http://]

一、协议方案名

http://代表的是协议方案名,表示请求时需要用到的协议,通常使用的是HTTP协议或者安全协议HTTPS。HTTPS协议是以安全为目标的HTTP协议,即在HTTP协议的基础上通过传输加密和身份认证保证了传输过程的安全性。

常见的应用层协议还有:DNS协议:域名系统、FTP协议:文件传输协议、TELNET协议:远程终端协议、HTTPS协议:安全数据传输协议、SMTP协议:电子邮件传输协议、POP3协议:邮件读取协议、SNMP协议:简单网络管理协议、TFTP协议:简单文件传输协议。

  • [user:pass]

二、登录信息(认证)

user:pass代表的是登录认证信息,包括用户的用户名和密码,虽然登录信息可以在URL中体现出来,但是绝大多数URL这个字段都是被省略的,因为登录信息可以通过其他方案交付给服务器。

  • [www.example.jp]

三、服务器地址

www.example.jp表示的是服务器地址,也叫做域名,比如www.baidu.comwww.qq.comwww.taobao.com等。

要注意的是,我们使用IP地址标识公网内的一台主机,但是IP地址本身不适合给用户看。我们可以通过ping命令,获得www.baidu.com域名解析后的IP地址。

如果用户看到的是IP地址,用户在访问网站前就不知道这个网站到底是干什么的,但是如果用户看到的是www.baidu.com这个域名,那么用户就至少知道这个网站是哪个公司的,因此域名有更好的自描述性。

实际上,我们可以认为域名和IP地址是等价的,因为在计算机中既可以使用域名,也可以使用IP地址。但是URL呈现出来的是可以让用户看到的,因此URL是以域名形式代表服务器地址的。

  • [80]

四、服务器端口号

80代表的是服务端口号,HTTP协议和套接字编程是一样位于应用层的,在进行套接字编程时,我们给服务器绑定对应的IP和端口号,而应用层协议同样需要明确的端口号。

常见协议对应的端口号:

HTTP-80、HTTPS-443、SSH-22

我们在使用某个协议时,该协议就是在给我们提供服务。现在大部分的服务与端口号是对应的,所以我们在使用时不用指明某个协议对应的端口号,因此在URL中服务器的端口号也是被省略的。

  • [/dir/index.htm]

五、带层次的文件路径

/dir/index.htm代表的是要访问的资源所在的路径,访问服务器是为了获取服务器上的某个资源,通过前面的域名和端口号已经可以找到对应的进程了,此时要做的就是指明该资源所在的路径。

比如我们输入百度的域名,浏览器就帮我们获取了百度的首页。

image-20240330144435887

当我们发起网页请求时,本质是获得了一张网页信息,然后浏览器对这张网页信息进行解释,最后就呈现出了对应的网页。

我们把这种资源称为网页资源,此外我们还可以向服务器请求视频、音频、图片等资源。HTTP之所以叫超文本传输协议,是因为很多资源并不是普通的文本资源。

因此在URL当中就有这样一个字段,用于表示要访问的资源所在的路径。此外我们可以看到,这里的路径分隔符是/,而不是\,这也就证明了实际很多服务都是部署在Linux上的。

  • [uid=1]

六、查询字符串

uid=1代表的是请求时提供的额外参数、这些参数是以键值对的形式存在的,通过&分隔。

我们在百度中搜索腾讯,URL中的参数其中一个是wd=腾讯,这个参数wd就是word,表示我们搜索的关键字。

image-20240330145236704

因此双方在网络通信时,是可以通过URL传输数据给用户的。

  • [ch1]

七、片段标识符

chi1代表的是片段标识符,是对资源的部分补充。

我们在看一组图片时,URL中就会出现片段标识符。

urlencode和urldecode

如果在搜索关键字当中出现了像/?:这样的字符,由于这些字符已经被URL当作特殊意义理解了,因此URL在呈现时会对这些特殊字符进行转义。

转义规则:

将需要转码的字符转换为16进制,从右到左,取4位(不足4位直接处理),每两位做一位,前面加上%,编码成%XY格式。

比如我们搜索C//时,由于/是一个特殊符号,就会被转义成对应的16进制的值0x2F,一个/就是%2F

image-20240330150940549

注意:URL不仅会对这些特殊符号做编码,也会对中文进行编码。

我们通过在线编码解码工具,看看我们上面说的对不对。

输入C%2F%2F

image-20240330151347651

点击解码

和我们在刚刚百度输入的一样

image-20240330151432728

实际上在服务器拿到对应的URL后,也需要对编码后的参数进行解码。

解码其实就是编码的逆过程。

HTTP协议格式

HTTP协议请求格式

image-20240330152611135

HTTP请求由以下四部分组成

  • 请求行

[请求方法]+[URL]+[http版本]

  • 请求报头

请求的属性,一般以[key:value]的形式,以行为单位陈列。

  • 空行

遇到空行代表请求报头结束。

  • 请求正文

允许为空,如果不为空,在请求报头中会有一个Content-Length属性来标识请求正文长度。

前三部分是HTTP协议自带的,请求正文一般是用户的信息或数据,如果没有信息上传就为空。

分离HTTP请求的报头和有效载荷

当应用层收到一个HTTP请求时,它必须将HTTP的报头和有效载荷进行分离。

请求行和请求报头就是HTTP报头,请求正文就是HTTP的有效载荷。

我们可以通过HTTP请求中的空行来进行分离报头和有效载荷。当服务器收到一个HTTP请求,就可以进行按行读取,如果读取到空行,就说明已经把报头读取完了。

实际上,HTTP请求中的空行就是为了方便分离报头和有效载荷的。

如果我们把HTTP请求想象成一个大的线性结构,每行都是用\n隔开的,如果连续读到两个\n就说明已经把报头读取完毕了,剩下的就是有效载荷了。

尝试获取浏览器的HTTP请求

在网络协议栈中,应用层的下一层叫做传输层,而HTTP协议底层通常使用的传输层协议是TCP协议,因此我们可以用套接字编写一个TCP服务器,然后启动浏览器访问我们的这个服务器。

由于我们的服务器是直接用TCP套接字读取浏览器发来的HTTP请求,此时在服务端没有应用层对这个HTTP请求进行过任何解析,因此我们可以直接将浏览器发来的HTTP请求进行打印输出,此时就能看到HTTP请求的基本构成。

我们编写一个简单的TCP服务器,这个服务器要做的就是把浏览器发来的HTTP请求进行打印即可。

#include <iostream>
#include <cassert>
#include <fstream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;

class Socket {
private:
    int Create() {
        _listen_sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sock < 0){
            cerr << "socket error!" << endl;
            return 1;
	    }
        return 0;
    }
    int Bind() {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(8081);
        local.sin_addr.s_addr = htonl(INADDR_ANY);
        if (bind(_listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
            cerr << "bind error!" << endl;
            return 2;
        }
        return 0;
    }
    int Listen() {
        if (listen(_listen_sock, 5) < 0){
            cerr << "listen error!" << endl;
            return 3;
	    }
        return 0;
    }
public:
    Socket():_listen_sock(-1){}
    void initServer() {
        //1.创建TCP套接字
        assert(Create() == 0);
        //2.bind绑定自己的网络信息
        assert(Bind() == 0);
        //3.设置服务器的socket为监听状态,监听客户端什么时候发来连接请求
        assert(Listen() == 0);
    }
    void start() {
        struct sockaddr peer;
        memset(&peer, 0, sizeof(peer));
        socklen_t len = sizeof(peer);
        for(;;) {
            int sock = accept(_listen_sock, (struct sockaddr*)&peer, &len);
            if(sock < 0) {
                cerr << "accept err" << endl;
                continue;
            }
            if(fork() == 0) {
                //爸爸进程
                close(_listen_sock);
                if(fork() > 0) {
                    exit(0);
                }
                //孙子进程
                char buffer[1024];
                recv(sock, buffer, sizeof(buffer), 0);//读取HTTP请求
                cout << "--------------------------http request begin--------------------------" << endl;
                cout << buffer << endl;
                cout << "---------------------------http request end---------------------------" << endl;
                close(sock);
                exit(0);
            }
            //爷爷进程
            close(sock);
            waitpid(-1, nullptr, 0);//等待爸爸进程
        }
    }
    
private:
    int _listen_sock;
};

int main() {
    Socket sock;
    sock.initServer();
    sock.start();
    return 0;
}

image-20240330163438992

运行服务器程序后,然后用浏览器进行访问,此时我们的服务器就会收到浏览器发来的HTTP请求,并将收到的HTTP请求进行打印输出。

注意:

  • 浏览器向我们的服务器发起HTTP请求后,因为我们的服务器没有对进行响应,此时浏览器就会认为服务器没有收到,然后再不断发起新的HTTP请求,因此虽然我们只用浏览器访问了一次,但会受到多次HTTP请求。

  • 浏览器发起请求时默认用的就是HTTP协议,因此我们在浏览器的url框当中输入网址时可以不用指明HTTP协议。

  • url当中的/不能称之为我们云服务器上根目录,这个/表示的是web根目录,这个web根目录可以是你的机器上的任何一个目录,这个是可以自己指定的,不一定就是Linux的根目录。

其中请求行一般是不携带域名以及端口号的,因为在请求报头中的Host字段会进行指明,请求行当中的url表示你要访问这个服务器上的哪一路径下的资源。

image-20240330152611135

如果浏览器在访问我们的服务器时指明要访问的资源路径,那么此时浏览器发起的HTTP请求当中的url也会跟着变成该路径。

image-20240330164317720

请求报头当中全部都是以key: value形式按行陈列的各种请求属性,请求属性陈列完后紧接着的就是一个空行,空行后的就是本次HTTP请求的请求正文,此时请求正文为空字符串,因此这里有两个空行。

HTTP的响应格式

image-20240330165030649

HTTP响应由以下四部分组成:

  • 状态行:

[http版本]+[状态码]+[状态码描述]

  • 响应报头

响应的属性,这些属性都是以key: value的形式按行陈列的。

  • 空行

遇到空行表示响应报头结束。

  • 响应正文

响应正文允许为空字符串,如果响应正文存在,则响应报头中会有一个Content-Length属性来标识响应正文的长度。比如服务器返回了一个html页面,那么这个html页面的内容就是在响应正文当中的。

分离HTTP响应的报头和有效载荷

状态行和响应报头就是HTTP的报头信息,响应正文实际就是HTTP的有效载荷。

与HTTP请求相同,当应用层收到一个HTTP响应时,也是根据HTTP响应当中的空行来分离报头和有效载荷的

当客户端收到一个HTTP响应后,就可以按行进行读取,如果读取到空行则说明报头已经读取完毕。

构建一个HTTP响应

我们就将当前服务程序所在的路径作为我们的web根目录,我们可以在该目录下创建一个html文件,然后编写一个简单的html作为当前服务器的首页。

<html>
    <head></head>
    <body>
        <h1>hello http</h1>
    </body>
</html>
#include <iostream>
#include <cassert>
#include <fstream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;

class Socket {
private:
    int Create() {
        _listen_sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sock < 0){
            cerr << "socket error!" << endl;
            return 1;
	    }
        return 0;
    }
    int Bind() {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(8081);
        local.sin_addr.s_addr = htonl(INADDR_ANY);
        if (bind(_listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
            cerr << "bind error!" << endl;
            return 2;
        }
        return 0;
    }
    int Listen() {
        if (listen(_listen_sock, 5) < 0){
            cerr << "listen error!" << endl;
            return 3;
	    }
        return 0;
    }
public:
    Socket():_listen_sock(-1){}
    void initServer() {
        //1.创建TCP套接字
        assert(Create() == 0);
        //2.bind绑定自己的网络信息
        assert(Bind() == 0);
        //3.设置服务器的socket为监听状态,监听客户端什么时候发来连接请求
        assert(Listen() == 0);
    }
    void start() {
        struct sockaddr peer;
        memset(&peer, 0, sizeof(peer));
        socklen_t len = sizeof(peer);
        for(;;) {
            int sock = accept(_listen_sock, (struct sockaddr*)&peer, &len);
            if(sock < 0) {
                cerr << "accept err" << endl;
                continue;
            }
            if(fork() == 0) {
                //爸爸进程
                close(_listen_sock);
                if(fork() > 0) {
                    exit(0);
                }
                //孙子进程
                char buffer[1024];
                recv(sock, buffer, sizeof(buffer), 0);//读取HTTP请求
                cout << "--------------------------http request begin--------------------------" << endl;
                cout << buffer << endl;
                cout << "---------------------------http request end---------------------------" << endl;
                close(sock);
                exit(0);
            }
            //读取default.html文件
			ifstream in("default.html");
			if (in.is_open()){
				in.seekg(0, in.end);
				int len = in.tellg();
				in.seekg(0, in.beg);
				char* file = new char[len];
				in.read(file, len);
				in.close();
				
				//构建HTTP响应
				string status_line = "http/1.1 200 OK\n"; //状态行
				string response_header = "Content-Length: " + to_string(len) + "\n"; //响应报头
				string blank = "\n"; //空行
				string response_text = file; //响应正文
				string response = status_line + response_header + blank + response_text; //响应报文
				
				//响应HTTP请求
				send(sock, response.c_str(), response.size(), 0);

				delete[] file;
            }
            //爷爷进程
            close(sock);
            waitpid(-1, nullptr, 0);//等待爸爸进程
        }
    }
private:
    int _listen_sock;
};

int main() {
    Socket sock;
    sock.initServer();
    sock.start();
    return 0;
}

当浏览器向服务器发起HTTP请求时,不管浏览器发来的是什么请求,我们都将这个网页响应给浏览器,此时这个html文件的内容就应该放在响应正文当中,我们只需读取该文件当中的内容,然后将其作为响应正文即可。

image-20240330173614981

我们也可以通过telnet命令来访问我们的服务器,也是能够得到这个HTTP响应的。

image-20240330174003655

注意:

  • 实际我们在进行网络请求的时候,如果不指明请求资源的路径,此时默认你想访问的就是目标网站的首页,也就是web根目录下的index.html文件。
  • 我们在构建HTTP响应时,在响应报头当中只添加了一个属性信息Content-Length,表示响应正文的长度,实际HTTP响应报头当中的属性信息还有很多。
为什么在进行通信时,要交互双方的版本

HTTP请求当中的请求行和HTTP响应当中的状态行,当中都包含了http的版本信息。

HTTP请求是由客户端发的,因此HTTP请求当中表明的是客户端的http版本,而HTTP响应是由服务器发的,因此HTTP响应当中表明的是服务器的http版本。

在通信时交互版本,目的是为了兼容性。因为客户端和服务端使用的http版本可能是不同的,为了让不同版本的客户端都能享受到服务端的服务,所以需要双方交互版本

HTTP的方法

常见方法

GET:

  • 作用:获取资源
  • 对应版本:1.0、1.1

POST:

  • 作用:传输实体主体
  • 对应版本:1.0、1.1

PUT:

  • 作用:传输文件
  • 对应版本:1.0、1.1

HEAD:

  • 作用:获得报文首部
  • 对应版本:1.0、1.1

DELETE:

  • 作用:删除文件
  • 对应版本:1.0、1.1

OPTIONS:

  • 作用:询问支持的方法
  • 对应版本:1.1

TRACE:

  • 作用:追踪路径
  • 对应版本:1.1

CONNECT:

  • 作用:要求用隧道协议连接代理
  • 对应版本:1.1

LINK:

  • 作用:建立和资源之间的关系
  • 对应版本:1.0

UNLINK:

  • 作用:断开连接关系
  • 对应版本:1.0

这些方法中,最常用的就是GET和POST方法。

GET方法一般用于获取某种资源信息,POST方法一般用于将数据上传给服务器。

实际上传数据时,也有可能使用GET方法。

GET和POST方法都是可以传参的:

  • GET通过URL传参
  • POST通过正文传参

由于URL的长度是有限的,所以POST方法通过正文传参能传递更多的参数。

而且理论上使用POST方法更加安全,因为POST方法不会把你的参数回显到URL中,也就不会被人轻易看到。

实际上,POST和GET方法都是不安全的,要做到安全智能通过加密来实现。

演示GET和POST的不同

我们html中再添加两个表单,作为用户名和密码的输入。

image-20240330180515731

效果:

image-20240330180607858

我们使用GET方法,当我们提交完用户名和密码时,我们的用户名和密码就会自动被同步到URL当中。

image-20240330180501109

服务器也能收到提交的请求。

image-20240330180207365

如果我们改为POST方法,我们提交的参数就不会在URL中显示。

image-20240330180854247

服务器会通过正文来接收到用户名和密码

image-20240330181031099

注意:

  • 我们使用GET方法时,会将提交的参数提交到URL中,所以GET方法一般是处理数据不敏感的
  • 如果你传递的一些数据比较私密,那就可以使用POST方法,不是因为POST安全,而是因为它通过正文传输数据,不会把数据回显到URL中,相对来说比较私密。
HTTP的状态码

1XX:信息性状态码,指接收的请求正在处理

2XX:成功状态码,请求正常处理完毕

3XX:重定向状态码,需要进行附件操作以完成请求

4XX:客户端错误状态码,服务器无法处理请求

5XX:服务器错误状态码,服务器处理请求出错

常见的状态码:200(OK)、302(Redirect)、404(Not Found)、403(Forbidden请求权限不够)、504(Bad Gateway)

重定向

重定向,也称为 URL 转发,通过各种方法将各种网络请求重新定个方向转到其它位置,这个服务器相当于提供了一个引路的服务。

重定向又可分为临时重定向和永久重定向,其中状态码301表示的就是永久重定向,而状态码302和307表示的是临时重定向。

临时重定向和永久重定向本质是影响客户端的标签,决定客户端是否需要更新目标地址。

如果某个网站是永久重定向,那么第一次访问该网站时由浏览器帮你进行重定向,但后续再访问该网站时就不需要浏览器再进行重定向了,此时你访问的直接就是重定向后的网站。

而如果某个网站是临时重定向,那么每次访问该网站时如果需要进行重定向,都需要浏览器来帮我们完成重定向跳转到目标网站。
演示重定向

#include <iostream>
#include <cassert>
#include <fstream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;

class Socket {
private:
    int Create() {
        _listen_sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sock < 0){
            cerr << "socket error!" << endl;
            return 1;
	    }
        return 0;
    }
    int Bind() {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(8080);
        local.sin_addr.s_addr = htonl(INADDR_ANY);
        if (bind(_listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
            cerr << "bind error!" << endl;
            return 2;
        }
        return 0;
    }
    int Listen() {
        if (listen(_listen_sock, 5) < 0){
            cerr << "listen error!" << endl;
            return 3;
	    }
        return 0;
    }
public:
    Socket():_listen_sock(-1){}
    void initServer() {
        //1.创建TCP套接字
        assert(Create() == 0);
        //2.bind绑定自己的网络信息
        assert(Bind() == 0);
        //3.设置服务器的socket为监听状态,监听客户端什么时候发来连接请求
        assert(Listen() == 0);
    }
    void start() {
        struct sockaddr peer;
        memset(&peer, 0, sizeof(peer));
        socklen_t len = sizeof(peer);
        for(;;) {
            int sock = accept(_listen_sock, (struct sockaddr*)&peer, &len);
            if(sock < 0) {
                cerr << "accept err" << endl;
                continue;
            }
            if(fork() == 0) {
                //爸爸进程
                close(_listen_sock);
                if(fork() > 0) {
                    exit(0);
                }
                //孙子进程
                char buffer[1024];
                recv(sock, buffer, sizeof(buffer), 0);//读取HTTP请求
                cout << "--------------------------http request begin--------------------------" << endl;
                cout << buffer << endl;
                cout << "---------------------------http request end---------------------------" << endl;
                close(sock);
                exit(0);
            }
            //构建HTTP响应
			string status_line = "http/1.1 307 Temporary Redirect\n"; //状态行
			string response_header = "Location: https://www.qq.com/\n"; //响应报头
			string blank = "\n"; //空行
			string response = status_line + response_header + blank; //响应报文
			
			//响应HTTP请求
			send(sock, response.c_str(), response.size(), 0);
            //爷爷进程
            close(sock);
            waitpid(-1, nullptr, 0);//等待爸爸进程
        }
    }
private:
    int _listen_sock;
};

int main() {
    Socket sock;
    sock.initServer();
    sock.start();
    return 0;
}

我们运行程序后,在浏览器上进行访问,我们会发现他自己跳转到了ww.qq.com

我们再用telnet演示:

image-20240330182826757

HTTP常见的Header

常见的Header:

  • Content-Type:数据类型(text/html等)。
  • Content-Length:正文的长度。
  • Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上。

Host字段代表了客户端要访问的服务的IP和端口。我们在通过浏览器访问我们的服务器时,HTTP请求中的Host字段填的就是服务器的IP和端口。

为什么客户端还要告诉服务器它要访问的服务的对应的IP和端口?

因为有的服务器是一个代理服务器,它是替代客户端向其他服务器发起请求,然后把请求的结果返回给客户端。这种情况下,就需要告诉代理服务器,它需要访问的IP和端口。

  • User-Agent:声明用户的操作系统和浏览器的版本信息。

User-Agent代表的是客户端对应的操作系统和浏览器的版本信息。

image-20240330184038281

  • Referer:当前页面是哪个页面跳转过来的。

代表从哪个页面跳转过来的,方便回退到上一个网页。

  • Location:搭配3XX状态码使用,告诉客户端接下来要去哪里访问。

  • Cookie:用于在客户端存储少量信息,通常用于实现会话(session)的功能。

  • Connect:Keep-Alive长连接

HTTP1.0的常见工作方式是客户端和服务端先建立连接,然后客户端再给服务端发起请求,服务器再进行响应。

如果一个连接建立以后客户端和服务端只进行一次交互,就将连接关闭,那就太浪费资源了,所以主流的HTTP1.1是支持长连接的。

长连接就是,建立连接后,客户端可以向服务端不断的写入多个HTTP请求,而服务器在上层一个个读取这些请求就行了,此时一条连接就可以传送大量的请求和响应。

如果HTTP请求或者响应报头中Connect字段对应的值是Keep-Alive,就代表是支持长连接的。

Cookie和Session

Cookie

HTTP实际上是一种无状态协议,HTTP的每次请求和响应之间是没有任何关系的,但是你在使用浏览器时发现并不是这样的。

比如,你登录了一次csdn后,就算你把浏览器关闭或者重启了电脑,当再一次打开csdn时,csdn并没有要求你重新登录,这实际就是通过Cookie实现的。

点击edge浏览器的锁标志就可以查看网站对应的Cookie数据。

image-20240330191837755

这些cookie数据都是服务器方写的,如果你将某些cookie删除,那么此时可能就需要你重新进行登录认证了,因为你删除的可能正好就是你登录时所设置的cookie信息。

那么什么是Cookie?

HTTP是一种无状态协议,如果没有Cookie,那么我们每次在访问页面时都需要进行身份认证也就是登录账号。

比如,你是某视频网的会员,当你访问各种VIP视频时,都要麻烦你登录一次,这样就是让人感觉体验极差。

而Cookie就是为了解决这个问题的:

我们在第一次登录这个网站时,进行了身份认证,服务器确认你是一个合法的用户,就会进行Set-Cookie操作。

注意:Set-Cookie是HTTP报头中的一种属性信息

在服务器Set-Cookie后,服务器进行HTTP响应时就会把这个Set-Cookie响应给浏览器,浏览器在收到响应后,会将Set-Cookie中的值提取出来放到本地的Cookie文件中,此时就相等于把用户名和密码保存在了本地。

image-20240330193618243

往后只要再次访问这个网站,浏览器会再次发起http请求,同时将保存的cookie信息推送至服务器,无须用户再次输入账号密码重新登录。
cookie信息可分为文件级cookie和内存级cookie两种,在本地,Cookie是可以手动配置文件级或者内存级。

如果是内存级cookie,则cookie的生命周期随浏览器进程,当我们把浏览器关闭之后,cookie信息会自动销毁,重新打开浏览器登录网站时,则需要再次输入账号和密码。

如果是文件级cookie,则cookie不受浏览器关闭的影响,或电脑开关机重启的影响,因为他存在于磁盘外设上。

但是Cookie还存在一个被盗用的问题。

Cookie中存储的是我们的私密信息,一旦Cookie被盗,我们的信息也就泄露了。因此主流的服务器还引入了一个SessionID。

Session

当我们第一次登录一个网站时,服务器认证成功后还会生成一个对应SessionID,这个SessionID和用户的信息是不相关的。系统会把所有登录用户的SessionID维护起来。

此时当认证通过后服务端在对浏览器进行HTTP响应时,就会将这个生成的SessionID值响应给浏览器。

浏览器收到响应后会自动提取出SessionID的值,将其保存在浏览器的cookie文件当中。后续访问该服务器时,对应的HTTP请求当中就会自动携带上这个SessionID。

image-20240330194945125

服务器识别到HTTP请求当中包含了SessionID,就会提取出这个SessionID,然后再到对应的集合当中进行对比,对比成功就说明这个用户是曾经登录过的,此时也就自动就认证成功了,然后就会正常处理你发来的请求,这就是我们当前主流的工作方式。

  • 安全是相对的

引入SessionID后,浏览器中的Cookie文件中存的是SessionID,但是这个SessionId同样可以被盗取。

但是此时用户的用户名和密码就不会泄露了,由于SessionID已经泄露,非法用户仍然可以通过SessionID区访问我们曾经登录过的服务器,还是存在刚才的问题。

虽然引入SessionID并没有彻底解决安全问题,但是这个方法是相对安全的。因为互联网上是不存在绝对的安全的,网络中的信息随时是可能被别人破解的。

但是如果破解成本远大于破解带来的收益,那么就不会有人去破解这个信息了,这个信息就可以说是安全的。

引入SessionID的好处

引入SessionID后,用户的用户名和密码都是由服务器来维护的,本地Cookie文件中存的只是SessionID。

虽然SessionID有可能被盗取,但是服务器也有一些策略来保证用户信息的安全性。

  1. 服务器可以通过IP地址来判断用户登录的地址范围,如果用户在短时间内登录地址发生了巨大变化,此时服务器就会知道这个用户出异常了,就会清楚服务器中对应的SessionID。

这会要求用户在登录时重新输入用户名和密码,因为用户名和密码只有本人和服务器知道。重新验证后,就会将另一个IP识别为非法用户,加入黑名单中。将合法用户和非法用户分为白名单和黑名单。

  1. 当用户进行一些高权限的操作时,会让用户再次输入密码,再次确认信息。

非法用户是不知道正确的密码的,就会让非法用户不能进行高权限的操作。比如修改密码时,需要输入旧密码。

  1. SessionID也是会过期的,假如某一个SessionID只有1小时的有效期。

即使你的SessionID被盗用了,也仅仅是在1小时内被盗用,而且和权限也有限,因此影响不会太大。

理你发来的请求,这就是我们当前主流的工作方式。

  • 安全是相对的

引入SessionID后,浏览器中的Cookie文件中存的是SessionID,但是这个SessionId同样可以被盗取。

但是此时用户的用户名和密码就不会泄露了,由于SessionID已经泄露,非法用户仍然可以通过SessionID区访问我们曾经登录过的服务器,还是存在刚才的问题。

虽然引入SessionID并没有彻底解决安全问题,但是这个方法是相对安全的。因为互联网上是不存在绝对的安全的,网络中的信息随时是可能被别人破解的。

但是如果破解成本远大于破解带来的收益,那么就不会有人去破解这个信息了,这个信息就可以说是安全的。

引入SessionID的好处

引入SessionID后,用户的用户名和密码都是由服务器来维护的,本地Cookie文件中存的只是SessionID。

虽然SessionID有可能被盗取,但是服务器也有一些策略来保证用户信息的安全性。

  1. 服务器可以通过IP地址来判断用户登录的地址范围,如果用户在短时间内登录地址发生了巨大变化,此时服务器就会知道这个用户出异常了,就会清楚服务器中对应的SessionID。

这会要求用户在登录时重新输入用户名和密码,因为用户名和密码只有本人和服务器知道。重新验证后,就会将另一个IP识别为非法用户,加入黑名单中。将合法用户和非法用户分为白名单和黑名单。

  1. 当用户进行一些高权限的操作时,会让用户再次输入密码,再次确认信息。

非法用户是不知道正确的密码的,就会让非法用户不能进行高权限的操作。比如修改密码时,需要输入旧密码。

  1. SessionID也是会过期的,假如某一个SessionID只有1小时的有效期。

即使你的SessionID被盗用了,也仅仅是在1小时内被盗用,而且和权限也有限,因此影响不会太大。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/503402.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JAVAEE之网络编程

1.网络编程 网络编程&#xff0c;指网络上的主机&#xff0c;通过不同的进程&#xff0c;以编程的方式实现网络通信&#xff08;或称为网络数据传输&#xff09;。 当然&#xff0c;我们只要满足进程不同就行&#xff1b; 所以即便是同一个主机&#xff0c;只要是不同进程&am…

算法学习——LeetCode力扣图论篇1

算法学习——LeetCode力扣图论篇1 797. 所有可能的路径 797. 所有可能的路径 - 力扣&#xff08;LeetCode&#xff09; 描述 给你一个有 n 个节点的 有向无环图&#xff08;DAG&#xff09;&#xff0c;请你找出所有从节点 0 到节点 n-1 的路径并输出&#xff08;不要求按特…

elementui 导航菜单栏和Breadcrumb 面包屑关联

系列文章目录 一、elementui 导航菜单栏和Breadcrumb 面包屑关联 文章目录 系列文章目录前言一、elementui 导航菜单栏和Breadcrumb 面包屑怎么关联&#xff1f;二、实现效果三、实现步骤1.本项目演示布局2.添加面包屑2.实现breadcrumbName方法3.监听方法4.路由指配5.路由配置…

【C语言】Infiniband驱动mlx4_reset

一、注释 这个 mlx4_reset 函数负责重置 Mellanox 设备。它保存了设备的 PCI 头信息&#xff0c;然后重置了设备&#xff0c;之后还原保存的 PCI 头信息。请注意&#xff0c;该函数是用英文注释的&#xff0c;下面提供中文注释的版本。以下是该函数的流程&#xff1a; 1. 为保…

springboot项目学习-瑞吉外卖(4)续

1.任务 菜品的添加功能(涉及到两张表的数据添加) 2.菜品添加 功能页面如上&#xff0c;该页面有两个注意点 菜品分类&#xff1a;点击菜品分类后&#xff0c;会展示当前已有菜品&#xff1a;这个功能的实现要从category表里查询数据&#xff0c;然后再做展示口味做法配置&#…

SRS OBS利用RTMP协议实现音视频推拉流

参考&#xff1a;https://ossrs.net/lts/zh-cn/docs/v5/doc/getting-started 1&#xff09;docker直接运行SRS服务&#xff1a; docker run --rm -it -p 1935:1935 -p 1985:1985 -p 8080:8080 registry.cn-hangzhou.aliyuncs.com/ossrs/srs:5运行起来后可以http://localho…

java Web 疫苗预约管理系统用eclipse定制开发mysql数据库BS模式java编程jdbc

一、源码特点 JSP 疫苗预约管理系统是一套完善的web设计系统&#xff0c;对理解JSP java 编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,eclipse开发&#xff0c;数据库为Mysql5.0&#xff0c;使…

kaggle竞赛(房价预测)(Pytorch 06)

一 下载数据集 此数据集由Bart de Cock于2011年收集&#xff0c;涵盖了2006‐2010年期间 亚利桑那州 埃姆斯市的房价。 下载地址&#xff1a; import hashlib import os import tarfile import zipfile import requests#save DATA_HUB dict() DATA_URL http://d2l-data.s3…

“崖山数据库杯”深圳大学程序设计竞赛(正式赛)M题 一图秒

“崖山数据库杯”深圳大学程序设计竞赛&#xff08;正式赛&#xff09;_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ (nowcoder.com) —————— 可以去牛客看题解&#xff1a; 题解 | #暂时没想法#_牛客博客 (nowcoder.net) —————— 上面的就是题解了。…

Adobe Illustrator 2023 for Mac/Win:创意无限,设计无界

在数字艺术与设计领域&#xff0c;Adobe Illustrator 2023无疑是一颗璀璨的明星。这款专为Mac和Windows用户打造的矢量图形设计软件&#xff0c;以其强大的功能和卓越的性能&#xff0c;赢得了全球设计师的广泛赞誉。 Adobe Illustrator 2023在继承前代版本优点的基础上&#…

基于ARM内核的智能手环(day1)

整体介绍 智能手环由 ARM 内核 MCU(Cortex-M 系列)、TFTLCD 屏、温湿度传感器、心率传感器、 加速度传感器等主要几部分构成。该平台硬件采用 STM32 芯片&#xff0c;通过对温湿度传感器的驱动编写&#xff0c;获取周围温湿度数据&#xff0c;并在 LCD 屏显示&#xff0c;通过对…

设计模式12--组合模式

定义 案例一 案例二 优缺点

docker配置github仓库ghcr国内镜像加速

文章目录 说明ghcr.io简介配置镜像命令地址命令行方式1panel面板方式方式一&#xff1a;配置镜像加速&#xff0c;命令行拉取方式二&#xff1a;配置镜像仓库&#xff0c;可视化拉取 说明 由于使用的容器需要从github下载镜像&#xff0c;服务器在国外下载速度很慢&#xff0c…

MySQL InnoDB 之 多版本并发控制(MVCC)

多版本并发控制&#xff08;MVCC&#xff0c;Multi-Version Concurrency Control&#xff09;是数据库管理系统中用于提供高并发性和在事务处理中实现隔离级别的一种技术。MVCC 允许系统在不完全锁定数据库资源的情况下&#xff0c;处理多个并发事务&#xff0c;从而提高了数据…

计算机网络实验五:特定主机路由和默认路由

实验五&#xff1a;特定主机路由和默认路由 5.1 实验目的 &#xff08;1&#xff09;学习默认路由的概念和作用 &#xff08;2&#xff09;学习特定路由的概念和作用 &#xff08;3&#xff09;了解网络中路由选择的基本原理和应用 5.2 实验步骤 5.2.1 构建网络拓扑 在栏…

LeetCode - 字母板上的路径

1138. 字母板上的路径 刚看到这道题的时候,我居然想用搜索去做这道题,其实有更优解,用 / %算会更加的快,只需要遍历一次即可.假如说我们要找n,n是第13个字母,那他就位于 13 / 5 2, 13 % 5 3.他就位于三行三列(a为0,0),知道了原理,代码就好写了. class Solution { public:st…

基于51单片机HC05蓝牙环境检测系统

目录 1、概要 2、HC05配对传送数据教程 2.1 进入AT模式 2.2串口软件配置 2.3 异常分析 3、代码编写 4、原理图 5、仿真图 6、实物运行视频 7、小结 资料下载地址&#xff1a;基于51单片机手自动浇花系统 1、概要 本文详细介绍HC05蓝牙模块与51单片机的连接配对过程&#xff0c…

【WEEK5】 【DAY5】DML语言【中文版】

2024.3.29 Friday 目录 3.DML语言3.1.外键&#xff08;了解&#xff09;3.1.1.概念3.1.2.作用3.1.3.添加&#xff08;书写&#xff09;外键的几种方法3.1.3.1.创建表时直接在主动引用的表里写&#xff08;被引用的表的被引用的部分&#xff09;3.1.3.2.先创建表后修改表以添加…

二十四种设计模式与六大设计原则(三):【装饰模式、迭代器模式、组合模式、观察者模式、责任链模式、访问者模式】的定义、举例说明、核心思想、适用场景和优缺点

接上次博客&#xff1a;二十四种设计模式与六大设计原则&#xff08;二&#xff09;&#xff1a;【门面模式、适配器模式、模板方法模式、建造者模式、桥梁模式、命令模式】的定义、举例说明、核心思想、适用场景和优缺点-CSDN博客 目录 装饰模式【Decorator Pattern】 定义…

设计模式(9):外观模式

一.迪米特法则(最少知识原则) 一个软件实体应当尽可能少的与其他实体发生相互作用。 二.外观模式 为子系统提供统一的入口&#xff0c;封装子系统的复杂性&#xff0c;便于客户端调用。它的核心是什么呢&#xff0c;就是为我们的子系统提供一个统一的入口&#xff0c;封装子…