应用层协议 ——— HTTP协议

应用层协议 ——— HTTP协议

  • HTTP简介
  • 认识URL
    • 二、登录信息
    • 三、服务器地址
    • 四、服务器端口号
    • 五、带层次的文件路径
    • 六、查询字符串
    • 七、片段标识符
    • urlencode和urldecode
    • HTTP协议格式
    • HTTP请求协议格式
    • HTTP的方法
    • HTTP的状态码
    • HTTP常见的Header
    • HTTPS VS HTTP
    • 对称加密 VS 非对称加密

HTTP简介

HTTP(Hyper Text Transfer Protocol)协议又叫做超文本传输协议,是一个简单的请求-响应协议,HTTP通常运行在TCP之上。
在编写网络通信代码时,我们可以自己进行协议的定制,但实际有很多优秀的工程师早就已经写出了许多非常成熟的应用层协议,其中最典型的就是HTTP协议。

认识URL

URL(Uniform Resource Lacator)叫做统一资源定位符,也就是我们通常所说的网址,是因特网的万维网服务程序上用于指定信息位置的表示方法。
在这里插入图片描述
一、协议方案名
http://表示的是协议名称,表示请求时需要使用的协议,通常使用的是HTTP协议或安全协议HTTPS。HTTPS是以安全为目标的HTTP通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性。
常见的应用层协议:

DNS(Domain Name System)协议:域名系统。
FTP(File Transfer Protocol)协议:文件传输协议。
TELNET(Telnet)协议:远程终端协议。
HTTP(Hyper Text Transfer Protocol)协议:超文本传输协议。
HTTPS(Hyper Text Transfer Protocol over SecureSocket Layer)协议:安全数据传输协议。
SMTP(Simple Mail Transfer Protocol)协议:电子邮件传输协议。
POP3(Post Office Protocol - Version 3)协议:邮件读取协议。
SNMP(Simple Network Management Protocol)协议:简单网络管理协议。
TFTP(Trivial File Transfer Protocol)协议:简单文件传输协议。

二、登录信息

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

三、服务器地址

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

需要注意的是,我们用IP地址标识公网内的一台主机,但IP地址本身并不适合给用户看。比如说我们可以通过ping命令,分别获得www.baidu.com和www.qq.com这两个域名解析后的IP地址。
如果用户看到的是这两个IP地址,那么用户在访问这个网站之前并不知道这两个网站到底是干什么的,但如果用户看到的是www.baidu.com和www.qq.com这两个域名,那么用户至少知道这两个网站分别对应的是哪家公司,因此域名具有更好的自描述性。

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

四、服务器端口号

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

常见协议对应的端口号:

协议名称对应端口号
HTTP80
HTTPS443
SSH22

当我们使用某种协议时,该协议实际就是在为我们提供服务,现在这些常用的服务与端口号之间的对应关系都是明确的,所以我们在使用某种协议时实际是不需要指明该协议对应的端口号的,因此在URL当中,服务器的端口号一般也是被省略的。

五、带层次的文件路径

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

比如我们打开浏览器输入百度的域名后,此时浏览器就帮我们获取到了百度的首页。
当我们发起网页请求时,本质是获得了这样的一张网页信息,然后浏览器对这张网页信息进行解释,最后就呈现出了对应的网页。
在这里插入图片描述
我们可以将这种资源称为网页资源,此外我们还会向服务器请求视频、音频、网页、图片等资源。HTTP之所以叫做超文本传输协议,而不叫做文本传输协议,就是因为有很多资源实际并不是普通的文本资源。

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

六、查询字符串

uid=1表示的是请求时提供的额外的参数,这些参数是以键值对的形式,通过&符号分隔开的。

比如我们在百度上面搜索HTTP,此时可以看到URL中有很多参数,而在这众多的参数当中有一个参数wd(word),表示的就是我们搜索时的搜索关键字wd=HTTP。

七、片段标识符

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

比如我们在看组图的时候,URL当中就会出现片段标识符。

urlencode和urldecode

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

转义的规则如下:

将需要转码的字符转为十六进制,然后从右到左,取4位(不足4位直接处理),每两位做一位,前面加上%,编码成%XY格式。
示例
比如当我们搜索C++时,由于+加号在URL当中也是特殊符号,而+字符转为十六进制后的值就是0x2B,因此一个+就会被编码成一个%2B。
在这里插入图片描述
说明一下: URL当中除了会对这些特殊符号做编码,对中文也会进行编码。

HTTP协议格式

