HTTP和HTTPS协议

news2024/11/24 8:37:44

HTTP协议

HTTP协议是一种应用层的协议,全称为超文本传输协议。

URL

URL值统一资源定位标志,也就是俗称的网址。

image-20230110214931586

协议方案名

http://表示的就是协议方案名,常用的协议有HTTP协议、HTTPS协议、FTP协议等。HTTPS协议是以HTTP协议为基础,通过传输加密和身份认证保证了传输过程的安全性。

登录信息

user:pass表示登录认证信息。绝大多数情况下,该字段是被省略。一般通过登录窗口的方式让用户输入。比如gitee的登录窗口:

image-20230110215925447

服务器地址

服务器地址也叫做域名。在进行网络访问时,网络地址通过DNS域名解析转换为标识唯一主机的IP地址。比如使用ping命令访问百度和京东的官网,最后会被转换为ip地址:

image-20230110220631900

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

服务器端口

一般0-1023号端口已经被一些特定的服务占有。比如HTTP协议默认的端口是80,HTTPS默认的端口是443。

image-20230110221922091

带层次的文件路径

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

这里的’/'并不是指根目录,而是指web更目录。具体的信息将在后面解释。

查询字符串

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

比如我们查询晓歌的灯如昼新皮肤的信息:

image-20230110224024344

在上面的URL中,存在wd这个字段。这个字段也就是我们想要查询的关键字。

片段标识符

片段标识符是对资源的补充

urlencode和urldecode

像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现。
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义 。

比如我们搜索C++关键字:

image-20230110224954597

转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。

在线编码工具

市面上存在很多免费的在线编码工具:https://tool.chinaz.com/Tools/urlencode.aspx

比如输入C++并进行编码,就可以得到C++对应的编码为C%2B%2B

image-20230110225348673

比如输入C%2B%2B进行解密,就可以得到对应的解码为C++

image-20230110225610116

HTTP协议的格式

HTTP协议能够干什么?

HTTP协议是向特定的服务器申请特定的资源,并获取到本地的协议。

image-20230110230350911

通过wget命令申请百度首页的资源。并得到一个html文件到本地。我们将html中的内容在浏览器中打开:

image-20230110230623529

因此成功获取百度首页的静态资源。

HTTP协议的请求格式

HTTP请求格式可以分为四个部分,格式如下:

image-20230110232642106

请求格式包含以下四个部分:

  • 请求行:请求方法+url(文件路径格式)+http版本
  • 请求报头:请求的属性,这些属性都是以key: value的形式按行陈列的。
  • 空行:作为请求报头和请求正文的的分割线
  • 请求正文:请求正文允许为空字符串,如果请求正文存在,则在请求报头中会有一个Content-Length属性来标识请求正文的长度。

HTTP如何保证自己的报头和有效载荷全部被读取?

  • 读取完整的报头:逐行读取,直到读取到空行。
  • 读取完整的正文:在报头中一定存在一个关于key:value保存正文长度的属性。

获取HTTP请求

HTTP协议的底层通常使用的传输层协议是TCP协议,因此可以通过一个TCP服务器获取HTTP请求。

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(8888);
	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 << "--------------------------request begin--------------------------" << endl;
			cout << buffer << endl;
			cout << "---------------------------request end---------------------------" << endl;
			
			close(sock);
			exit(0);
		}
		//爷爷进程
		close(sock);
		waitpid(-1, nullptr, 0); //等待爸爸进程
	}
	return 0;
}

上面的服务器通过监听8888端口获取HTTP请求信息。

image-20230111001007192

说明:

  • 浏览器向服务器发起HTTP请求后,由于服务器没有对其进行响应,此时浏览器就会认为服务器没有收到请求,然后再不断发起新的HTTP请求。因此虽然我们只用浏览器访问了一次,但会受到多次HTTP请求。
  • 由于浏览器发起请求时默认用的就是HTTP协议,因此我们在浏览器的url框当中输入网址时可以不用指明HTTP协议。
  • 这里URL中的/并不是指云服务器的根目录,而是web根目录。web根目录可以由自己指定。

下面访问其他路径下的资源:

image-20230111001926148

当访问的资源发生变化时,请求头中的URL也跟着改变。

HTTP的响应格式

HTTP响应格式可以分为四个部分,格式如下:

image-20230111105736510

响应格式包含以下四个部分:

  • 状态行:HTTP版本+状态码+状态码描述符
  • 响应报头:响应的属性,以key: value键值对的形式按行陈列的。
  • 空行:响应报头和响应正文的分割线
  • 响应正文:响应正文允许为空字符串,如果响应正文存在,则响应报头中会有一个Content-Length属性来标识响应正文的长度。

比如我们访问百度搜索晓歌的信息:

image-20230111111323119

模拟HTTP的响应

