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.com
、www.qq.com
、www.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请求进行打印即可。
#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>
<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文件的内容就应该放在响应正文当中,我们只需读取该文件当中的内容,然后将其作为响应正文即可。
我们也可以通过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表示的是临时重定向。
临时重定向和永久重定向本质是影响客户端的标签,决定客户端是否需要更新目标地址。
如果某个网站是永久重定向,那么第一次访问该网站时由浏览器帮你进行重定向,但后续再访问该网站时就不需要浏览器再进行重定向了,此时你访问的直接就是重定向后的网站。
而如果某个网站是临时重定向,那么每次访问该网站时如果需要进行重定向,都需要浏览器来帮我们完成重定向跳转到目标网站。
演示重定向
#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
Cookie
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有可能被盗取,但是服务器也有一些策略来保证用户信息的安全性。
- 服务器可以通过IP地址来判断用户登录的地址范围,如果用户在短时间内登录地址发生了巨大变化,此时服务器就会知道这个用户出异常了,就会清楚服务器中对应的SessionID。
这会要求用户在登录时重新输入用户名和密码,因为用户名和密码只有本人和服务器知道。重新验证后,就会将另一个IP识别为非法用户,加入黑名单中。将合法用户和非法用户分为白名单和黑名单。
- 当用户进行一些高权限的操作时,会让用户再次输入密码,再次确认信息。
非法用户是不知道正确的密码的,就会让非法用户不能进行高权限的操作。比如修改密码时,需要输入旧密码。
- SessionID也是会过期的,假如某一个SessionID只有1小时的有效期。
即使你的SessionID被盗用了,也仅仅是在1小时内被盗用,而且和权限也有限,因此影响不会太大。
理你发来的请求,这就是我们当前主流的工作方式。
- 安全是相对的
引入SessionID后,浏览器中的Cookie文件中存的是SessionID,但是这个SessionId同样可以被盗取。
但是此时用户的用户名和密码就不会泄露了,由于SessionID已经泄露,非法用户仍然可以通过SessionID区访问我们曾经登录过的服务器,还是存在刚才的问题。
虽然引入SessionID并没有彻底解决安全问题,但是这个方法是相对安全的。因为互联网上是不存在绝对的安全的,网络中的信息随时是可能被别人破解的。
但是如果破解成本远大于破解带来的收益,那么就不会有人去破解这个信息了,这个信息就可以说是安全的。
引入SessionID的好处
引入SessionID后,用户的用户名和密码都是由服务器来维护的,本地Cookie文件中存的只是SessionID。
虽然SessionID有可能被盗取,但是服务器也有一些策略来保证用户信息的安全性。
- 服务器可以通过IP地址来判断用户登录的地址范围,如果用户在短时间内登录地址发生了巨大变化,此时服务器就会知道这个用户出异常了,就会清楚服务器中对应的SessionID。
这会要求用户在登录时重新输入用户名和密码,因为用户名和密码只有本人和服务器知道。重新验证后,就会将另一个IP识别为非法用户,加入黑名单中。将合法用户和非法用户分为白名单和黑名单。
- 当用户进行一些高权限的操作时,会让用户再次输入密码,再次确认信息。
非法用户是不知道正确的密码的,就会让非法用户不能进行高权限的操作。比如修改密码时,需要输入旧密码。
- SessionID也是会过期的,假如某一个SessionID只有1小时的有效期。
即使你的SessionID被盗用了,也仅仅是在1小时内被盗用,而且和权限也有限,因此影响不会太大。