应用层常见的协议有HTTP和HTTPS,传输层常见的协议有TCP,网络层常见的协议是IP,数据链路层对应就是MAC帧了。其中下三层是由操作系统或者驱动帮我们完成的,它们主要负责的是通信细节。如果应用层不考虑下三层,在应用层自己的心目当中,它就可以认为自己是在和对方的应用层在直接进行数据交互。
在这里插入图片描述
下三层负责的是通信细节,而应用层负责的是如何使用传输过来的数据,两台主机在进行通信的时候,应用层的数据能够成功交给对端应用层,因为网络协议栈的下三层已经负责完成了这样的通信细节,而如何使用传输过来的数据就需要我们去定制协议,这里最典型的就是HTTP协议。

HTTP是基于请求和响应的应用层服务,作为客户端,你可以向服务器发起request,服务器收到这个request后,会对这个request做数据分析,得出你想要访问什么资源,然后服务器再构建response,完成这一次HTTP的请求。这种基于request&response这样的工作方式,我们称之为cs或bs模式,其中c表示client,s表示server,b表示browser。

由于HTTP是基于请求和响应的应用层访问,因此我们必须要知道HTTP对应的请求格式和响应格式,这就是学习HTTP的重点。

HTTP请求协议格式

HTTP请求协议格式如下:
在这里插入图片描述
HTTP请求由以下四部分组成:

请求行:[请求方法]+[url]+[http版本]
请求报头:请求的属性,这些属性都是以key: value的形式按行陈列的。
空行:遇到空行表示请求报头结束。
请求正文:请求正文允许为空字符串,如果请求正文存在,则在请求报头中会有一个Content-Length属性来标识请求正文的长度。
其中,前面三部分是一般是HTTP协议自带的,是由HTTP协议自行设置的,而请求正文一般是用户的相关信息或数据,如果用户在请求时没有信息要上传给服务器,此时请求正文就为空字符串。
如何将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 <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;

int main()
{
	//创建套接字
	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_sock < 0){
		cerr << "socket error!" << endl;
		return 1;
	}
	//绑定
	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;
	}
	//监听
	if (listen(listen_sock, 5) < 0){
		cerr << "listen error!" << endl;
		return 3;
	}
	//启动服务器
	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 error!" << 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); //等待爸爸进程
	}
	return 0;
}

说明一下:

浏览器向我们的服务器发起HTTP请求后,因为我们的服务器没有对进行响应,此时浏览器就会认为服务器没有收到,然后再不断发起新的HTTP请求,因此虽然我们只用浏览器访问了一次,但会受到多次HTTP请求。
由于浏览器发起请求时默认用的就是HTTP协议,因此我们在浏览器的url框当中输入网址时可以不用指明HTTP协议。
url当中的/不能称之为我们云服务器上根目录,这个/表示的是web根目录,这个web根目录可以是你的机器上的任何一个目录,这个是可以自己指定的,不一定就是Linux的根目录。
其中请求行当中的url一般是不携带域名以及端口号的,因为在请求报头中的Host字段当中会进行指明,请求行当中的url表示你要访问这个服务器上的哪一路径下的资源。如果浏览器在访问我们的服务器时指明要访问的资源路径,那么此时浏览器发起的HTTP请求当中的url也会跟着变成该路径。
而请求报头当中全部都是以key: value形式按行陈列的各种请求属性,请求属性陈列完后紧接着的就是一个空行,空行后的就是本次HTTP请求的请求正文,此时请求正文为空字符串,因此这里有两个空行。

HTTP响应协议格式
HTTP响应协议格式如下:
在这里插入图片描述
HTTP响应由以下四部分组成:

状态行:[http版本]+[状态码]+[状态码描述]
响应报头:响应的属性,这些属性都是以key: value的形式按行陈列的。
空行:遇到空行表示响应报头结束。
响应正文:响应正文允许为空字符串,如果响应正文存在,则响应报头中会有一个Content-Length属性来标识响应正文的长度。比如服务器返回了一个html页面,那么这个html页面的内容就是在响应正文当中的。
如何将HTTP响应的报头与有效载荷进行分离?
对于HTTP响应来讲,这里的状态行和响应报头就是HTTP的报头信息,而这里的响应正文实际就是HTTP的有效载荷。与HTTP请求相同,当应用层收到一个HTTP响应时,也是根据HTTP响应当中的空行来分离报头和有效载荷的。当客户端收到一个HTTP响应后,就可以按行进行读取,如果读取到空行则说明报头已经读取完毕。
构建HTTP响应给浏览器
服务器读取到客户端发来的HTTP请求后,需要对这个HTTP请求进行各种数据分析,然后构建成对应的HTTP响应发回给客户端。而我们的服务器连接到客户端后,实际就只读取了客户端发来的HTTP请求就将连接断开了。