下面我们在服务器中构建HTTP响应:当浏览器发送请求时,在网页上显示accept your request

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(8888);
	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 << "--------------------------request begin--------------------------" << endl;
			cout << buffer << endl;
			cout << "---------------------------request end---------------------------" << endl;
			//构建HTTP响应
			string response="http/1.1 200 ok\r\n";
			string world="accept your request";
			response+=("Content-Length: "+ to_string(world.size()) + "\r\n");
			response+="\r\n";
			response+=world;
			send(sock,response.c_str(),response.size(),0);
			close(sock);
			exit(0);
		}
		//爷爷进程
		close(sock);
		waitpid(-1, nullptr, 0); //等待爸爸进程
	}
	return 0;
}

image-20230111114010827

在实际的使用中,难道每一个请求都需要程序员去构造响应正文?实际上,HTTP请求的请求行中存在URL,URL就是请求需要访问资源的存放地址。

响应正文存放在哪?

在URL中,存在一个带层次的文件路径:(比如) /dir/index.html。所以可以从请求行的第二个字段中获取资源路径。

GET /dir/index.html http/1.0

下面的函数实现了从请求行提取读取URL路径:

#define CRLF "\r\n"
#define SPACE " "
#define SPACELEN strlen(SPACE)
#define ROOT_PATH "wwwpath"
#define HOME_PAGE  "index.html"
string readURL(string buffer){
    //读取第一行
    size_t pos=buffer.find(CRLF);
    if(pos==std::string::npos)  return "";
    string firstline=buffer.substr(0,pos);
    size_t first=firstline.find(SPACE);
    if(first==std::string::npos) return "";
    size_t second=firstline.rfind(SPACE);
    if(second==std::string::npos) return "";
    string URL=buffer.substr(first+SPACELEN,second-SPACELEN-first);
    if(URL.size()==1&&URL[0]=='/')
    {
        URL+=HOME_PAGE;
    }
    return URL;
}

image-20230111152720336

注意:

对于访问web根目录/,需要进行特殊处理。一般默认为web根目录下的index.html文件。

实验

当我们访问对应URL路径在资源时,便打开对应文件夹,并添加到HTTP响应正文并返回。

string readFile(const string& filepath)
{
    std::ifstream in(filepath,std::ifstream::binary);
    if(!in.is_open())   return "404";
    std::string content;
    std::string line;
    while (getline(in,line))
    {
        content+=line;
    }
    cout<<content<<endl;
    in.close();
    return content;
}

假设我们将当前目录设置为根目录,并创建一个index.html文件。

<html>
    <head></head>
    <body>
        <h1>Hello webroot</h1>
    </body>
</html>

并创建文件夹a/b/c,在该文件夹下创建一个d.html文件

<html>
    <head></head>
    <body>
        <h1>Hello d.html</h1>
    </body>
</html>

image-20230111154349569

访问web根目录结果:

image-20230111161202054

访问/a/b/c/d.html资源结果:

image-20230111161250298

完整的主程序

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请求
			//读取URL
			string URL=readURL(buffer);
			cout<<"URL:"<<URL<<endl;
			//拼接路径
			string filepath=ROOT_PATH+URL;
			cout<<filepath<<endl;
			//构建HTTP响应
			string response="http/1.1 200 ok\r\n";
			string world=readFile(filepath);
			response+=("Content-Length: "+ to_string(world.size()) + "\r\n");
			response+="\r\n";
			response+=world;
			send(sock,response.c_str(),response.size(),0);
			close(sock);
			exit(0);
			
			close(sock);
			exit(0);
		}
		//爷爷进程
		close(sock);
		waitpid(-1, nullptr, 0); //等待爸爸进程
	}
	return 0;
}

POST和GET方法

GET方法

网络行文无非有两种:

  • 把远端的资源拿到本地:GET
  • 将自己的属性提交到远端:POST或者GET方法。

我们在web根目录下的index.html文件中添加一个表单用于比较两者的区别:

<html>
    <head></head>
    <body>
        <h1>Hello webroot</h1>
        <form action="/a/b/c/d.html" method="get">
            Username: <input type="text" name="user"><br>
            Password: <input type="password" name="passwd"><br>
            <input type="submit" value="Submit">    
        </form>
    </body>
</html>

image-20230111162531987

得到结果为:

image-20230111162617033

观察URL的变化。提交的user和passwd以明文的方式出现在URL中。

image-20230111164534253

这也是GET方法的特点:把参数以明文的方式按照Key:value格式拼接到URL后面。

POST方法

将表单的方法修改为POST。

<html>
    <head></head>
    <body>
        <h1>Hello webroot</h1>
        <form action="/a/b/c/d.html" method="post">
            Username: <input type="text" name="user"><br>
            Password: <input type="password" name="passwd"><br>
            <input type="submit" value="Submit">    
        </form>
    </body>
</html>

