一、HTTP协议
1.URL
1.1 URL的组成
1. 在之前的文章中我们实现了一个网络版本的计算器,在那个计算器中揉合了协议定制以及序列化反序列化的内容,我们当时也自己定制了一套协议标准,比如请求和响应的格式应该是什么?如何读到一个完整的报文?支持的运算符有什么?等等我们都有自己的标准。 那么有没有其他大佬针对应用层的某些使用场景,已经提前给我们写好了协议软件呢?有,这个协议就是http协议,我们当时的协议仅仅是针对计算场景所设计的,而http协议主要是针对web场景所设计的。 虽然到现在我们还没真正的接触http协议的具体内容,但我们现在已经可以知道,http中一定有网络套接字编程,序列化反序列化,以及http要进行的自己的业务逻辑,而这三个方面实际和我们当时的计算器相同,都是分别对应OSI上三层模型,分别是会话,表示,应用,http的业务逻辑一般主要是电子邮件的发送,远程登陆,文件传输等……
2. URL听着比较高级,但他其实就是我们平常所说的网址,红色下划线是域名,这个域名等价于ip,用于标识一台主机在全网中的唯一性,域名实际还会做解析,解析之后就是服务器的ip地址,用域名不用ip主要是因为域名用起来方便,域名会和特定的ip地址做映射,域名后面的就是访问资源的路径,/是web根目录,这个根目录可以是linux下的任意一个目录,/后面的erridjsis是向服务器请求的资源,?后面的是请求资源时所匹配的参数。https是在http的基础上增加了一层加密层,这个协议后面会讲。 在url里面我们并没有看到port,这是怎么回事呢?其实是因为浏览器自动忽略了服务器的端口号,但真正在发送url进行请求的时候,还要将端口号填充到url里面,那浏览器怎么知道自己要访问的服务器的端口号是多少呢?浏览器其实是通过我们的协议来知晓服务器的端口号的,一般http协议对应服务器的端口号是80,https对应的服务器的端口号是443. 服务器端口号大部分情况下都可以省略,因为协议和端口号是强绑定的!所有的网络服务都有对应的明确的端口号,这些是已经确定好的。
3. 而我们所有的网络行为无外乎就两种,一种是通过http协议从服务器拿下来对应的资源,一种是通过http协议向服务器上传你本地的资源。 你在网络中看到的音频,视频,网页,图片等等,都是服务器上的文件资源,客户端看到的这些资源实际就是服务器对应返回的响应结果,因为文件资源的种类很多,文件后缀就很多,但这些文件的传输HTTP协议都能搞定,比如音乐,视频等文件都能传输,所以HTTP叫做超文本传输协议,不仅仅只能传文本,还能传其他很多种类的文件资源。 像我们平常刷抖音,听网易云实际就是抖音的服务器将他内部的视频传输到我们客户端,或是网易云的服务器将音乐传输到我们的客户端,而我们远程登录,在浏览器中搜索某些东西等行为就是将信息提交到远端服务器,将账号密码或搜索的关键字提交给服务器。
1.2 urlencode && urldecode
1. 在url中像// ? : @ #等字符已经被当作特殊意义处理了,所以如果你要搜索的内容中出现了这些特殊意义的字符,则他们会被编码化也就是urlencode化,除这些已经被使用的特殊字符外,汉字也会被urlencode化。 一般来说网页URL只能使用英文,数字,还有一些特定的字符等可以不经过编码直接用于URL,其他的字符都必须先经过urlencode编码才能用于URL,否则传给服务器的request URL会包含乱码,服务器无法正确识别URL,自然就无法确定你要访问什么样的资源,这样就会出现错误。 可以看到在百度和必应搜索引擎上?的左边有一个search字段(百度的是s),这个字段代表的是服务器要提供的特定的服务是什么,当然对于搜索引擎来说服务当然是搜索search啦,?右面的是以&作为分隔的name=value的一个个的键值对的参数形式,你搜索的时候可能只输入了一串关键字符串,但实际形成的URL中会添加很多的参数,?右边的就是微软或百度在搜索时,服务器需要的对应的参数,需要的参数还是比较多的,我们认识的其中一个参数就是wd=XX%XX%XX%,这其实就是我们搜索内容编码化后的样子,除了这个外,还可能包括用户端的编码格式等等一系列的信息,这些参数信息都是搜索引擎公司所需要的。
2. 如何urlencode和urldecode呢?首先需要将转码的字符先转为16进制的表示形式,然后从右到左取4位,将4位再以2位为一组分为两位,这两位可以记作XY,然后前面再加上%,这样就变成了%XY的格式。 encode的工作浏览器帮我们做了,那decode的工作还需要我们自己做吗?一般情况下是不需要的,但不排除你是从0开始写服务器的,并且我还是学C 的,写的就是服务器,这种情况下是需要自己做decode的工作的,因为C 不会直接完成decode的工作,其他的后端语言python java是可以直接完成decode的工作,我们也不用担心,直接上网搜一下C URL decode的源码即可,直接copy一份,拿来复用就行,不需要我们自己做这个工作。
3. 网上也有很多在线的url编码工具,可以进行我们输入字符的编码和解码,大家可以玩一玩这些工具。
URL解码编码工具
2.HTTP协议格式
2.1 http请求和响应的格式
1. http请求分为请求行,请求报头,空行,请求报文这4个部分,其中请求行又以空格作为分隔符,由请求方法 url 协议版本三部分组成,http这里最常用的两个请求方法就是GET和POST,后者可以保证用户数据的私密性,这个我们后面会讲,常用的协议版本是1.1,除1.1外还有1.0版本。 request的Header报头中是以行为单位的http请求的各种属性,每行都是由name 冒号 空格 value rn组成,接下来是空行,空行之后就是正文内容body,请求正文可以为空,如果你只单纯的想从服务器上拿资源到本地的话,请求正文可以为空,如果你想向服务器上提交一些内容,比如提交账号和密码进行登录,又或是提交一些搜索时需要的关键字进行相关网络内容的搜索,这些信息就可以放在请求正文body中。 http响应分为状态行,响应报头,空行,响应报文这四个部分,其中状态行又以协议版本,状态码,状态码描述组成,常见的状态码有200 404 502等等,响应报头的格式和请求报头一样,内容包含响应内容的各种属性,接下来是空行,之后就是响应正文,相应正文就是客户端请求的资源内容,所以响应正文内容可以是html网页,图片,视频,音频等各种资源。
2. http协议也会遇到我们当时定制网络版本计算器协议时所遇到的问题,比如你怎么保证读到一个完整的请求或响应报文呢? 其实很简单,因为首行和报头都是以行作为分隔符的,所以只要while循环一直按行读取,直到读取到空行为止,这样就读取完了首行和报头,而报头中会有一个字段Content-Length表示正文内容的长度,则读取空行结束后,我们只需要再读取Content-Length长度个字节的内容即可读取一个完整的报文。
3. http的请求和响应怎么做到序列化和反序列化呢?这个问题也很简单,http并没有使用json protobuf xml等方案来实现序列化和反序列化,因为根本没那个必要!如果要进行序列化,则从第一行开始,将首行 报头 空行 正文等内容,以rn作为分隔将每行字符串进行拼接,拼接成一个大字符串,然后直接发送即可。反序列化则按照以rn作为分隔符,读取每个小字符串的内容即可。 所以http的序列化和反序列化实际就是通过特殊字符rn来将每个内容分隔开来的。 如果正文的内容是图片 音频 视频等文件,则他的序列化和反序列化工作一般不考虑,直接按照二进制发送就行。
2.2 通过代码来进行验证
2.2.1 打印出http请求报文(了解一下request的各个字段)
1. 下面是http服务器的代码,服务器的代码和最开始我们写TCP套接字编程的代码没太大区别,首先服务器初始化时,要传递一个回调函数func,这个函数是http的业务逻辑处理函数,并且服务器运行后,孙子进程执行的函数也发生了改变,我们让父进程负责会话管理,监听来自客户端的连接请求并受理,而孙子进程负责给客户端提供对应的http服务,执行HandlerHttp方法。 在HandlerHttp里面,首先要保证读取一个完整的请求报文,但今天我们不做这个工作了,这个工作做起来比较麻烦,需要套一堆的while循环进行判断,我们大概率是直接能读取到一个完整的http请求的。 所以直接从sockfd里面读取http请求,将内容放到buffer里面,读取成功的话,则将buffe内容拷贝到req结构体中的_inbuffer字段里面,然后就可以回调_func(req, resp)函数,将req中的内容进行http处理,然后得到一个resp结构体,最后我们再把resp中的_outbuffer内容返回给客户端即可,至于req的parse方法,现在可以先不管,这个方法是后面进行某些实验时使用的,而func的逻辑也很简单,我们就直接打印出http的请求内容,先不构建http响应什么的,看看完整的http请求的庐山真面目。
2. 上面就是我们所写的http服务器,那http的客户端呢?我们没有写浏览器的客户端啊。其实不用担心,我们有现成的http客户端,就是浏览器,我们可以用本主机的浏览器来发送http请求到云服务器上,而我们在云服务器上写的代码完成的http请求处理工作其实很简单,就是单纯的把http请求内容打印出来而已。 所以在输入Web地址的一栏中,我们可以用ip 端口号的方式来访问我们的云服务器,只要一访问,vscode的命令行就会打印出http请求的内容。内容包括首行 报头 空行 以及正文,当然今天我们仅仅只是访问了一下服务器,没有向服务器提交什么信息,所以请求正文自然为空,什么都没有。
3. 浏览器作为客户端请求服务器,请求是多线程的请求,所以我们的服务器会一次收到很多的http request。 请求报头中包含了Host,也就是访问的服务器的ip地址和端口号,主要是因为客户端在发送请求给服务器的时候,中间可能还存在代理服务器,代理服务器需要知道自己转发消息应该发给谁,而Host就是告诉代理服务器请求应该继续转发给谁。 Cache-Control表示客户端要求服务器对请求的资源进行重新获取,而不应该从自己服务器内部的缓存位置中返回任何数据,意味着客户端需要最新的资源。 Upgrade-Insecure-Requests为1的时候表示客户端告知服务器,客户端想要升级到安全连接HTTPS,替换原本不安全的HTTP,如果服务器支持HTTPS,则会将请求重定向到HTTPS地址,如果服务器不支持HTTPS,则自动忽略该请求报头字段。 (使用ip地址 端口号的方式访问服务器时,默认使用的协议是HTTP,如果需要使用HTTPS则需要在ip地址前面加上https://如果服务器支持HTTPS协议,浏览器会发起https连接请求,如果不支持,则会出现连接错误。) User-Agent表示客户端信息,包括客户端主机的操作系统版本信息,浏览器内核版本信息等等。我用我的电脑和手机分别访问了云服务器,所以两个操作系统版本分别为x64架构的win10和基于linux的Android 13操作系统。 Accept表示客户端接受的文件类型,包括多种格式,有图片,html网页等等。 Accept-Encoding表示如果文档过大时,浏览器可接受的压缩格式。 Accept-Language表示浏览器支持什么样的编码和语言格式。
4. 浏览器请求时,默认的请求方法是GET,如果请求时请求资源的路径没有指定,则服务器会默认返回web server的首页,也就是web根目录下的index.html首页,通常情况下一个网站只有一个index.html作为此网站的首页,一个服务器可以存在多个网站,每个网站会分配不同的端口号。 http在请求时,还会交换bs(browser server)通信双方的协议版本,因为客户端的版本可能由于未及时更新导致和服务器的协议版本不兼容,为了保证服务器返回的响应报文的协议版本和客户端保持一致,所以服务器需要知晓browser的协议版本。 通过http请求的User-Agent字段,还可以理解一个生活现象,我们用电脑浏览器搜索微信进行下载时,浏览器会主动给我们推送pc版本的微信,用手机浏览器搜索微信进行下载时,浏览器会主动推送Android版本的微信,这是为什么呢?因为浏览器的服务器会收到http请求,而http请求的User-Agent字段包含了客户端的操作系统版本信息,通过OS版本信息,浏览器就可以给我们推荐响应版本的微信app了
5. 上面我们提到过index.html网站的首页信息,许多人可能不了解这个东西,我们可以通过wget爬一下百度网站的首页信息,cat打印一下index.html,可以看到前端的一些html代码。
2.2.2 硬编码响应一个html网页(给浏览器返回一个html网页)
1. 上面仅仅只是打印了http请求的内容,下面我们构建http响应,让服务器给浏览器返回一个静态的html网页,再谈论html网页之前先来介绍一个本地测试的工具telnet,该工具可以远程登陆我们的服务器,进行TCP连接,连接成功后,可以在命令行中手动构建http请求,测试服务器返回的http响应结果是否正确。 安装telnet的指令分别为sudo yum install -y telnet-server和sudo yum install -y telnet.* 安装成功后,输入ctrl ],按下回车就可以得到telnet的命令行,得到命令行之后,继续按下回车,然后开始构建HTTP请求的请求行并按下回车,然后服务器就会返回该请求行对应的响应内容。
2. 响应报文我们先硬编码写一个html网页,然后把这个字段加到响应报文里面,服务器读取该报文之后就会解释为一个网页并给我们呈现出来。如果你不知名UTF-8的编码,则浏览器解释时可能会出现乱码的情况。
出现乱码也不要慌,只要在html里面指明编码规则为UTF-8即可,并且通过telnet本地测试也可以得到响应报文内容。
下面就是服务器返回的一个网页响应。
3. 实际上浏览器是很智能的,就算我们的响应内容不包含报头字段,例如Content-Length,浏览器依旧也可以识别文件类型,并进行完整的读取。不过我们不依赖浏览器这样的行为,而是自己主动构建出完整的响应报文,这样是比较好的。
2.2.3 服务器与网页分离(web根目录wwwroot)
1. 上面请求服务器的时候,什么路径都不带,所以返回的就应该是网站首页,但如果我想访问web根目录下的某个路径下的资源呢?那直接硬编码返回html网页的方式就不可行了,我们需要一个真正的存放网页的web根目录。 而这个鼎鼎大名的web根目录实际也就是服务器中的一个普通目录而已,只不过这个目录存放的是网站的各种资源文件,包括HTML CSS JS 图片文件等等,当用户访问网站的某个页面时,web服务器就会从web根目录开始查找对应的资源文件。
下面代码我们做了请求行的解析,将method url http_version等信息解析出来放到req结构体的字段中,在构建响应报文的时候,我们将这些信息打印出来看一下。 如果请求路径只有一个/,则直接返回网站首页./wwwroot/index.html
2. 从打印结果可以看到,当访问web根目录下的某个路径的文件资源的时候,path路径就是该文件资源的路径,而当url为/时,path路径就变为./wwwroot/index.html,也就是网站的首页。 所谓的web根目录其实就是在进行url请求时,http协议自动给我们拼接的前缀./wwwroot,这个目录就是web根目录,同时也是网站的首页。
2.2.4 首页中增加跳转链接(重新发起http请求)
1. 如果想要将一个HTML文件返回给浏览器实际也很简单,我们只需要以读取文件的方式,将HTML文件内容读取到响应正文respbody里面即可,然后将respbody字符串拼接到resp中的outbuffer字段里面即可,实际就是多了个文件操作。 而在首页中增加跳转链接也很简单,设置href加路径即可,当我们在首页中点击链接时,其实就是重新又向服务器发起该链接对应资源的请求,如果你点击a,则服务器会重新给你返回a网页的资源,重新在浏览器端显示出来。
2.2.5 首页中增加图片(服务器读取图片文件内容时,必须以二进制读取到缓冲区中)
1. 除将HTML文件返回给浏览器外,我们也可以将图片文件返回给浏览器,两者本质是相同的,因为在Linux下一切皆文件!别跟我说你是音频 视频 网页 还是什么乱七八糟的东西,我linux服务器不管这些,我只认文件,无论是什么到linux这里全都是文件,所以无论返回给浏览器什么,其实无非都是把文件内容按照二进制的方式先读取到缓冲区中,然后调用socket接口将缓冲区的内容发送回浏览器而已,本质都是相通的。
2. 所以用户看到的一个网页,可能是经过多个资源组合而成的!例如我们现在所看到的网页是由多个HTML文件组成,包括体育新闻,金融新闻,首页HTML文件,以及图片文件.jpg,每次发送HTTP请求时,请求资源的类型都不一样,那我们怎么知道请求的资源类型是什么呢?
3. 其实道理很简单,答案就藏在请求资源的后缀里,我们可以从path路径内容中倒着查找,以.作为分隔符将后缀字符串切割出来,放到请求结构体req的suffix字段里面,然后我们就可以把这个字段转成Content-Type: text/htmlrn等这样的类型,根据切分出来的suffix字段进行穷举类型,是什么类型我们就将该类型尾插到Content-Type: 中,然后将此字段尾插到响应结构体resp中的outbuffer里面。
3.HTTP中细节探究
3.1 HTTP的请求方法(GET和POST)
1. HTTP的请求方法有很多,但我们只要了解两个最常用的就够了,一个是GET获取资源,一个是POST上传资源,GET方法通常用于请求服务器发送一些信息,这GET方法的主要限制是URL的长度。大多数浏览器和服务器都对URL的长度有限制些信息(参数)是通过URL的查询字符串部分发送的。 一般向服务器提交数据的时候,前端都是通过form表单的方式来提交,浏览器会自动将form表单中的内容转换为GET或POST方法下的http请求中的某个字段,下面是表单提交的路径和方法,一旦输入内容点击登录后,页面会自动跳转到/a/b/c.py目录.
2. GET方法比较尴尬,用户提交的信息直接拼接到url的后面了,谁都能够看到,私密性不够好。 而POST方法会将用户提交的参数信息保存到请求正文里面,然后提交给服务器,用户是看不到的,私密性比GET更好一些。 但值得注意的是GET和POST只是在私密性上有所不同,两者都是不安全的,网上某些人说POST方法比GET方法更安全,这种说法是错误的,想要使网络通信安全,则必须加密(HTTPS)。 一般如果不要求私密性,想要用URL给服务器传参,参数必须是简洁的KV式的参数,这样从场景可以使用GET方法。如果传参内容过长,则可以使用POST方法来传,因为请求正文可以很大,例如上传简历,文件什么的,都可以使用POST,也比GET方法更私密一些。
3. 我们提交给服务器指定的路径,有什么意义呢? 意义非常重大,服务器可以通过解析url的值,如果是POST方法则先以?进行分隔得到左边的path,通过path服务器会提供对应的服务,例如一开始我们讲述URL的时候,可以看到百度和微软搜索时的URL中?的左边都是search,代表服务器会提供搜索的服务,又或是csdn网址中的md,article等等,这些不同的path表示服务器会提供不同对应的服务。 所以提交路径的意义就是进行HTTP服务器的功能路由,通过解析url中?的左半部分,让服务器提供对应服务。
3.2 HTTP的状态码和Header(Location配合3XX状态码进行重定向)
1. 最常见的状态码就是404(not found)客户端错误,表示在服务器中找不到客户端所请求的资源路径,除此之外就是503 502等状态码,学校的垃圾服务器崩的时候,我们会经常见到这样的状态码,原因就是服务器过载或者正在进行维护所导致的,http请求数量过多,服务器响应不过来,无法同时建立那么多的TCP连接,所以会报很多503这样的状态码错误。
2. 需要额外说明一下的是重定向错误码,重定向其实就是你点击网站请求服务器的时候,服务器返回了307状态码并附带了新的url,此时客户端又会再次请求新的url,跳转到另一个网站中。
3. 重定向可分为临时重定向和永久重定向,状态码分别为302和301,两者主要区别在于重定向次数的不同, 临时重定向:在请求新的URL后,在新的URL上浏览器不会认为该URL是请求资源的最终位置,所以还可以继续进行新的连续多次的重定向。 永久重定向:在请求新的URL后,浏览器会认为新的URL就是请求资源的最终位置,则之后不会再进行重定向,所以永久重定向只能重定向一次。 比301和302更标准一些的是307和308,307符合HTTP标准,是实现临时重定向的更标准的一种方式,308是更符合HTTP1.1标准的一种永久重定向的方式。
4. 想要实现重定向其实也很简单,我们只要将http响应的状态行中的状态码设置为307,状态码描述为Temporary Redicet,同时在http响应报头中增加Location字段后面跟着要重定向到的网站,将此响应结构体返回,如果再次访问43.143.22.45:8080,则会直接跳转到我的csdn账号首页,不是原来的网页了。
输入我的服务器地址和端口号之后,可以看到网页重定向到了我的csdn首页
3.3 长连接(keep-alive)
1. 我们知道一张网页是可能由多种元素构成的,则一张完整的网页则一定需要经过浏览器多次的HTTP请求才能获取到,浏览器会将每次请求到的多个内容进行渲染组合,最后形成完整的一张网页。 我们知道HTTP是基于TCP的,所以如果频繁发起HTTP请求,则势必会频繁创建TCP连接,这样的网络传输就有点低效,但他的好处就是简单,不用做多余的工作,HTTP1.0标准就是这么做的。 后来随着网络资源的增多以及用户对网络传输的效率要求变高,所以频繁创建TCP连接这样的方式就不太好,进而推出了HTTP1.1版本,它允许客户端和服务器在一条TCP连接上进行多个HTTP的请求和响应,减少网络资源的消耗,提高服务器的性能和响应速度。
2. 在HTTP1.0中,长连接是可选的,需要通过在请求和响应头中设置Connection: keep-alive来启用,而在HTTP1.1中,长连接是默认启用的,除非显示的在请求和响应头中设置Connection: close来关闭长连接。 但实际上,在HTTP/2和HTTP/3中还引入了多路复用的技术,长连接在这些协议中反而变得不重要了,因为我们有更为高效的解决方案了。
3.4 周边会话保持(cookie和session)
1. 会话保持严格意义上不是HTTP天然具有的,是后面使用时,发现需要会话保持从而加上的。 HTTP协议是无状态的,也就是说HTTP协议不会记录自己历史所发送过的请求,但我们常见的一种网络现象是,如果我登陆过了某个网站,下次在登陆的时候,网站是能够记住我的,我无须再次输入账号和密码,这和无状态的http协议有点对不上啊。 首先需要明白一点的是,不要孤立的看待http协议,http就是一个协议,他是要被浏览器所使用的,虽然你http是无状态的,你每次发起http请求,浏览器都会去请求,但浏览器会有自己相关的信息缓存策略,因为用户是需要http有状态的,用户需要有自己的浏览历史状态,否则每次访问网站都需要登录,在一个网站内跳转也需要登录,这太影响用户体验了。 用户查看新的网页是常规操作,如果在一个网站中发生了新的网页跳转,新的页面是无法识别是哪一个用户的,需要用户重新登录,为了让用户一经登录就可以按照自己的身份,在整个网站中访问任意的网页,浏览器做出了缓存策略。 (例如我的edge浏览器中就有浏览器帮我缓存的图像和文件等信息)
2. 浏览器使用http协议是如何做到周边会话保持的呢? 通过cookie文件来实现,当发起第一次远程登录请求时,浏览器会将我们的账号密码等信息保存在本地的一个cookie文件中,往后只要再次访问这个网站,浏览器会再次发起http请求,同时将保存的cookie信息推送至服务器,无须用户再次输入账号密码重新登录。 cookie信息可分为文件级cookie和内存级cookie两种,如果是内存级cookie,则cookie的生命周期随浏览器进程,当我们把浏览器关闭之后,cookie信息会自动销毁,重新打开浏览器登录网站时,则需要再次输入账号和密码,如果是文件级cookie,则cookie不受浏览器关闭的影响,或电脑开关机重启的影响,因为他存在于磁盘外设上。 cookie是文件还是内存级别,这是在浏览器里面可以配置的。
3. 上面的方法还存在一些问题,常见的病毒分为蠕虫病毒和木马病毒,蠕虫病毒以直接攻击主机为目标,攻击内存或CPU,让计算机的资源吃的很紧,以至于你什么都干不了,而木马病毒并不会直接攻击你的主机,他希望你的主机越健康越好,主要目的是通过开启某些后门,盗取你主机上的cookie文件,从内部瓦解。 所以有可能你第一次登录网站后,浏览器自动保存了cookie文件到你的本地主机上,往后你每次登录都不需要输入密码和账号,只需要让浏览器推送cookie信息进行登录即可。如果黑客在你的电脑上植入了木马病毒,并盗取了你的cookie文件,那黑客用自己的浏览器来推送cookie信息到服务器,服务器会误认非法用户hacker就是用户本身,此时黑客就有可能使用你的身份从事某些违反犯罪活动,对社会产生较大的影响。
4. 为了解决上面的问题,需要引用session的概念,即浏览器不去保存用户的私密信息到本地上,而是将这样的信息保存在服务器上,服务器主机内部会形成一个session文件,此文件内部用于保存登录网站时所需要的账号密码等信息,一个服务器内部一定会有很多的session文件,因为不止你一个人访问此网站,所以在服务器内部session文件都会有唯一的标识叫做session id,标识服务器内部当前session文件的唯一性。而浏览器的cookie文件负责保存session id,当下一次进行登录时,浏览器会向服务器推送保存session id的cookie文件,服务器会比对当前发送过来的session id是否在服务器内部也同样存在一份,如果存在则表示登录成功,返回响应。通过这样的方式来实现http的周边会话保持。 但黑客有没有可能盗取session id呢?是有可能的,但用户信息的泄露问题已经大大改善了,以前我们作为平头老百姓,没有办法保护我们自己主机上的私密信息,但现在我们把信息保存在那些大型公司的服务器上了,让那些大公司间接的保护我们的私密信息,大公司肯定比我们平头老百姓有实力啊,他们有自己专业的网络攻防团队。 但浏览器误认黑客是用户本身这样的问题是解决不了的,我们只能配合其他策略来缓解这样的问题,例如ip异常登录警告,迫使双方下线,使得原来的session id失效,重新进行登录 重新生成新的session id,以及源IP溯源查找非法用户等策略,一旦黑客拿到session id,我们可以配合其他策略让用户和服务器同时下线,这样黑客拿到的session id就是无效的id,当用户重新上线时,再让服务器生成新的session id,通过这样的方式来缓解黑客恶意登录普通用户账号的问题。
5. 多说一点题外话,除了上面那种ip异常登录的方式外,如果很长时间你没联系过的用户突然给你发消息了,腾讯会甄别这个用户的安全性,检测到我们的账号可能存在安全问题,让我们重新登录,或者你与上一次登录的时间间隔过长,你的账号也可能存在异常,此时腾讯也会强制你下线,让你重新登录,使得原有session id失效,生成新的session id。 那些网站服务器其实也没那么弱,他们也并不是只会傻傻的等别人来攻击,白帽子工程师也会在网站中形成一些陷阱让黑帽子来踩,这样的陷阱称为口袋,一旦黑客踩到陷阱,则黑客的ip等相关信息会全部暴露在网站中,维护网站的公司中的安全团队可以通过源ip溯源的方式抓到hacker
6. 上面说了一大堆,你怎么证明你所说的是正确的呢?服务器是怎么做到把你输入的账号密码等信息写入到cookie中呢?又怎么证明http后续请求时会携带cookie信息呢?
当我们在响应报头header中添加了Set-Cookie字段时,从浏览器中保存的网站信息中就可以看到cookie数据,设置cookie内容的同时,也可以设置cookie的持续时间Max-Age
当服务器返回的响应报头中添加了Set-Cookie字段后,下一次浏览器在向服务器发起请求时,请求报头中会自动携带Cookie信息,此信息用于服务器鉴权,如果cookie信息正确,则登录成功,否则登录失败。
4.基本工具
4.1 postman(API测试工具)
1. postman是一款测试和调试Web API的工具,可以支持发送http请求和检查测试服务器的http响应,具有可视化查看内容的功能,主要用于测试和调试Web API,API一般指网络通信时,应用程序之间进行通信和数据交换的标准接口。
除了用postman获取某些网站信息外,我们也可以使用telnet url port的方式获取某些网站的信息。
获取成功后,postman会给我们返回一个可视化查看响应内容的窗口,其中有美化 原始 预览等查看类型。
用postman也可以获取自己web server服务器的信息内容,检查自己服务器的相应内容是否正常,通常可以用来调试测试我们的服务器功能。
postman也可以构建http请求发送给服务器,例如可以在请求正文body中添加表单form,发送至服务器,从服务器的响应内容可以看到请求正文中确实包含了encode的张三和hello world
4.2 fiddler(本地抓包工具)
1. fiddler是一个本地抓包工具,也可以用于捕获和分析HTTP的请求和响应,可以查看请求和响应的头部 正文等信息,Fiddler还提供了一些高级功能,如重放请求、修改请求和响应、自定义脚本等。(这些高级功能我也不会用,我用他也就只能调试检测一下我的web server的响应内容而已)
下面是fiddler抓到的我的服务器的http响应,可以查看到请求的cookie,host等信息
下面是服务器打印出来的内容
2. fiddler是一个主要抓http包的本地抓包工具,更多的是为了程序员进行本地测试的,还有一个软件wireshark,这个软件主要是针对传输层协议UDP和TCP的,postman不是抓包工具,他只是一个API测试工具,用于构建http请求和可视化查看http响应。
3. fiddler的角色其实就是中间代理服务器,所以fiddler抓到的http包的url都是非常完整的,包括前缀 ip 端口 请求资源路径等,因为作为中间代理服务器需要转发来自客户端的请求,则必须知道更为详细的信息,以便于将完整的信息转发至服务器。
二、HTTPS协议
1.基本概念的铺设
1.1 HTTPS的引入 && 什么是加密?(有加密,也一定有解密)
1. 在HTTP协议中,无论是GET方法还是POST方法都是不安全的,只不过POST方法相比GET方法更私密一些,但也仅此而已了,想要安全则必须有加密的参与。 HTTP协议中网络数据的传输全部都是明文的,安全性很低,20年前我们国家网络刚兴起的时候,当时大部分数据都是明文传输,由于各种安全问题的产生,所以在近十年HTTP协议大部分都被换成了https协议,但HTTPS协议被引入并不代表HTTP协议不用了!某些场景下还会继续使用http协议。
2. 在原有的http协议层和传输层之间设计了一层软件层,这层软件层就是加密层ssl/tls,当数据传输时若没有经过加密层,则默认使用的是http协议,如果数据传输经过了加密层,则使用的是https协议,这层软件层被设计到传输层了,不再应用层,所以,我下面画的图其实是有问题的,应该把ssl和tls画到传输层里面。 http协议的服务器匹配的端口号是80,https协议的服务器匹配的端口号是443,通过端口号我们就可以区分协议是否经过了加密。 需要明确的是http和https只是在技术上有交集,但在应用层两者是完全不同的协议。 (作为一个后端开发程序员,其实只要知道HTTPS就可以了,做网安 网络攻防的人才需要对HTTPS的原理进行深入学习,我学个皮毛就够了)
3. 加密就是把明文(要传输的信息)进行一系列变换得到密文,解密就是把密文经过一些列变换还原成明文,在加密和解密过程中通常需要一个或多个数据来辅助加密和解密过程的进行,这样的数据称为密钥。例如下面数据的传输过程中,client要发送a明文,随后他使用key作为密钥对a明文进行异或加密得到密文b,将密文发送给server,server接收到密文之后,也使用密钥key对密文进行异或,得到原始数据a=100,这样的一个传输过程我们称为加密和解密。 值得注意的是,拉长时间线和成本线来考虑的话,只要是加密那一定是能解密的,大不了穷举嘛,只不过可能需要的成本很高,时间人力物力财力等。 所以我们可以这样定义加密,通过密钥对明文进行一系列的变化,同时还能够通过密钥进行解密的通信过程,我们称为加密通信。
1.2 网络通信中的运营商角色 && 为什么要加密?(明文传输可能会受到中间人攻击)
1. 在中国大陆地区使用互联网时,许多的局域网网络请求都必须经过运营商的网络设备才能到达目标服务器,主要是因为中国政府实施了一系列的互联网监管措施,要求所有的互联网服务提供商ISP(Internet Service Provider)和互联网内容提供商ICP(Internet Content Provider)必须遵守相关的监管规定和技术要求。 常见的监管措施包括实名制、防火长城、DNS污染、IP封锁、HTTPS拦截、流量限制等,为了实施这些监管措施,中国三大运营商会在全国部署各种网络设备,如防火墙、代理服务器、路由器等,用户在局域网中发送的网络请求会被运营商的设备拦截和过滤,或者重定向到运营商的服务器进行处理(违规的HTTP或HTTPS请求,用户主机使用违规的DNS服务器,一些VPN和代理请求可能会被运营商拦截重定向到自己的服务器进行处理) 需要注意的是,也有特殊情况,有些局域网会使用虚拟专用网络(VPN)等工具来绕过运营商的限制,从而直接访问目标服务器,此外一些国际互联网公司在中国境内也部署了自己的服务器和网络设备,以提供更加稳定和快速的服务(这些国际公司都是符合中国法律规定的,访问这些国际互联网公司的服务器一般不需要经过运营商的设备)。
2. 局域网用户需要连接到广域网才能访问互联网资源,所以局域网的请求一般经过路由器路由后,会将请求发送至运营商的网络,由运营商将请求发送到广域网中。 大家应该听过臭名昭著的"运营商劫持"吧,比如我在浏览器上下载一个千千动听播放器,但点击下载的时候,链接就变为了QQ浏览器,这其实就是运营商在从中作梗,他替我们把请求发送至千千动听服务器,但运营商在返回时,自动给我们返回成QQ浏览器的下载链接,这其实就是腾讯给运营商钱了,运营商帮助他们做产品的推广,增加QQ浏览器的用户量,被金钱蒙蔽住了双眼。
3. 运营商劫持我们的数据其实还好,没有什么安全问题,就是恶心我们一下,但如果中间人劫持了信息呢?因为http是明文传送的,明文数据会经过路由器,代理服务器,通信服务运营商等多个结点,如果信息在传输过程中被中间人劫持并且进行篡改,双方还不会察觉到,这样就很危险,所以我们要对传输的信息进行加密。HTTPS就是在HTTP的基础上进行了加密,进一步保证网络传输数据的安全。
1.3 对称加密和非对称加密
1. 常见的加密方式有对称加密和非对称加密两种。 使用同一个密钥作为信息的加密和解密,这种加密方法称为对称加密,也成为单密钥加密,常见的算法有DES、3DES、AES、TDEA、Blowfish、RC2等,这些算法都是公开的,计算量较小,加密速度快,加密效率高。上面我们举得一个例子中,使用按位异或^进行加密和解密,这其实就是一种对称加密。
2. 非对称加密需要两个密钥来进行加密和解密,一个是公钥,一个是私钥,用公钥加密则只能用私钥解密,用私钥加密则只能用公钥解密,常见的非对称加密算法有RSA,DSA,ECDSA,非对称加密的算法强度复杂,安全性高,但最大的缺点就是运算速度非常慢,比对称加密要慢很多。
1.4 数据摘要和数据指纹(不是加密,常用来进行数据对比)
1. 数据摘要和数据指纹是一回事,其原理就是对一段文本使用单向散列函数(hash函数)生成一串固定长度的数字摘要,hash函数是不可逆的,因为映射时会使用随机值,并且不同的值还可能存在哈希碰撞,则一段文本经过特定的hash算法散列后,极大概率下不会和其他文本重叠,并且无法通过生成的散列值来逆推出原始文本。 常见的摘要算法有MD5、SHA1、SHA256、SHA512等,两个不同的信息经过hash散列后的散列值可能相同,但概率极低,就像人类的指纹在科学上是有相同的概率的,但在现实中遇到两个完全相同的指纹,这几乎是不可能的,就像两段不同的文本在经过同一hash函数散列后得到的散列值一样,两个散列值完全相同这几乎是不可能的。
2. 值得注意的是数据摘要算法不是加密,因为如果是加密,则必须有解密的过程,显然我们无法从散列值会推到原始文本,所以他不是加密。 数据摘要通常用来进行数据对比,把两段非常大的文本都经过同一散列方法进行散列,通过比对两个散列值是否相同来判断这两段文本是否相同,因为比较两个散列值要更轻松一些。 只要你稍微变化一下文章的内容,经过hash算法生成的散列值就会有很大的改变。
3. 理想的哈希算法可以让不同文本计算出的散列值发生碰撞的概率降至极低。 下面给大家举两个使用hash算法进行数据对比的栗子。 大家应该知道百度网盘秒传的功能吧,在用户看来他自己如果向百度网盘传了一个电影,从用户感知角度来讲,可能只需要不到1s的时间就把电影传上去了,但实际上不是用户传上去一个电影,而是百度网盘本身就有这个电影,试想一下如果中国那么多的用户都向百度网盘上传一个战狼2电影,那百度网盘不就存放了很多相同的文件吗?百度可没那么傻,他不会存储这样多余重复的文件,而只会存储一份这样的文件。 所以实际的秒传根本就没有传到百度网盘,而是将你所传的文件使用hash算法生成散列值,并且百度服务器也会把他自己数据库中的战狼2电影生成一个散列值,两者使用相同的hash算法,对比两个散列值是否相同,如果相同则不上传用户的电影文件,不相同则上传,如果你要看战狼2的话,则百度直接把他自己数据库中的战狼2电影传输给你即可。
4. 除此之外,当我们访问服务器进行某些网站的登录时,我们会输入账号和密码,而这些信息都会保存到服务器的数据库当中,如果直接把密码放到数据库中,一旦数据泄露则会带来巨大的后果,因为密码都是明面的,所以涉及到密码这样的字段最好使用hash算法进行数据摘要,这样即使数据泄露,不法分子也无法根据数据摘要逆推出密码是什么。 而我们自己登录的时候,还是道理相同,将自己的密码形成数据摘要与数据库中的数据摘要进行对比,相同则登录成功,否则失败。
2.HTTPS工作过程探究(解决数据被监听或被篡改的问题)
2.1 只使用对称加密
1. 如果通信双方都持有同一个密钥X,并且只有通信双方知道这个密钥X,则通信一定是安全的,除非黑客破解密钥,但我们假设黑客无法破解密钥。 但实际上下面的方案存在着一个很大的问题,你怎么保证客户端和服务器使用的是同一个密钥呢?所以服务器就必须把密钥传到客户端,让客户端知晓对称密钥X,但传输密钥是明文传还是密文传呢?明文传肯定不行,因为明文传,黑客劫持后不就知道密钥是什么了吗?那如果加密传的话,有需要新的密钥来进行加密,这样就变成了鸡生蛋,蛋生鸡的问题了。 所以只使用对称加密是不行的,因为无法保证密钥传递给对方的安全性!
2.2 只使用非对称加密
1. 服务器形成非对称加密的公钥和私钥,当客户端发起第一次密钥协商握手时,服务器将自己的公钥推送给客户端,则下次客户端发送信息时,可以使用公钥对明文进行加密,然后将密文传输给服务器,传输过程一定是安全的,因为中间人只能劫持公钥,而当前密文只能由服务器的私钥才能解开。 但当服务器发送密文给客户端时,这个过程就会出问题了,因为密钥加密的明文可以使用公钥进行解密,而全世界人民都有公钥,那服务器还加密了个寂寞啊,中间人轻轻松松就知道服务器发送的消息是什么了。
2.3 双方都使用非对称加密
1. 所以更进一步的做法就是客户端和服务器都使用非对称加密,第一次密钥协商握手时,客户端将自己的公钥推送给服务器,服务器接收后,服务器把自己的公钥也响应给客户端,此后当客户端要发送消息时,就使用服务器的公钥来进行加密,服务器收到后用自己的私钥进行解密,当服务器发送消息时,就使用客户端的公钥来进行加密,客户端收到后用自己的私钥进行解密。
2. 但这样的方案效率很低,因为非对称加密很慢,他的算法强度复杂度高,一次非对称加密就已经很慢了,使用两次非对称加密那就会更耗时,网络传输的效率就会很低。 并且实际上还存在着一定的安全问题,这个安全问题后面会讲。
2.4 非对称加密 对称加密
1. 我们先来解决两个非对称加密效率低的问题,后面在解决其依旧存在的安全问题。 如果要解决效率问题,服务器可以形成一对非对称密钥对,在客户端发起第一次密钥协商ssl握手时,服务器将自己的公钥推送至客户端,客户端获得公钥后,自己通过对称加密算法进行明文的加密,同时使用公钥将对称密钥key进行加密发送给服务器,这个过程即使中间人劫持了数据也无法进行解密,因为只有服务器的私钥才能解密,所以当服务器解密之后就能拿到对称密钥key了,之后客户端和服务器通信就可以使用对称密钥key来进行通信了,而对称加密的加密速度快同时加密效率高,这样就可以解决方案3效率低的问题了。
2.5 解决中间人一开始就进行攻击的问题
2.5.1 引入CA证书
1. 实际上面那种非对称 对称加密的方案已经很接近答案了,但他其实还存在一种问题,中间人不等客户端和服务器双方安全的将对称密钥传输后在进行攻击,而是在一开始就发起攻击呢? 当服务器推送自己公钥给客户端时,中间人此时直接劫持响应报文,将响应报文中的公钥替换成中间人自己的公钥,来个狸猫换太子呢?那客户端使用中间人的公钥进行对称密钥X的加密发送回服务器时,中间人依旧就可以巧妙的先用自己的私钥进行解密,先读取到对称密钥X,然后再拿服务器的公钥S将对称密钥X进行加密,然后伪装成客户端把加密后的对称密钥X发回给服务器,至此中间人其实已经拿到了自己最想要的东西了,但客户端和服务器却依旧无法察觉到,所以中间人拥有对称密钥X之后,那就可以随意篡改或监听数据,并且客户端和服务器都是不知晓的,一切尽在中间人的掌握之中。
2. 上面问题的产生主要是因为,客户端无法辨别收到的公钥是否就是服务器发送过来的,因为这个公钥有可能是中间人发送过来的,所以为了解决这样的问题,需要先引入CA证书。 服务器在使用HTTPS之前,需要向CA机构申领一份数字证书,数字证书中包含证书申请者信息,以及CA公钥信息,当服务器申请CA认证时,公钥会包含在数字证书里面,以证明公钥的权威性,而私钥则由服务器自己保存好。在申请CA证书前,需要准备好公司的相关信息,例如公司的营业执照,合法人信息等来确定服务器所在公司的合法性。
3. 申请证书时需要在特定平台申请,此时会生成一对儿密钥对儿,即公钥和私钥,公钥会随着CSR文件一起发给CA机构进行权威认证,私钥服务器自己保留,用于后续的网络通信,主要是用于交换客户端生成的对称密钥
4. 我们在网络访问时,可能也会遇到这样的情况,这种情况一般就是网站的CA证书可能过期了,或者网站没有申请CA认证,浏览器不信任该类网站。
2.5.2 理解数据签名(对数据摘要使用CA私钥进行非对称加密)
1. 数据签名实际就是先对数据(CA公钥)进行hash散列函数,形成散列值,然后对散列值使用CA认证的私钥进行加密,这样就得到了数据签名,将数据签名附加到数据上就得到了真正意义上的CA认证证书,而数据签名最大的意义就是防止有人篡改公钥,先前存在的问题不就是中间人将服务器发送的公钥篡改为中间人自己的公钥了吗? 服务器在第一次密钥协商握手时,发送的是数字证书,而不仅仅是公钥信息,当客户端收到数字证书后会验证证书的真假,验证方式为把证书拆分成签名和数据两部分,使用证书中的公钥对数字签名进行解密得到散列值,然后对证书中的数据(CA公钥)使用相同的hash散列函数也得到一个散列值,比较两个散列值是否相同,如果相同则说明证书中的公钥没有被中间人篡改过,如果不同则说明中间人一定篡改了证书中的公钥。 (CA证书数据=签名 数据,签名=CA公钥 hash散列 CA私钥加密,数据=CA公钥。)
2. 签名其实就是公钥先做hash得到散列值,然后再用服务端的私钥进行加密得到的密文签名,而数据其实就是公钥,如果中间人将证书的公钥更改为他自己的客户端的话,那么当客户端用中间人的公钥去解密签名时,一定是解不开的,就算解开了,将公钥使用hash散列后,与解密后的签名一对比,这一定是不相同的,所以中间人更改证书的公钥是不行的。如果想要更改签名,那也没什么意义,因为中间人用证书的公钥将签名解密之后,仅仅得到的是公钥hash之后的散列值,更改散列值有什么意义呢? 所以当我们在非对称传递服务器公钥的过程中,引入了CA证书,那么就能够保证服务器传递公钥的正确性,从而让客户端在接收到公钥后,使用公钥来加密传递对称密钥。
3. 永远记住,由于中间人不可能拥有CA认证的私钥,只有服务器才会有这个私钥,那么中间人对证书做的任何修改,就都是非法的,客户端可以立马察觉到证书的非法性。 至于证书的整体掉包,中间人做这个工作也没啥意义,除了没意义之外,还有风险,毕竟证书里面有各种服务器认证信息,黑客把自己的各种位置信息都暴露吗?他可没那么傻。
2.5.3 非对称加密 对称加密 证书认证
1. 在方案四中增加证书认证就可以解决安全问题了。 即让客户端具有辨别公钥是否合法的能力,如果公钥合法,则就可以使用公钥传递对称密钥至服务器,服务器使用私钥解密拿到对称密钥后,就可以使用对称密钥快速高效的和客户端进行网络通信了。
在浏览器中,我们可以从隐私搜索和服务一栏中找到浏览器内置的各种认证机构所认证的证书。
我随便点开了一个证书,在证书的详细信息中可以看到公钥信息,以及sha256哈希算法生成的数据指纹(数据摘要)。
下面是完整的证书认证 非对称加密 对称加密 通信的完整流程,需要注意的是服务器返回的是包含公钥以及其他各种信息的数字证书。
2.5.4 常见问题
1.为什么摘要内容在⽹络传输的时候⼀定要加密形成签名?
防止中间人对证书进行篡改。使用CA私钥加密后,如果中间人篡改证书中的明文内容或签名或公钥,则客户端使用CA公钥解密后一定能察觉到。
2.为什么签名不直接加密,⽽是要先hash形成摘要?
摘要的长度要比原始文本较小,而文本长度小的话,加密和解密的速度也就会变快,这样可以提升加密文本的传输效率。 同时某些加密算法对加密文本的长度是有要求的,不能过长。
3.如何成为中间⼈(了解即可)?