接下来我们可以构建一个HTTP请求给浏览器,鉴于现在还没有办法分析浏览器发来的HTTP请求,这里我们可以给浏览器返回一个固定的HTTP响应。我们就将当前服务程序所在的路径作为我们的web根目录,我们可以在该目录下创建一个html文件,然后编写一个简单的html作为当前服务器的首页。
在这里插入图片描述
当浏览器向服务器发起HTTP请求时,不管浏览器发来的是什么请求,我们都将这个网页响应给浏览器,此时这个html文件的内容就应该放在响应正文当中,我们只需读取该文件当中的内容,然后将其作为响应正文即可。

#include <iostream>
#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;

int main()
{
	//创建套接字
	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_sock < 0){
		cerr << "socket error!" << endl;
		return 1;
	}
	//绑定
	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;
	}
	//监听
	if (listen(listen_sock, 5) < 0){
		cerr << "listen error!" << endl;
		return 3;
	}
	//启动服务器
	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 error!" << 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;
			
#define PAGE "index.html" //网站首页
			//读取index.html文件
			ifstream in(PAGE);
			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);
			exit(0);
		}
		//爷爷进程
		close(sock);
		waitpid(-1, nullptr, 0); //等待爸爸进程
	}
	return 0;
}

因此当浏览器访问我们的服务器时,服务器会将这个index.html文件响应给浏览器,而该html文件被浏览器解释后就会显示出相应的内容。
说明一下:

实际我们在进行网络请求的时候,如果不指明请求资源的路径,此时默认你想访问的就是目标网站的首页,也就是web根目录下的index.html文件。
由于只是作为示例,我们在构建HTTP响应时,在响应报头当中只添加了一个属性信息Content-Length,表示响应正文的长度,实际HTTP响应报头当中的属性信息还有很多。
HTTP为什么要交互版本?
HTTP请求当中的请求行和HTTP响应当中的状态行,当中都包含了http的版本信息。其中HTTP请求是由客户端发的,因此HTTP请求当中表明的是客户端的http版本,而HTTP响应是由服务器发的,因此HTTP响应当中表明的是服务器的http版本。

客户端和服务器双方在进行通信时会交互双方http版本,主要还是为了兼容性的问题。因为服务器和客户端使用的可能是不同的http版本,为了让不同版本的客户端都能享受到对应的服务,此时就要求通信双方需要进行版本协商。

客户端在发起HTTP请求时告诉服务器自己所使用的http版本,此时服务器就可以根据客户端使用的http版本,为客户端提供对应的服务,而不至于因为双方使用的http版本不同而导致无法正常通信。因此为了保证良好的兼容性,通信双方需要交互一下各自的版本信息。

HTTP的方法

常用方法

方法说明支持的HTTP协议版本
GET获取资源1.0、1.1
POST传输实体主体1.0、1.1

GET方法一般用于获取某种资源信息,而POST方法一般用于将数据上传给服务器。但实际我们上传数据时也有可能使用GET方法,比如百度提交数据时实际使用的就是GET方法。

GET方法和POST方法都可以带参:

GET方法是通过url传参的。
POST方法是通过正文传参的。
从GET方法和POST方法的传参形式可以看出,POST方法能传递更多的参数,因为url的长度是有限制的,POST方法通过正文传参就可以携带更多的数据。

此外,使用POST方法传参更加私密,因为POST方法不会将你的参数回显到url当中,此时也就不会被别人轻易看到。不能说POST方法比GET方法更安全,因为POST方法和GET方法实际都不安全,要做到安全只能通过加密来完成。

HTTP的状态码

在这里插入图片描述
最常见的状态码,比如200(OK),404(Not Found),403(Forbidden请求权限不够),302(Redirect),504(Bad Gateway)。
Redirection(重定向状态码)
重定向就是通过各种方法将各种网络请求重新定个方向转到其它位置,此时这个服务器相当于提供了一个引路的服务。

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