再次访问并提交用户密码:

image-20230111163354115

这次并没有将参数添加到URL中,观察HTTP请求,可以看到user和password参数都出现在HTTP请求正文中。

image-20230111163546472

POST和GET方法的区别

  • GET方法以URL传参

  • POST通过HTTP请求正文传参

  • GET传参的方式不私密。

    注意:一定不是不安全,因为GET方法和POST方法通过代理服务器或者抓包等方法都可以获取对应的参数,要想实现数据安全,就需要对传输的数据进行加密。比如HTTPS协议

  • GET通过URL传参,URL有长度的限制,所以数据量较大的参数都会通过POST方法传递。

  • URL是文本类,没有严格意义上的数据类型。而请求正文有。

HTTP其他的请求方法

image-20230111164112863

HTTP状态码

image-20230111164144792

最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

常见的状态码

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

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

image-20230115181547832

临时重定向实验

下面我们实现:访问我们的服务时,会跳转到B站的首页。

在实现的过程中,将HTTP的响应码设置为302,还需要在HTTP响应报头当中添加Location字段,这个Location后面跟的就是你需要重定向到的网页,比如我们这里将其设置为CSDN的首页。

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<<"------------------------request begin----------------------"<<endl;
			cout<<buffer<<endl;
			cout<<"-------------------------request end-----------------------"<<endl;
			string response="http/1.1 302 Temporary Redirect/\r\n";
			response+="Location: https://www.bilibili.com/\r\n";
			response+="\r\n";
			send(sock,response.c_str(),response.size(),0);
			close(sock);
			exit(0);
			close(sock);
			exit(0);
		}
		//爷爷进程
		close(sock);
		waitpid(-1, nullptr, 0); //等待爸爸进程
	}
	return 0;
}

使用telnet命令只是接收到了服务器发送的HTTP响应,并没有实现重定向功能。实际上的重定向功能是由浏览器实现完成。

image-20230115183555761

下面我们用浏览器访问该网址:

image-20230115202447347

回车发送请求:重定向到B站官网

image-20230115202519014

临时重定向和永久重定向的区别

  • 两者的区别主要体现在用户体验方面
  • 临时重定向主要用于网站维护、网站服务升级等。服务升级结束和维护成功后,原网址依然可以被使用。临时重定向不需要客户记住新的网址。
  • 永久重定向,比如用于使用新的网址,原网址被废弃的情况。永久重定向需要用户记住新的网址。

HTTP常见的Header

  • Content-Type: 数据类型(text/html等)
  • Content-Length: Body的长度
  • Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
  • User-Agent: 声明用户的操作系统和浏览器版本信息;
  • referer: 当前页面是从哪个页面跳转过来的;
  • location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
  • Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;

Cookie

HTTP协议的特点之一:无状态。对于用户的状态,HTTP协议不会记录用户的信息和行为。

而Cookie是实现HTTP状态化的一种手段。比如实现网址的无登陆访问,VIP访问VIP资源。(回忆下面的场景:我们在使用B站时,第一次需要我们输入账号密码登录。后续再使用时,我们可以直接进入B站而不需要登录。这就是HTTP状态化的一种手段;

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

Cookie实验一

此时已经登录了账号。点击网址前面的小锁,可以看到网页的Cookie。

image-20230115203857379

下面我们将关于B站的Cookie全部删除,再访问B站。

image-20230115204259588

回车访问B站,可以看到需要我们重新登录。

image-20230115204419623

Cookie的原理

在我们第一次登录输入账号和密码进行身份认证时,如果认证成功,服务端就会向客户端发送对应的响应,其中就包含Set-Cookie字段(Set-Cookie也是HTTP报头当中的一个字段)。该字段通知客户端设置Cookie。输入的账号和密码就保存在本地浏览器Cookie文件当中。

image-20230115205642054

后续再次访问相同的网址时,浏览器发送的HTTP请求当中就会包含一个Cookie信息。服务端在需要认证时会提取Cookie当中账号和密码。

Cookie文件:内存级和磁盘级别

cookie就是在浏览器当中的一个小文件,文件里记录的就是用户的私有信息。cookie文件可以分为两种:一种是内存级别的cookie文件,另一种是磁盘文件级别的cookie文件。

  • 浏览器关闭后再打开,访问之前的网站,需要输入账号和密码。此时浏览器保存的是内存级别的Cookie文件
  • 浏览器关闭再打开,甚至重启电脑。访问之前的网站,不需要输入账号和密码。浏览器保存的是磁盘文件级别的Cookie文件。

设置Cookie

设置Cookie可以在HTTP响应中添加一个Set-Cookie字段。(Set-Cookie:Cookie信息)

设置Cookie实验

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<<"------------------------request begin----------------------"<<endl;
			cout<<buffer<<endl;
			cout<<"-------------------------request end-----------------------"<<endl;
			//读取URL
			string URL=readURL(buffer);
			//拼接路径
			string filepath=ROOT_PATH+URL;
			//构建HTTP响应
			string response="http/1.1 200 ok/\r\n";
			response+="Set-Cookie:this is my Cookie content\r\n";
			string world=readFile(filepath);
			response+=("Content-Length: "+ to_string(world.size()) + "\r\n");
			response+="\r\n";
			response+=world;
			send(sock,response.c_str(),response.size(),0);
			close(sock);
			exit(0);
			
			close(sock);
			exit(0);
		}
		//爷爷进程
		close(sock);
		waitpid(-1, nullptr, 0); //等待爸爸进程
	}
	return 0;
}

