超文本传输协议HTTP

HTTP协议

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

HTTP协议简介

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

URL

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

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

shell 复制代码
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代表的是要访问的资源所在的路径,访问服务器是为了获取服务器上的某个资源,通过前面的域名和端口号已经可以找到对应的进程了,此时要做的就是指明该资源所在的路径。

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

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

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

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

  • [uid=1]

六、查询字符串

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

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

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

  • [ch1]

七、片段标识符

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

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

urlencode和urldecode

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

转义规则:

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

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

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

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

输入C%2F%2F

点击解码

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

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

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

HTTP协议格式

HTTP协议请求格式

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请求进行打印即可。

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

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

注意:

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

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

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

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

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

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

HTTP的响应格式

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

  • 状态行:

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

  • 响应报头

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

  • 空行

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

  • 响应正文

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

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

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

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

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

构建一个HTTP响应

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

html 复制代码
<html>
    <head></head>
    <body>
        <h1>hello http</h1>
    </body>
</html>
shell 复制代码
#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文件的内容就应该放在响应正文当中,我们只需读取该文件当中的内容,然后将其作为响应正文即可。

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

注意:

  • 实际我们在进行网络请求的时候,如果不指明请求资源的路径,此时默认你想访问的就是目标网站的首页,也就是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中再添加两个表单,作为用户名和密码的输入。

效果:

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

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

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

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

注意:

  • 我们使用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表示的是临时重定向。

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

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

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

shell 复制代码
#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演示:

HTTP常见的Header

常见的Header:

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

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

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

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

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

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

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

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

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

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

  • Connect:Keep-Alive长连接

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

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

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

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

Cookie和Session

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

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

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

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

那么什么是Cookie?

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

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

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

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

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

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

往后只要再次访问这个网站,浏览器会再次发起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。

服务器识别到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小时内被盗用,而且和权限也有限,因此影响不会太大。

相关推荐
m0_7482382713 分钟前
WebClient HTTP 请求问题处理模板(泛型响应、忽略 SSL 证书等)
网络协议·http·ssl
我曾经是个程序员23 分钟前
鸿蒙学习记录之http网络请求
服务器·学习·http
0zxm25 分钟前
06 - Django 视图view
网络·后端·python·django
轩辰~1 小时前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
燕雀安知鸿鹄之志哉.2 小时前
攻防世界 web ics-06
网络·经验分享·安全·web安全·网络安全
ProcessOn官方账号2 小时前
如何绘制网络拓扑图?附详细分类解说和用户案例!
网络·职场和发展·流程图·拓扑学
Ven%3 小时前
如何在防火墙上指定ip访问服务器上任何端口呢
linux·服务器·网络·深度学习·tcp/ip
神的孩子都在歌唱3 小时前
TCP/IP 模型中,网络层对 IP 地址的分配与路由选择
网络·tcp/ip·智能路由器
阿雄不会写代码3 小时前
ubuntu安装nginx
linux·服务器·网络
starstarzz4 小时前
计算机网络实验四:Cisco交换机配置VLAN
网络·计算机网络·智能路由器·vlan·虚拟局域网