临时重定向和永久重定向本质是影响客户端的标签,决定客户端是否需要更新目标地址。如果某个网站是永久重定向,那么第一次访问该网站时由浏览器帮你进行重定向,但后续再访问该网站时就不需要浏览器再进行重定向了,此时你访问的直接就是重定向后的网站。而如果某个网站是临时重定向,那么每次访问该网站时如果需要进行重定向,都需要浏览器来帮我们完成重定向跳转到目标网站。
临时重定向演示
进行临时重定向时需要用到Location字段,Location字段是HTTP报头当中的一个属性信息,该字段表明了你所要重定向到的目标网站。

我们这里要演示临时重定向,可以将HTTP响应当中的状态码改为307,然后跟上对应的状态码描述,此外,还需要在HTTP响应报头当中添加Location字段,这个Location后面跟的就是你需要重定向到的网页,比如我们这里将其设置为CSDN的首页。

#include <iostream>
#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;

int main()
{
	//创建套接字
	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_sock < 0){
		cerr << "socket error!" << endl;
		return 1;
	}
	//绑定
	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;
	}
	//监听
	if (listen(listen_sock, 5) < 0){
		cerr << "listen error!" << endl;
		return 3;
	}
	//启动服务器
	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 error!" << 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;
			
			//构建HTTP响应
			string status_line = "http/1.1 307 Temporary Redirect\n"; //状态行
			string response_header = "Location: https://www.csdn.net/\n"; //响应报头
			string blank = "\n"; //空行
			string response = status_line + response_header + blank; //响应报文
			
			//响应HTTP请求
			send(sock, response.c_str(), response.size(), 0);

			close(sock);
			exit(0);
		}
		//爷爷进程
		close(sock);
		waitpid(-1, nullptr, 0); //等待爸爸进程
	}
	return 0;
}

HTTP常见的Header

HTTP常见的Header如下:

Content-Type:数据类型(text/html等)。
Content-Length:正文的长度。
Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上。
User-Agent:声明用户的操作系统和浏览器的版本信息。
Referer:当前页面是哪个页面跳转过来的。
Location:搭配3XX状态码使用,告诉客户端接下来要去哪里访问。
Cookie:用于在客户端存储少量信息,通常用于实现会话(session)的功能。
Host
Host字段表明了客户端要访问的服务的IP和端口,比如当浏览器访问我们的服务器时,浏览器发来的HTTP请求当中的Host字段填的就是我们的IP和端口。但客户端不就是要访问服务器吗?为什么客户端还要告诉服务器它要访问的服务对应的IP和端口?

因为有些服务器实际提供的是一种代理服务,也就是代替客户端向其他服务器发起请求,然后将请求得到的结果再返回给客户端。在这种情况下客户端就必须告诉代理服务器它要访问的服务对应的IP和端口,此时Host提供的信息就有效了。
User-Agent
User-Agent代表的是客户端对应的操作系统和浏览器的版本信息。

比如当我们用电脑下载某些软件时,它会自动向我们展示与我们操作系统相匹配的版本,这实际就是因为我们在向目标网站发起请求的时候,User-Agent字段当中包含了我们的主机信息,此时该网站就会向你推送相匹配的软件版本。
Referer
Referer代表的是你当前是从哪一个页面跳转过来的。Referer记录上一个页面的好处一方面是方便回退,另一方面可以知道我们当前页面与上一个页面之间的相关性。
Keep-Alive(长连接)
HTTP/1.0是通过request&response的方式来进行请求和响应的,HTTP/1.0常见的工作方式就是客户端和服务器先建立链接,然后客户端发起请求给服务器,服务器再对该请求进行响应,然后立马端口连接。

但如果一个连接建立后客户端和服务器只进行一次交互,就将连接关闭,就太浪费资源了,因此现在主流的HTTP/1.1是支持长连接的。所谓的长连接就是建立连接后,客户端可以不断的向服务器一次写入多个HTTP请求,而服务器在上层依次读取这些请求就行了,此时一条连接就可以传送大量的请求和响应,这就是长连接。

如果HTTP请求或响应报头当中的Connect字段对应的值是Keep-Alive,就代表支持长连接。
Cookie和Session
HTTP实际上是一种无状态协议,HTTP的每次请求/响应之间是没有任何关系的,但你在使用浏览器的时候发现并不是这样的。

比如当你登录一次CSDN后,就算你把CSDN网站关了甚至是重启电脑,当你再次打开CSDN网站时,CSDN并没有要求你再次输入账号和密码,这实际上是通过cookie技术实现的,点击浏览器当中锁的标志就可以看到对应网站的各种cookie数据。
在这里插入图片描述
这些cookie数据实际都是对应的服务器方写的,如果你将对应的某些cookie删除,那么此时可能就需要你重新进行登录认证了,因为你删除的可能正好就是你登录时所设置的cookie信息。
cookie是什么呢?
因为HTTP是一种无状态协议,如果没有cookie的存在,那么每当我们要进行页面请求时都需要重新输入账号和密码进行认证,这样太麻烦了。