image-20230115211724760

Session

单纯的使用Cookie是不安全的。cookie文件当中就保存的是你的私密信息,一旦cookie文件泄漏你的隐私信息也就泄漏。

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

此时当认证通过后服务端在对浏览器进行HTTP响应时,就会将这个生成的SessionID值响应给浏览器。浏览器收到响应后会自动提取出SessionID的值,将其保存在浏览器的cookie文件当中。后续访问该服务器时,对应的HTTP请求当中就会自动携带上这个SessionID。
image-20230115223352511

服务端会根据session_id找到对应的Session文件。再提取session文件中的私密信息。

Cookie+Session的安全保证:着手于Session_id的加密方式。比如用户的IP地址等。

Connection字段

HTTP有两种连接方式:长连接和短链接。

  • Connection:keep_alive
  • Connection:closed

HTTP的长连接和短链接本质上是TCP的长连接和短链接。

HTTP短连接

在HTTP/1.0中,默认使⽤的是短连接。也就是说,浏览器和服务器每进⾏⼀次HTTP操作,就建⽴⼀次连接,但任务结束就中断连接。

随着网页信息不断的增大,需要传输的数据量也不断增加。如果客户端访问的某个HTML或其他类型的web页中包含有其他的web资源,如JavaScript⽂件、图像⽂件、CSS⽂件等,当浏览器每遇到这样⼀个web资源,就会建⽴⼀个HTTP会话。如果建立多个HTTP短连接传输数据,**传输层会不断的进行三次握手和四次挥手,**过于浪费网络资源。

因此HTTP长连接诞生。

HTTP长连接

但从HTTP/1.1起,默认使⽤长连接,⽤以保持连接特性。使⽤长连接的HTTP协议,会在请求头和响应头加⼊这⾏代码。Connection: keep-alive。

在使⽤长连接的情况下,当⼀个⽹页打开完成后,客户端和服务器之间⽤于传输HTTP数据的TCP链接不会关闭,如果客户端再次访问这个服务器上的⽹页,会继续使⽤这⼀条已经建⽴的连接(HTTP长连接利⽤同⼀个TCP连接处理多个HTTP请求和响应)。

Keep-Alive不会永久保持连接,它有⼀个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接要客户端和服务端都⽀持长连接。长连接中关闭连接通过Connection:closed头部字段。如果请求或响应中的Connection被指定为closed,表⽰在当前请求或相应完成后将关闭TCP连接。

TCP的keep-alive是检查当前TCP连接是否活着;HTTP的Keep-Alive是要让⼀个TCP连接活久点。

长连接如何保证读取完整的信息?

HTTP报头中存在Conten-Length字段。通过控制读取的长度判断是否读取完整的信息。

如何保证长连接的响应顺序?

pipeline技术。

HTTP VS HTTPS

HTTPS协议也是⼀个应⽤层协议。是在HTTP协议的基础上引⼊了⼀个加密层。这场加密层也输入应用层,他会对用户传输的信息进行加密。

image-20230116000958994

而在用户和服务端使用时,对应拿到的都是明文数据。

为什么要加密?

QQ浏览器的运营商劫持例子。

下载天天动听,在未被QQ浏览器劫持的情况:

image-20230116002252766

已被劫持的效果,点击下载按钮,就会弹出QQ浏览器的下载链接:

image-20230116002324139

通过劫持天天动听的下载链接从而达到推广产品的目的。

image-20230116002415626

http的内容是明⽂传输的,明⽂数据会经过路由器、wifi热点、通信服务运营商、代理服务
器等多个物理节点,如果信息在传输过程中被劫持,传输的内容就完全暴露了。劫持者还可以篡改传输的信息且不被双⽅察觉,这就是中间⼈攻击 ,所以我们才需要对信息进⾏加密。

对称加密和非对称加密

对称加密

  • 采⽤单钥密码系统的加密⽅法,同⼀个密钥可以同时⽤作信息的加密和解密,这种加密⽅法称为对称加密,也称为单密钥加密,特征:加密和解密所⽤的密钥是相同的
  • 常⻅对称加密算法:DES、3DES、AES、TDEA、Blowfish、RC2等
  • 特点:算法公开、计算量⼩、加密速度快、加密效率⾼

