超文本传输协议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小时内被盗用,而且和权限也有限,因此影响不会太大。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

带你学习现代C++并发编程

通过对C并发编程的理解&#xff0c;我总结了相关的文档&#xff0c;有需要的可以关注我公众号&#xff0c;并给我留言&#xff01; 这是目录

Cookie/Session

1.Cookie HTTP 协议自身是属于 "无状态" 协议. "无状态" 的含义指的是: 默认情况下 HTTP 协议的客户端和服务器之间的这次通信, 和下次通信之间没有直接的联系. 但是实际开发中, 我们很多时候是需要知道请求之间的关联关系的. 例如登陆网站成功后, 第二…

创建数据库管理账户以及授权

一、创建数据酷管理账户命令 为了保障数据库系统的安全性&#xff0c;以及让 其他用户协同管理数据库&#xff0c;可以在MariaDB数据库管理系统中为他们创建多个专用的数据库管理账户&#xff0c;然后再分配合理的权限&#xff0c;以满足他们的工作需求. 使用root管理员 登录…

自定义类型(二)结构体位段,联合体,枚举

这周一时兴起&#xff0c;想写两篇文章来拿个卷吧&#xff0c;今天也是又来写一篇博客了&#xff0c;也是该结束自定义类型的学习与巩固了。 常常会回顾努力的自己&#xff0c;所以要给自己的努力留下足迹。 为今天努力的自己打个卡&#xff0c;留个痕迹吧 2024.03.30 小闭…

LInux|命令行参数|环境变量

LInux|命令行参数|环境变量 命令行参数main的参数之argc&#xff0c;argv几个小知识<font color#0099ff size 5 face"黑体">1.子进程默认能看到并访问父进程的数据<font color#4b0082 size 5 face"黑体">2.命令行创建的程序父进程都是bash 环…

微分方程错题本

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

【Qt】:信号与槽(二)

信号与槽 一.带参数的信号和槽二.信号与槽的多对多连接三.信号与槽的断开四.lamda表达式定义槽函数 一.带参数的信号和槽 Qt的信号和槽也⽀持带有参数,同时也可以⽀持重载.此处我们要求,信号函数的参数列表要和对应连接的槽函数参数列表⼀致.&#xff08;一致指的是类型一致&a…

mysql安装遇到的问题

最近mysql安装遇到了许多问题 这个界面是下载器界面&#xff0c;reconfigure是重新配置这个版本&#xff0c;要新安装要点add 进入这个界面选择对应的版本下载

MySQL 数据库基础操作详解

文章目录 MySQL 数据库基础操作详解1. 基本概念2. 库的操作3. 表的操作4. 数据操作5. 示例示例一&#xff1a;创建表和插入数据示例二&#xff1a;查询数据示例三&#xff1a;更新数据示例四&#xff1a;删除数据 MySQL 数据库基础操作详解 MySQL 是一种常用的关系型数据库管理…

【目录整理】(五)

​​​​​Git 基础 Git 详细安装教程文章浏览阅读10w次&#xff0c;点赞9.6k次&#xff0c;收藏1.7w次。Git 是个免费的开源分布式版本控制系统&#xff0c;下载地址为git-scm.com 或者 gitforwindows.org&#xff0c;本文介绍 Git-2.40.0-64-bit.exe 版本的安装方法&#x…

python电商结合双轨制

最近又重新整合翻看以前的数据&#xff0c;图片&#xff0c;绘画&#xff0c;还有各种编程代码&#xff0c;python,leetcode,还有关于商业方面的一些见解,想起了大学时候和同学们并肩作战&#xff0c;熬夜编码的时光。还有大数据&#xff0c;八爪鱼爬虫。 下面是我的手稿电商打…

算法学习——LeetCode力扣动态规划篇9

算法学习——LeetCode力扣动态规划篇9 1035. 不相交的线 1035. 不相交的线 - 力扣&#xff08;LeetCode&#xff09; 描述 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#x…

JavaSE day16笔记 - string

第十六天课堂笔记 学习任务 Comparable接口★★★★ 接口 : 功能的封装 > 一组操作规范 一个抽象方法 -> 某一个功能的封装多个抽象方法 -> 一组操作规范 接口与抽象类的区别 1本质不同 接口是功能的封装 , 具有什么功能 > 对象能干什么抽象类是事物本质的抽象 &…

2536. 子矩阵元素加 1

跳转题目 本题暴力可以做&#xff0c;猜到用差分&#xff0c;但是不熟&#xff0c;不知道二维差分怎么用&#xff0c;碰到用差分的题目太少了。 暴力算法&#xff1a; class Solution { public:vector<vector<int>> rangeAddQueries(int n, vector<vector<…

Docker 部署 FRP 内网穿透 实现端口映射

Frp 是一个专注于内网穿透的高性能的反向代理应用&#xff0c;支持 TCP、UDP、HTTP、HTTPS 等多种协议&#xff0c;且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。 官网地址&#xff1a;https://github.com/fatedier/frp 准备工作…

数据结构算法刷题笔记——题型解法

数据结构算法刷题笔记——题型解法 一、常用容器1.1 vector1.1.1 vector基本操作1.1.1.0 头文件#include<vector>1.1.1.1 构造一个vector容器1.1.1.2 元素访问1.1.1.3 元素个数 .size()1.1.1.4 最大容量 .capacity()1.1.1.5 改变容器有效元素个数 .resize(n)1.1.1.6 改变…

STM32 | PWM脉冲宽度调制(第五天按键中断,控制电机正/反转、加速、减速、暂停与继续源码解析)

​ STM32 | PWM脉冲宽度调制(第五天)STM32 | PWM脉冲宽度调制(第五天呼吸灯源码解析)STM32 | PWM脉冲宽度调制(第五天电机速度控制源码解析)PWM 技术在以下其他机器学习领域和应用中也可以发挥作用: 自然语言处理 (NLP):调节文本生成模型(例如 GPT-3)的输出长度和多样…

NFT-前端开发(一)

使用 在我们想要保存项目的目录下打开终端运行npx create-react-app test2命令初始化&#xff0c;test2是我们的项目名字&#xff0c;可以自己去更改。 初始化完成后&#xff0c;我们目录下就会多出一个test2文件夹 &#xff0c;然后我们在vscode中打开该文件夹 然后我们打开j…

Java8之接口默认方法

Java8之接口默认方法 一、介绍二、代码1、接口2、实现类3、测试代码4、效果 一、介绍 在Java8中&#xff0c;允许为接口方法提供一个默认的实现。必须用default修饰符标记这样一个方法。默认方法也可以调用其他方法 二、代码 1、接口 public interface PersonService {void…

代码随想录算法训练营第二十五天| 回溯算法理论基础、LeetCode77.组合

一、216.组合总和III 题目链接/文章讲解/视频讲解&#xff1a; https://programmercarl.com/0216.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8CIII.html 状态&#xff1a;已解决 1.思路 做过77题&#xff08;上篇博客&#xff09;后&#xff0c;这道题也就不难了&#xff0c;无非是多…