比如你是某个视频网站的VIP,这个网站里面的VIP视频有成百上千个,你每次点击一个视频都要重新进行VIP身份认证。而HTTP不支持记录用户状态,那么我们就需要有一种独立技术来帮我们支持,这种技术目前现在已经内置到HTTP协议当中了,叫做cookie。

当我们第一次登录某个网站时,需要输入我们的账号和密码进行身份认证,此时如果服务器经过数据比对后判定你是一个合法的用户,那么为了让你后续在进行某些网页请求时不用重新输入账号和密码,此时服务器就会进行Set-Cookie的设置。(Set-Cookie也是HTTP报头当中的一种属性信息)

当认证通过并在服务端进行Set-Cookie设置后,服务器在对浏览器进行HTTP响应时就会将这个Set-Cookie响应给浏览器。而浏览器收到响应后会自动提取出Set-Cookie的值,将其保存在浏览器的cookie文件当中,此时就相当于我的账号和密码信息保存在本地浏览器的cookie文件当中。
在这里插入图片描述

从第一次登录认证之后,浏览器再向该网站发起的HTTP请求当中就会自动包含一个cookie字段,其中携带的就是我第一次的认证信息,此后对端服务器需要对你进行认证时就会直接提取出HTTP请求当中的cookie字段,而不会重新让你输入账号和密码了。

也就是在第一次认证登录后,后续所有的认证都变成了自动认证,这就叫做cookie技术。
内存级别&文件级别
cookie就是在浏览器当中的一个小文件,文件里记录的就是用户的私有信息。cookie文件可以分为两种,一种是内存级别的cookie文件,另一种是文件级别的cookie文件。

将浏览器关掉后再打开,访问之前登录过的网站,如果需要你重新输入账号和密码,说明你之前登录时浏览器当中保存的cookie信息是内存级别的。
将浏览器关掉甚至将电脑重启再打开,访问之前登录过的网站,如果不需要你重新输入账户和密码,说明你之前登录时浏览器当中保存的cookie信息是文件级别的。
cookie被盗
如果你浏览器当中保存的cookie信息被非法用户盗取了,那么此时这个非法用户就可以用你的cookie信息,以你的身份去访问你曾经访问过的网站,我们将这种现象称为cookie被盗取了。

比如你不小心点了某个链接,这个链接可能就是一个下载程序,当你点击之后它就会通过某种方式把程序下载到你本地,并且自动执行该程序,该程序会扫描你的浏览器当中的cookie目录,把所有的cookie信息通过网络的方式传送给恶意方,当恶意方拿到你的cookie信息后就可以拷贝到它的浏览器对应的cookie目录当中,然后以你的身份访问你曾经访问过的网站。
SessionID
单纯的使用cookie是非常不安全的,因为此时cookie文件当中就保存的是你的私密信息,一旦cookie文件泄漏你的隐私信息也就泄漏。

所以当前主流的服务器还引入了SessionID这样的概念,当我们第一次登录某个网站输入账号和密码后,服务器认证成功后还会服务端生成一个对应的SessionID,这个SessionID与用户信息是不相关的。系统会将所有登录用户的SessionID值统一维护起来。

此时当认证通过后服务端在对浏览器进行HTTP响应时,就会将这个生成的SessionID值响应给浏览器。浏览器收到响应后会自动提取出SessionID的值,将其保存在浏览器的cookie文件当中。后续访问该服务器时,对应的HTTP请求当中就会自动携带上这个SessionID。
在这里插入图片描述
而服务器识别到HTTP请求当中包含了SessionID,就会提取出这个SessionID,然后再到对应的集合当中进行对比,对比成功就说明这个用户是曾经登录过的,此时也就自动就认证成功了,然后就会正常处理你发来的请求,这就是我们当前主流的工作方式。
安全是相对的
引入SessionID之后,浏览器当中的cookie文件保存的是SessionID,此时这个cookie文件同样可能被盗取。此时用户的账号和密码虽然不会泄漏了,但用户对应的SessionID是会泄漏的,非法用户仍然可以盗取我的SessionID去访问我曾经访问过的服务器,相当于还是存在刚才的问题。