最简单的对称加密算法:异或加密

假设明⽂a=1234,密钥key=8888。则加密akey得到的密⽂b为9834。然后针对密⽂9834再次进⾏运算bkey,得到的就是原来的明⽂1234。

image-20230116003026920

非对称加密

  • 需要两个密钥来进⾏加密和解密,这两个密钥是公开密钥(publickey,简称公钥)和私有密钥(private key,简称私钥)。
  • 常见非对称加密算法:RSA,DSA,ECDSA
  • 特点:算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,⽽使得加密解密速度没有对称加密解密的速度快

非对称加密要⽤到两个密钥,⼀个叫做"公钥",⼀个叫做"私钥"。公钥和私钥是配对的。非对称加密最⼤的缺点就是运算速度⾮常慢,比对称加密要慢很多。

  • 通过公钥对明⽂加密,变成密⽂
  • 通过私钥对密⽂解密,变成明⽂

也可以反着⽤
• 通过私钥对明⽂加密,变成密⽂
• 通过公钥对密⽂解密,变成明⽂

对称加密和非对称加密联合使用

对称加密的效率更高,因此双方在进行正常通信时使用的是对称加密。

对称加密的过程

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

数据摘要和数据指纹

  • 数字指纹(数据摘要),其**基本原理是利⽤单向散列函数(Hash函数)**对信息进⾏运算,⽣成⼀串固定⻓度的数字摘要。数字指纹并不是⼀种加密机制,但可以⽤来判断数据有没有被窜改。
  • 摘要常⻅算法:有MD5、SHA1、SHA256、SHA512等。
  • 摘要特征:和加密算法的区别是,摘要严格意义不是加密,因为没有解密,只不过从摘要很难反推原信息,通常⽤来进⾏数据对比(哈希函数是不可逆的,所以无法从数据摘要反推数据全文,因此数据摘要常用来数据的对比)。

云盘的秒传功能

一些云盘实现了一秒上传文件的功能。其原理就使用了数据摘要。

比如用户要上传一部电影,云盘接收到请求后,先对电影数据进行数据摘要得到摘要信息。此时服务端会对该数据摘要和其他用户已经上传的文件的数据摘要进行对比。如果存在用户想要上传的文件,就建立软连接,指向原来用户上传好的相同文件。

image-20230116005350962

HTTPS的工作过程加密

既然要保证数据安全,就需要进行加密。

方案一:只使用对称加密

image-20230208183613947

服务器同⼀时刻其实是给很多客户端提供服务的。每个客⼾端,每个⼈⽤的秘钥都必须是不同的(如果是相同那密钥,⿊客就也能拿到了)。因此服务器就需要维护每个客户端和每个密钥之间的关联关系 。

而同时维护多个密钥,服务端的压力就比较大。因此理想的做法是所有的客户端使用同一个密钥。

image-20230208184010041

如果直接把密钥明⽂传输,那么⿊客也就能获得密钥了。因此就需要对密钥进行加密。但是要想对密钥进⾏对称加密,就仍然需要先协商确定⼀个"密钥的密钥"。这就成了"先有鸡还是先有蛋"的问题。

方案二:只使用非对称加密

image-20230208231601940

由于服务端在传输公钥时没有进行加密,因此所有人都可以得到公钥。因此服务端发送消息可能被中间人劫持。服务端到客户端的信息传输是不安全的。

方案三:双方都使用非对称加密

  • 1.服务端拥有公钥S与对应的私钥S’,客户端拥有公钥C与对应的私钥C’
  • 2.客户和服务端交换公钥
  • 3.客⼾端给服务端发信息:先⽤S对数据加密,再发送,只能由服务器解密,因为只有服务器有私钥S’
  • 4.服务端给客户端端发信息:先⽤C对数据加密,在发送,只能由客户端解密,因为只有客户端有私钥C’

image-20230208232821852

使用2对非对称密钥可以实现客户端和服务端的信息安全。缺点:效率太低并且依然有安全问题(存在中间人攻击)

方案四:使用对称加密+非对称加密

  • 服务端具有⾮对称公钥S和私钥S’
  • 客⼾端发起https请求,获取服务端公钥S
  • 客⼾端在本地⽣成对称密钥C,通过公钥S加密,发送给服务器。
  • 这样客户端和服务端都拥有密钥C,可以进行通信。

image-20230208233802488

该方案相对于方案三解决了效率低的问题。但是依然存在安全问题:中间人攻击

中间人攻击

在⽅案2/3/4中,客⼾端获取到公钥S之后,对客⼾端形成的对称秘钥X⽤服务端给客⼾端的公钥
S进⾏加密,中间⼈即使窃取到了数据,此时中间⼈确实⽆法解出客⼾端形成的密钥X,因为只有服务器有私钥S’。但是中间⼈的攻击,如果在最开始握⼿协商的时候就进⾏了,就会发生安全问题。

以方案四为例

假设黑客一开始就成为了中间人。

  • 服务器具有⾮对称加密算法的公钥S,私钥S’
  • 中间⼈具有⾮对称加密算法的公钥M,私钥M’
  • 客⼾端向服务器发起请求,服务器明⽂传送公钥S给客⼾端
  • 中间⼈劫持数据报⽂,提取公钥S并保存好,然后将被劫持报⽂中的公钥S替换成为⾃⼰的公钥M,并将伪造报⽂发给客⼾端
  • 客⼾端收到报⽂,提取公钥M(⾃⼰当然不知道公钥被更换过了),⾃⼰形成对称秘钥X,⽤公钥M加密X,形成报⽂发送给服务器
  • 中间⼈劫持后,直接⽤⾃⼰的私钥M’进⾏解密,得到通信秘钥X,再⽤曾经保存的服务端公钥S加密后,将报⽂推送给服务器
  • 服务器拿到报⽂,⽤⾃⼰的私钥S’解密,得到通信秘钥X
  • 客户端和服务端双方开始采⽤X进⾏对称加密,进⾏通信。但⼀切都在中间⼈的掌握中,劫持数据,进⾏窃听甚⾄修改,都是可以的

image-20230209001204192

双方通信的密钥X被中间人掌握。

问题的本质是:客⼾端⽆法确定收到的含有公钥的数据报⽂,就是⽬标服务器发送过来的

证书

为了应对中间人攻击,网络通信又引入了证书。

CA证书

服务端在使⽤HTTPS前,需要向CA机构申领⼀份数字证书,数字证书⾥含有证书申请者信息、公钥信息等。服务器把证书传输给浏览器,浏览器从证书⾥获取公钥就可。

image-20230209001626899

证书包含以下的信息(证书明文和数据签名):

  • 证书颁发的机构
  • 证书有效期
  • 公钥B
  • 证书持有者
  • 数据签名

在申请证书时,需要在特定的权威平台生成,并且会产生一对公钥B和私钥B’。公钥会附加在证书上,私钥被CA机构和服务端保存。这对密钥在网络通信中进行明文加密和数字签名

数据签名

签名的形成是基于⾮对称加密算法的。数据签名是由数据摘要通过私钥加密得到。

image-20230209002931612

**证书包括证书明文和数据签名两个部分。**数据签名部分是由证书明文通过摘要算法形成数据摘要,数据摘要被再CA公司的私钥A加密得到。

image-20230209003620640

  • CA机构拥有⾮对称加密的私钥A和公钥A’
  • CA机构对服务端申请的证书明⽂数据进⾏hash,形成数据摘要
  • 然后对数据摘要⽤CA私钥A’加密,得到数字签名S。
  • 数据签名附加在证书明文上形成证书。

服务端申请的证书明⽂和数字签名S共同组成了数字证书,这样⼀份数字证书就可以颁发给服务端了。

注意:上述过程中一共出现了两对密钥。

一对是服务端申请的密钥,也就是CA证书上公钥B对应的密钥。这对密钥的作用是:在网络通信中对数据信息进行加密。且被服务端和CA公司都持有。

一对是CA公司持有的密钥,也就是对证书明文的数据摘要进行加密的私钥A’对应的密钥。该对密钥的作用是:形成数据签名并被用于检查证书的合法性。只被CA公司持有私钥,公钥被嵌入到操作系统中。

方案五:非对称加密+对称加密+证书

首先需要明确:CA公司的公钥被嵌入到了操作系统中。

在使用方案五时。证书上已经包含了服务端进行通信的公钥B。服务端与客户端要进行通信时,服务端先将证书发送给客户端,客户端检查证书的合法性(检查是否被篡改),并提取公钥B。

随后客户端使用B加密客户端形成的对称密钥X,发送给客户端。客户端使用私钥B’解密得到对称密钥X。

image-20230209005542819

如何检查证书的合法性?

证书包含证书明文和数据签名两部分。数据签名被CA机构的密钥A’加密无法被更改。

如果有第三方修改了证书明文,比如证书上用于通信的公钥。客户端在拿到服务端发送的证书后,会使用被嵌入到操作系统的CA机构公钥A进行解密得到数据摘要。再将证书明文通过摘要算法形成数据摘要。

如果两个摘要相同,证书就合法。否则,证书被修改,不合法。

以证书明文数据为hello为例:

假设我们的证书只是⼀个简单的字符串hello,对这个字符串计算hash值(⽐如md5),结果为
BC4B2A76B9719D91

image-20230209010659125

如果相同表示证书是合法的。

如果⿊客把hello篡改为了hella。

image-20230209011348203