之前的工作方式就相当于把账号和密码信息在浏览器当中再保存一份,每次请求时都自动将账号和密码的信息携带上,但是账号和密码一直在网当中发送太不安全了。
因此现在的工作方式是,服务器只有在第一次认证的时候需要在网络中传输账号和密码,此后在网络上发送的都是SessionID。
这种方法虽然没有真正解决安全问题,但这种方法是相对安全的。互联网上是不存在绝对安全这样的概念的,任何安全都是相对的,就算你将发送到网络当中的信息进行加密,也有可能被别人破解。
不过在安全领域有一个准则:如果破解某个信息的成本已经远远大于破解之后获得的收益(说明做这个事是赔本的),那么就可以说这个信息是安全的。
引入SessionID后的好处
在引入SessionID之前,用户登录的账号信息都是保存在浏览器内部的,此时的账号信息是由客户端去维护的。
而引入SessionID后,用户登录的账号信息是有服务器去维护的,在浏览器内部保存的只是SessionID。
此时虽然SessionID可能被非法用户盗取,但服务器也可以使用各种各样的策略来保证用户账号的安全。

IP是有归类的,可以通过IP地址来判断登录用户所在的地址范围。如果一个账号在短时间内登录地址发送了巨大变化,此时服务器就会立马识别到这个账号发生异常了,进而在服务器当中清除对应的SessionID的值。这时当你或那个非法用户想要访问服务器时,就都需要重新输入账号和密码进行身份认证,而只有你是知道自己的密码的,当你重新认证登录后服务器就可以将另一方识别为非法用户,进而对该非法用户进行对应的黑名单/白名单认证。
当操作者想要进行某些高权限的操作时,会要求操作者再次输入账号和密码信息,再次确认身份。就算你的账号被非法用户盗取了,但非法用户在改你密码时需要输入旧密码,这是非法用户在短时间内无法做到的,因为它并不知道你的密码。这也就是为什么账号被盗后还可以找回来的原因,因为非法用户无法在短时间内修改你的账号密码,此时你就可以通过追回的方式让当前的SessionID失效,让使用该账号的用户进行重新登录认证。
SessionID也有过期策略,比如SessionID是一个小时内是有效的。所以即便你的SessionID被非法用户盗取了,也仅仅是在一个小时内有效,而且在功能上受约束,所以不会造成太大的影响。
任何事情都有两面性,如果不是这些非法用户的存在,现在的服务器肯定是漏洞百出,只有双方不断进行对抗双方才能不断进步。

HTTPS VS HTTP

早期很多公司刚起步的时候,使用的应用层协议都是HTTP,而HTTP无论是用GET方法还是POST方法传参,都是没有经过任何加密的,因此早期很多的信息都是可以通过抓包工具抓到的。

为了解决这个问题,于是出现了HTTPS协议,HTTPS实际就是在应用层和传输层协议之间加了一层加密层(SSL&TLS),这层加密层本身也是属于应用层的,它会对用户的个人信息进行各种程度的加密。HTTPS在交付数据时先把数据交给加密层,由加密层对数据加密后再交给传输层。

当然,通信双方使用的应用层协议必须是一样的,因此对端的应用层也必须使用HTTPS,当对端的传输层收到数据后,会先将数据交给加密层,由加密层对数据进行解密后再将数据交给应用层。
在这里插入图片描述
此时数据只有在用户层(应用层)是没有被加密的,而在应用层往下以及网络当中都是加密的,这就叫做HTTPS。

对称加密 VS 非对称加密

加密的方式可以分为对称加密和非对称加密:

采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密。
采用公钥和私钥来进行加密和解密,用其中一个密钥进行加密就必须用另一个密钥进行解密,这种加密方法称为非对称加密。
一般而言,对称加密的效率比非对称加密的效率高。
对称加密的过程
对称加密的效率更高,因此双方在进行正常通信时使用的是对称加密。但双方要进行对称加密通信,此时就要去加密方要把密钥给解密方,此时解密方才能对数据进行解密,但加密方把密钥(密钥也是数据)给解密方时也是需要对密钥进行加密的,解密废没法解密就没法拿到密钥,这就变成了鸡生蛋蛋生鸡的问题了。

这里也不可能让加密方给密钥的时候不进行加密,因为那样全世界都知道这个密钥了,后续加密方的加密也就没有意义了。因此刚开始在进行密钥协商的时候需要用非对称加密。

对称加密的建立过程如下:

通信双方建立连接的时候,双方就可以把支持的加密算法作协商,协商之后在服务器端形成非对称加密时使用的公钥和私钥,在客户端形成对称加密时使用的密钥。
然后服务器将公钥交给客户端(这个公钥全世界都可以看到),然后客户端用这个公钥对客户端形成的密钥进行加密,将加密后的密钥发送给服务器,服务器拿到后再用它的私钥进行解密,最终服务器就拿到了客户端的密钥。
这时客户端和服务器都有了这个密钥,并且其他人是不知道的,此时客户端和服务器就可以进行对称加密通信了。
需要注意的是,这里客户端用公钥加密后的密钥只有服务器能够解密,因为在非对称加密当中,数据用公钥加密就必须用私钥解密,而只有服务器才有私钥,其他人都只有公钥。

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

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

相关文章

Unity | YooAssetV2.1.0 + HybridCLR热更新

目录 一、项目更改 二、使用YooAsset热更 1.资源配置 2.资源构建 3.将两个文件夹下的资源上传CDN服务器 4.修改代码 5.运行效果 本文记录利用YooAssetHybridCLR来进行资源和dll的更新。YooAsset使用的是新版V2.1.0。相比于旧版&#xff0c;dll(原生文件)和资源要建两个p…

zabbix添加主机

zabbix添加主机 查看ip地址 [rootershi ~]# yum -y install net-tools [rootershi ~]# ifconfig eth0 |grep netmask |cut -d " " -f 10 192.168.88.20被监控主机安装zabbix-agent [root20 ~]# mount /dev/cdrom /mnt [root20 ~]# yum -y install wget [root20 ~]…

conda虚拟环境基础

【一文搞定最新版Anaconda】Win11 安装 Anaconda&#xff08;2023.9&#xff09;详解&#xff08;不删除旧版情况下下载、安装、注册、登录、设置环境变量、迁移旧环境、配置修改换源等&#xff09;连接Pycharm_win11安装anaconda-CSDN博客 conda命令大全&#xff08;create/in…

消息总线在微服务中的应用

直连式配置中心 上一篇文章介绍了 Spring Cloud 中的分布式配置组件 Config&#xff0c;每个服务节点可以从Config Server 拉取外部配置信息。但是似乎还有一个悬而未决的问题&#xff0c;那就是当服务节点数量非常庞大的时候&#xff0c;我们不可能一台一台服务器挨个去手工触…

2024Node.js零基础教程(小白友好型),nodejs新手到高手,(四)NodeJS入门——网络基础概念

041_网络基础概念_IP的介绍 hello&#xff0c;大家好&#xff0c;我们来一起认识一下IP。 在开始介绍 IP 之前&#xff0c;我们首先来介绍一个场景&#xff0c;方便大家去理解 IP 这个概念。比如这会儿强哥正在成都&#xff0c;然后还有另外一个小伙伴&#xff0c;谁呢&#x…

CodeFuse成功支持通义千问算法大赛,评测方案已开源

前段时间&#xff0c; 首届通义千问AI挑战赛成功举办&#xff0c;CodeFuse 为大赛提供技术支持&#xff0c;模型微调框架 MFTCoder 和 CodeFuseEval 评测框架为大赛保驾护航&#xff0c;助力大赛圆满完成。我们基于leetcode 阿里和蚂蚁最新面试题库建设了“模型赛马”在线打榜的…

25.云原生之ArgoCD-app of apps模式

文章目录 app of apps 模式介绍app如何管理apphelm方式管理kustomize方式管理 app of apps 模式介绍 通过一个app来管理其他app&#xff0c;当有多个项目要发布创建多个app比较麻烦&#xff0c;此时可以创建一个管理app&#xff0c;管理app创建后会创建其他app。比较适合项目环…

Ansible基础及常用模块

目录 1.前言 Ansible Ansible的特性 2.ansible环境安装部署 管理端安装ansible(192.168.88.22) ansible目录结构 配置主机清单 配置密钥对验证 3.ansible命令行模块 command 模块 shell 模块 ​编辑cron 模块 user 模块 group 模块 copy 模块 file 模块 hostn…

爱上算法:每日算法(24-2月2号)

&#x1f31f;坚持每日刷算法&#xff0c;将其变为习惯&#x1f91b; 题目链接&#xff1a;101. 对称二叉树 最开始肯定是比较简单的想法&#xff0c;就是遍历左右节点呀&#xff0c;不相等我就直接返回false。 但是这样错了&#xff0c;我们要的是以根节点为轴&#xff0c;而…

如何保证MySQL和Redis中的数据一致性?