客户端通过比较两个数据摘要发现不同,说明证书内容被篡改,证书不合法。客户端停止对服务端发送消息。

查看浏览器的受信任证书发布机构

打开浏览器,点击右上⻆的设置

image-20230209012531854

找到”隐私设置和安全性“里面的安全属性:

image-20230209012725267

往下翻可以看到相关的字段:

image-20230209012800188

总结

下面是HTTPS通信的完整流程:

image-20230209011543948

HTTPS工作流程一共包含三组密钥:

第一组(非对称加密):用于形成数据签名和校验证书是否被篡改。CA机构和服务端持有私钥,公钥被嵌入到操作系统中(操作系统包含了可信任的CA认证机构有哪些,同时持有对应的公钥) 。服务器在客⼾端请求时,返回携带签名的证书。客⼾端通过这个公钥进⾏证书验证,保证证书的合法性,进⼀步保证证书中携带的服务端公钥权威性。

第二组(非对称加密):用于客户端和服务端协商对称加密的密钥。服务端生成公钥和私钥。公钥被填写到CA证书上。客⼾端⽤收到的CA证书中的公钥(是可被信任的)给随机⽣成的对称加密的密钥加密,传输给服务器,服务器通过私钥解密获取到对称加密密钥。

第三组(对称加密):客户端生成。用于服务端和客户端进行加密通信。

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

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

相关文章

java面经汇总

Java基础 什么是字节码&#xff1f; jvm可以理解的代码&#xff08;.class文件&#xff09; Java代码从源代码到运行过程&#xff1a; java代码 -> javac编译器->.class字节码文件 -> 解释器&JIT&#xff08;运行时编译器&#xff09;->机器码 JIT编译器会将热…

球面坐标系下的三重积分

涉及知识点 三重积分球面坐标系点火公式一些常见积分处理手法 球面坐标系定义 球面坐标系由方位角φ\varphiφ、仰角θ\thetaθ和距离rrr构成 直角坐标系(x,y,z)(x,y,z)(x,y,z)到球面坐标系的(r,φ,θ)(r,\varphi,\theta)(r,φ,θ)的转化规则如下&#xff1a; {xrsin⁡φco…

使用Python脚本修改Maya ASCII文件路径方法

以下脚本修改当前项目路径和子文件夹中扩展名为“.ma”的所有文件&#xff0c;这样您就可以轻松地一次编辑所有文件。此脚本搜索特定字符串replace_This变量并将其替换为with_This&#xff0c;您可以使用它更改引用路径、纹理路径等… 话不多说直接上脚本&#xff1a; import…

JavaWeb-JavaScropt入门(二)

目录函数语法传参函数表达式作用域对象使用 字面量 创建对象 [常用]使用 new Object 创建对象使用 构造函数 创建对象函数 语法 function 函数名(形参列表) {函数体return 返回值; }那么有了创建函数&#xff0c;肯定有调用函数&#xff1a; // 函数调用 函数名(实参列表) …

聊一聊,我对DDD的关键理解

作者&#xff1a;闵大为 阿里业务平台解决方案团队 当我们在学习DDD的过程中&#xff0c;感觉学而不得的时候&#xff0c;可能会问&#xff1a;我们还要学么&#xff1f;这的确引人深思。本文基于工作经验&#xff0c;尝试谈谈对DDD的一些理解。 一、序 《阿甘正传》中&#xf…

亚马逊要求UL94防火测试阻燃测试标准及项目

UL94认证是什么&#xff1f;分几个等级?是如何表示各等级?带电的产品上架亚马逊都需要相关的UL报告&#xff0c;需要有ISO 17025资质的实验室出具的测试报告才能正常销售和恢复链接&#xff0c;UL94防火测试则是其中一项。UL94试验共有五种&#xff1a;1.B级的水平燃烧试验2.…

Nginx负载均衡

1、概念 访问量太大&#xff0c;一个 Tomcat 扛不住&#xff0c;所以就搭建 Tomcat 集群。让集群中的多个 Tomcat 服务器实例分担负载。 纵向扩容和横向扩容&#xff1a; 纵向扩容&#xff1a;给单台服务器提升硬件的配置。例如&#xff1a;提升 CPU、增加内存、扩大带宽、扩…

关于华为网络设备操作系统的介绍

1、什么是VRP VRP是华为公司数据通信产品的通用操作系统平台&#xff0c;作为华为公司从低端到核心的全系列路由器、以太网交换机、业务网关等产品的软件核心引擎。 功能&#xff1a;实现统一的用户界面和管理界面、实现控制平面功能&#xff0c;并定义转发平面接口规范、实现…

量子比特大突破!原子薄材料成为“救世主”

&#xff08;图片来源&#xff1a;网络&#xff09;量子计算是一项极其复杂的技术&#xff0c;现阶段的一些挑战正严重阻碍着它的发展&#xff0c;尤其是量子比特的小型化和质量问题。IBM计划在2023年实现具有1121个超导量子比特的处理器。以目前的技术手段&#xff0c;要达到这…

大数据框架之Hadoop:入门(二)从Hadoop框架讨论大数据生态

第2章 从Hadoop框架讨论大数据生态 2.1 Hadoop是什么 Hadoop是一个由Apache基金会所开发的分布式系统基础架构。主要解决&#xff0c;海量数据的存储和海量数据的分析计算问题。广义上来说&#xff0c;Hadoop通常是指一个更广泛的概念-Hadoop生态圈。 2.2 Hadoop发展历史 1&…

引入汇丰完成C+轮融资,镁信健康有何资本“魅力”?

近日&#xff0c;上海镁信健康科技股份有限公司&#xff08;下称“镁信健康”&#xff09;宣布完成C轮融资&#xff0c;引入汇丰集团作为战略投资者。镁信健康近两年是资本市场货真价实的“香饽饽”&#xff0c;2021年&#xff0c;完成B轮和C轮两轮融资&#xff0c;融资金额合计…

基于merlin使用chatGPT进行对话

最近chatGPT很热&#xff0c;大家都想试用它。但由于各种限制&#xff0c;一般情况下国内不能试用。 下面给大家介绍基于merlin使用chatGPT&#xff08;目前每天只有11次问答次数&#xff09;。 1 打开merlin页面 访问地址merlin.foyer.work&#xff0c;点击“add to chro…

深入Kafka核心设计与实践原理读书笔记第二章

1 生产者 生产逻辑 配置生产者客户端参数及创建相应的生产者实例。构建待发送的消息。发送消息关闭实列 参数说明 bootstrap.servers &#xff1a;用来指定生产者客户端链接Kafka集群搜需要的broker地址清单&#xff0c;具体格式 host1:port1,host2:port2,可以设置一个或多…

Sentinel-线程隔离和熔断降级

一、线程隔离 ​​​​ 总结 线程隔离的两种手段是&#xff1f; 信号量隔离线程池隔离信号量隔离的特点是&#xff1f; 基于计数器模式&#xff0c;简单&#xff0c;开销小线程池隔离的特点是&#xff1f; 基于线程池模式&#xff0c;有额外开销&#xff0c;但隔离控制更强 二…

为什么在容器中 1 号进程挂不上 arthas?

作者&#xff1a;卜比 本文是《容器中的 Java》系列文章之 4/n &#xff0c;欢迎关注后续连载 &#x1f603; 。 系列1&#xff1a;JVM 如何获取当前容器的资源限制&#xff1f; 系列2&#xff1a;Java Agent 踩坑之 appendToSystemClassLoaderSearch 问题 系列3&#xff1a;让…

FPGA纯verilog代码实现图像对数变换,提供工程源码和技术支持

目录1、图像对数变换理论2、log系数的matlab生成3、FPGA实现图像对数变换4、vivado与matlab联合仿真5、vivado工程介绍6、上板调试验证并演示7、福利&#xff1a;工程代码的获取1、图像对数变换理论 对数变换可以将图像的低灰度值部分扩展&#xff0c;显示出低灰度部分更多的细…

解决报错: ERR! code 128npm ERR! An unknown git error occurred

在github下载的项目运行时&#xff0c;进行npm install安装依赖时&#xff0c;出现如下错误&#xff1a;npm ERR! code 128npm ERR! An unknown git error occurrednpm ERR! command git --no-replace-objects ls-remote ssh://gitgithub.com/nhn/raphael.gitnpm ERR! gitgithu…

展锐UDX710:ProSLIC SI32185移植适配(未完待续)

一、SLIC基本流程图 在展锐UDX710平台上新增slic 驱动,可将应用代码置于source/unisoc/atrouter2.0下面,Si32185驱动代码放置于kernel,通过IOCTL对slic器件进行控制。整体流程如下图所示: 二、ProSLIC SI32185驱动调试 1. DTS 控制通路SPI0需增加一个spidev用于绑定对…

SpringCloud学习笔记03

目录 四十七、Hystrix是什么 四十八、Hystrix停更进维 四十九、Hystrix的服务降级熔断限流概念初讲 五十、Hystrix支付微服务构建 五十一、JMeter高并发压测后卡顿 五十二、订单微服务调用支付服务出现卡顿 五十三、降级容错解决的维度要求 五十四、Hystrix之服务降级支…

ReentrantReadWriteLock、StampedLock

ReentrantLock、ReentrantReadWriteLock、StampedLock 读写锁 一个资源可以被多个读线程访问&#xff0c;或者被一个写线程访问&#xff0c;但是不能同时存在读写线程。 小口诀&#xff1a;读写互斥&#xff0c;读读共享 锁的演变 无锁-----> 独占锁----->读写锁---…