文章目录 前言一、缓存案例1.1 缓存常见用法1.2 缓存不一致产生的原因 二、解决方案2.1 先删除缓存&#xff0c;再更新数据库2.2 先更新数据库&#xff0c;删除缓存2.3 只更新缓存&#xff0c;由缓存自己同步更新数据库2.4 只更新缓存&#xff0c;由缓存自己异步更新数据库2.5 …

Unity_使用Shader实现玻璃和镜面效果

效果图如下&#xff1a; 玻璃效果图 镜面效果图 Step1 搭建场景→镜子使用Quad代替&#xff0c;放置在需要反射的墙面→创建新的材质和Shader Step2 墙壁外创建Camera&#xff0c;用来渲染物体后方的视图→创建RenderTexture&#xff0c;赋于该相机 Step3 Shader的编写如下…

如何使用本地私有NuGet服务器

写在前面 上一篇介绍了如何在本地搭建一个NuGet服务器&#xff0c; 本文将介绍如何使用本地私有NuGet服务器。 操作步骤 1.新建一个.Net类库项目 2.打包类库 操作后会生成一个.nupkg文件&#xff0c;当然也可以用dotnet pack命令来执行打包。 3.推送至本地NuGet服务器 打开命…

来看看Tomcat和Web应用的目录结构

在前面两篇大致了解了Tomcat的架构和运行流程&#xff0c;以及Tomcat应用中的web.xml。 聊一聊Tomcat的架构和运行流程&#xff0c;尽量通俗易懂一点-CSDN博客 来吧&#xff0c;好好理解一下Tomcat下的web.xml-CSDN博客 那接下来&#xff0c;再看看Tomcat的目录&#xff0c;…

el-table点击某一行选中改变背景色且执行方法

elementUI table表格点击某一行选中并且改变背景色 使用:row-style"rowStyle"及row-click“selectRow”&#xff1a; 其中 selectRow 方法中&#xff1a; row 输出&#xff1a;当前行的内容 column 输出&#xff1a;当前列的信息 event 输出&#xff1a;当前事件 …

Unknown custom element:<xxx>-did you register the component correctly解决方案

如图所示控制台发现了爆红&#xff08;大哭&#xff09;&#xff1a; 报错解释&#xff1a; 当我们看到报错时&#xff0c;我们需要看到一些关键词&#xff0c;比如显眼的“component”和“name”这两个单词&#xff0c; 因此我们就从此处切入&#xff0c;大概与组件有关系。…

洛谷 B3635 硬币问题(DP入门)

[题目概述] 今有面值为 1、5、11 元的硬币各无限枚。 想要凑出 n 元&#xff0c;问需要的最少硬币数量。 输入格式 仅一行&#xff0c;一个正整数 n。 输出格式 仅一行&#xff0c;一个正整数&#xff0c;表示需要的硬币个数。 输入 15输出 3输入 12输出 2样例解释 …

深度学习入门笔记(二)神经元的结构

神经网络的基本单元是神经元&#xff0c;本节我们介绍神经元的结构。 2.1 神经元 一个神经元是由下面 5 部分组成的&#xff1a; 输入&#xff1a;x1,x2,…,xk。权重&#xff1a;w1,w2,…,wk。权重的个数与神经元输入的个数相同。偏移项&#xff1a;可省略。激活函数&#…

Hadoop:HDFS学习巩固——基础习题及编程实战

一 HDFS 选择题 1.对HDFS通信协议的理解错误的是&#xff1f; A.客户端与数据节点的交互是通过RPC&#xff08;Remote Procedure Call&#xff09;来实现的 B.HDFS通信协议都是构建在IoT协议基础之上的 C.名称节点和数据节点之间则使用数据节点协议进行交互 D.客户端通过一…

【Vue】指令之内容绑定,事件绑定

Vue指令[1] 内容绑定&#xff0c;事件绑定v-test指令v-html指令v-on基础 内容绑定&#xff0c;事件绑定 v-test指令 作用&#xff1a;设置标签的文本值&#xff08;textContent&#xff09; 默认写法会替换全部内容&#xff0c;使用差值表达式可以替换指定内容内部支持写表达…

Nicn的刷题日常之打印菱形

目录 1.题目描述 2.解题思路 3.解题 1.题目描述 用C语言在屏幕上输出以下图案&#xff1a; 2.解题思路 仔细观察图形&#xff0c;可以发现&#xff0c;此图形中是由空格和*按照不同个数的输出组成的。 上三角&#xff1a;先输出空格&#xff0c;后输出*&#xff0c;每…
最新文章