关注「Java视界」公众号,获取更多技术干货

面试官:HTTP协议你知道多少?

一、Http协议简介

HTTP协议,即超文本传输协议(Hypertext transfer protocol)。HTTP协议通常承载于TCP协议之上,属于TCP/IP模型中应用层的协议,有时也承载于TLS或SSL协议层之上,这个时候,就成了我们常说的HTTPS。

HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。

二、 在浏览器地址栏键入URL,按下回车之后发生了什么?

一般会经历以下流程:

  1. 浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址
  2. 解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接
  3. 浏览器发出读取资源(URL 中域名后面部分对应的资源)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器
  4. 服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器
  5. 释放 TCP连接
  6. 浏览器将该 html 内容进行展示

三、HTTP协议的特点

3.1 无连接

无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。尤其是早期,不和每个用户建立长久的连接,请求一次相应一次,服务端和客户端就中断了,但是后来(http 1.1后)不是直接就断开了,而是等几秒钟,主要是等待用户是否有后续请求操作,若超过特定的时间才会断开,这样的好处就是减少了短时间内建立连接的次数,可以节省时间提高效率。

无连接的特点是因为早期每个客户端与服务器之间交换数据的间歇性较大(即传输具有突发性、瞬时性),大部分通道实际上会很空闲、无端占用资源。因此 HTTP 的设计者有意利用这种特点将协议设计为请求时建连接、请求完释放连接,以尽快将资源释放出来服务其他客户端。

但是随着时间推移及技术发展,网页变得越来越复杂,里面可能嵌入了很多图片,这时候每次访问图片都需要建立一次 TCP 连接就显得很低效。

后来,Keep-Alive 被提出用来解决这个效率低的问题。Keep-Alive 功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive 功能避免了建立或者重新建立连接。这样一来,客户端和服务器之间的 HTTP 连接就会被保持,不会断开(超过 Keep-Alive 规定的时间,意外断电等情况除外),当客户端发送另外一个请求时,就使用这条已经建立的连接。市场上的大部分 Web 服务器,包括 iPlanet、IIS 和 Apache,都支持 HTTP Keep-Alive。

对于提供静态内容的网站来说,这个功能通常很有用。但是,对于负担较重的网站来说,这里存在另外一个问题:虽然为客户保留打开的连接有一定的好处,但它同样影响了性能,因为在处理暂停期间,本来可以释放的资源仍旧被占用。当Web服务器和应用服务器在同一台机器上运行时,Keep-Alive 功能对资源利用的影响尤其突出。

3.2 无状态

 HTTP无状态协议,是指协议对于交互性场景没有记忆能力,http不会为了下次连接所需要的信息而维护这次连接。  但是当同一个浏览器再次给你服务器发送请求的时候,服务器并不知道它就是刚才那个浏览器。 简单的说,服务器不会记得你,所以就是无状态协议。举个例子:小明经常去校门口的超市买东西,老板娘时间久了就会记得他,可能以后会给他优惠,但是Http类似就是老板娘就是得了健忘症一样,每天认为来的顾客都是第一次来。这样显然会有一个好处:不会记录多余的信息,解放服务器。

但是也出现了一个不好的地方:如果后续处理需要前面的信息,那就要重新请求前面的资源,要是业务很复杂,一环套一环那后面的请求要传输的数据量就会很大。

为了弥补无状态下所带来的短板,就出现了Cookie和Session。本文后面会详细说明。

3.3 无连接和无状态的区别与联系

无连接是说的客户端和服务器之间的连接状态,Keep-Alive只是能够保持两者之间建立的TCP连接不断开,这个时候若是客户端再次请求不需要再重新建立TCP连接经历3次握手等过程,但是注意:这时服务器还是没有记录这个客户端的任何信息,服务器不知道客户端是什么状态。也就是说即使TCP连接没有断开,客户端的再次请求在服务器看来也是像第一次请求一样,这说的就是无状态特点。所以无连接说的是客户端与服务器间的TCP连接,无状态说的是服务器不存储客户端每次的请求信息。

四、 HTTP请求参数响应参数

如下,主要的请求响应参数如下:

Genaral通用头: 

Request URL:当前请求的请求地址

Request Method:请求类型 get、post、put、delete等

Status Code:响应状态码 200、404、503等

Remote Address:域名对应的真实ip:port

Request Headers请求头:

Accept:浏览器(或者其他基于HTTP的客户端程序)可以接收的内容类型(Content-types),例如 Accept: text/plain

Accept-Encoding:浏览器可以处理的编码方式,这里的编码方式通常指gzip,deflate等

Accept-Language:浏览器接收的语言,其实也就是用户在什么语言地区,例如简体中文的就是 Accept-Language: zh-CN

Authorization:在HTTP中,服务器可以对一些资源进行认证保护,如果你要访问这些资源,就要提供认证码

Connection:告诉服务器这个user agent(通常就是浏览器)想要使用怎样的连接方式。值有keep-alive和close。http1.1默认是keep-alive。keep-alive就是浏览器和服务器的通信连接会被持续保存,不会马上关闭,而close就会在response后马上关闭。但这里要注意一点,我们说HTTP是无状态的,跟这个是否keep-alive没有关系,不要认为keep-alive是对HTTP无状态的特性的改进。keep-alive是对无连接的改进

Host:初始URL中的主机和端口

Origin: origin主要是用来说明最初请求是从哪里发起的;origin只用于Post请求,而Referer则用于所有类型的请求

Referer:浏览器向web服务器发送请求的时候,referer用来告诉服务器从哪个页面链接过来的

User-Agent: 客户端使用的操作系统和浏览器的名称和版本响应头


Response Headers响应头:

 Access-Control-Allow-Origin:在服务器端设置这个属性来控制允许跨域的域名

五、 Cookie机制

5.1 简介

因为http协议的无状态特点,客户端请求之后服务器就不记得了,这点可以说是双刃剑,好处是简单,对于服务器来说不用存储更多的冗余信息,更节省资源;坏处是当我们需要是哪个客户端在访问或者这个请求来自于谁时,因为没有保留客户端连接相关的信息也就没法区分每次请求的不同之处,会阻碍了交互式应用程序的实现。比如记录用户浏览哪些网页判断用户是否拥有权限访问等,现在的Web应用一般都是要有身份认证才能提供相应的服务,比如你在淘宝买衣服,你必须先登录才能进到相应的页面,由于无状态特性,可能你每进入一个页面就要登录一次,从商品列表进入商品详情要登录一下,点击购买进入订单页面要登录一下,进入支付再登录一下,...这哪里是在愉快的购物啊,简直是折磨。

为了解决上面的不足诞生了会话机制,用来跟踪用户的整个会话。理论上,一个用户的所有请求操作都属于同一个会话,而另一个用户的所有请求操作则应属于另一个会话,二者不能混淆。若没有会话机制,即用户A购买了一件商品放入购物车内,当再次购买商品时服务器已经无法判断该购买行为是属于用户A的还是用户B的会话了。但是有了会话机制,用户A在超市购买的任何商品都应该放在A购物车内,不论用户A是什么时间购买的,这都是属于同一个会话,不能放入用户B或用户C的购物车里面。

目前实现会话机制主要有两种技术:Cookie 和 session。

先看Cookie,下面是我在浏览器截取的cookies:

5.2 什么是Cookie?

Cookie就好比最近疫情期间小区物业发的“出入证”,小区住那么多人物业门卫不可能记住每个人,那怎么办,就给每个人发一个出入证吧,每人一个,无论谁访问都必须携带自己的出入证这样门卫就能从出入证上确认每个人的身份了。这就是Cookie的工作原理的比喻。

实际上,就是服务器给客户端返回数据的时候,中间加了一个标识, 客户端接收后自行保存,然后客户端再次请求数据的时候,数据中带上这个标识, 那么服务器接收到请求消息时就知道这个请求来自于谁了(相当于服务器接收到请求时,如果没有带识别码,生成一个识别码给客户端, 如果有识别码,就把这个识别码需要的对应内容返回给客户端)。

所以Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再次请求该网站时,浏览器把请求的网址连同该Cookie一同发送给服务器。服务器检查该Cookie,以此来辨别用户状态。服务器还可以根据需要修改Cookie的内容。

5.3 Cookie工作流程

1. 客户端第一次请求服务器,请求头如下:

    GET / HTTP/1.0

    HOST: xxx.com

2. 服务器第一次响应请求

    Cookie是一种key=value形式的字符串,服务器需要记录这个客户端请求的状态,因此在响应头中包一个Set-Cookie字段。响应头如下:

    HTTP/1.0 200 OK

    Set-Cookie: UserID=itbilu; Max-Age=3600; Version=1

    Content-type: text/html

    ……

3. 再次请求时,客户端请求中会包含一个Cookie请求头

客户端会对服务器响应的Set-Cookie头信息进行存储。再次请求时,将会在请求头中包含服务器响应的Cookie信息。请求头如下

    GET / HTTP/1.0

    HOST: xxx.com

    Cookie: UserID=itbilu

5.4 Cookie类

上面的源码可以知道Cookie对象的全部属性:

  1. name:该Cookie的名称。Cookie一旦创建,名称便不可更改
  2. value:该Cookie的值。如果值为Unicode字符,需要为字符编码。如果值为二进制数据,则需要使用BASE64编码
  3. maxAge:该Cookie失效的时间,单位秒。如果为正数,则该Cookie在maxAge秒之后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为–1
  4. secure:该Cookie是否仅被使用安全协议传输。安全协议。安全协议有HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false
  5. path:该Cookie的使用路径。如果设置为“/sessionWeb/”,则只有contextPath为“/sessionWeb”的程序可以访问该Cookie。如果设置为“/”,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”
  6. domain:可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“google.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”
  7. comment:该Cookie的用处说明。浏览器显示Cookie信息的时候显示该说明
  8. version:该Cookie使用的版本号。0表示遵循Netscape的Cookie规范,1表示遵循W3C的RFC 2109规范

Cookie对象使用key-value属性对的形式保存用户状态,一个Cookie保存一个属性对,一个request或者response同时使用多个Cookie。

5.5 Cookie的修改和删除

Cookie修改,如果要修改某个Cookie,只需要新建一个同名的Cookie,添加到response中覆盖原来的Cookie。

Cookie删除,只需要新建一个同名的Cookie,并将maxAge设置为0,并添加到response中覆盖原来的Cookie。注意是设置为0不是负数。负数代表关闭窗口就消失。

注意:修改、删除Cookie时,新建的Cookie除value、maxAge之外的所有属性,例如name、path、domain等,都要与原Cookie完全一样。否则,浏览器将视为两个不同的Cookie不予覆盖,导致修改、删除失败。

5.6 Cookie的不可跨域性

cookie对象都有一个domain属性,表明可以访问该Cookie的域名,Cookie是不可跨域名的。域名www.google.com颁发的Cookie不会被提交到域名www.baidu.com去。这是由Cookie的隐私安全机制决定的。隐私安全机制能够禁止网站非法获取其他网站的Cookie。

正常情况下,同一个一级域名下的两个二级域名如www.helloweenvsfei.com和 images.helloweenvsfei.com也不能交互使用Cookie,因为二者的域名并不严格相同。如果想所有gelloweenvsfei.com名下的二级域名都可以使用该Cookie,需要设置Cookie的domain参数。

name相同但domain不同的两个Cookie是两个不同的Cookie。如果想要两个域名完全不同的网站共有Cookie,可以生成两个Cookie,domain属性分别为两个域名,输出到客户端。

5.7 Cookie的安全属性

HTTP协议是无状态的,而且是不安全的。使用HTTP协议的数据不经过任何加密就直接在网络上传播,有被截获的可能。使用HTTP协议传输很机密的内容是一种隐患。如果不希望Cookie在HTTP等非安全协议中传输,可以设置Cookie的secure实行为true。浏览器只会在HTTPS和SSL等安全协议中传输此类Cookie。

注意:secure属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密,以防泄密。

 六、 Session机制

Session是另一种记录客户状态的机制,同样是为了克服HTTP的无状态性,不同的是Cookie保存在客户端浏览器中,Session保存在服务器上。

客户端浏览器访问服务器的时候,服务端把客户端信息以某种形式记录在服务器上,这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。如果说Cookie机制是通过检查客户身上的"通行证"来确定客户身份的话,那么Session及时就是通过检查服务器上的"客户明细表"来确认身份。Session相当于程序在服务器上建立的一份客户档案,客户来访时只需要检查客户的档案表就可以了。

6.1 Session的工作机制

实际上Session一般也是和Cookie结合起来使用的,流程如下:

1、当客户端第一次请求session对象时,服务器会创建一个session,并通过特殊算法算出一个session的ID,用来标识该session对象,然后将这个session序列放置到set-cookie中发送给浏览器

2、浏览器下次发请求的时候,这个sessionID会被放置在请求头中,和cookie一起发送回来

3、服务器再通过内存中保存的sessionID跟cookie中保存的sessionID进行比较,并根据ID在内存中找到之前创建的session对象,提供给请求使用,也就是服务器会通过session保存一个状态记录,浏览器会通过cookie保存状态记录,服务器通过两者的对比实现跟踪状态,这样的做,也极大的避免了cookie被篡改而带来的安全性问题

到这里你肯定会问,要是浏览器不支持Cookie怎么办(虽然大部分浏览器都支持Cookie),按照上面的流程没有了Cookie就没办法实现Session了。实际上,Session还有一个种工作方式,若没有Cookie,还可以利用URL重写,就是把session id直接附加在URL路径的后面,附加方式也有两种,一种是作为URL路径的附加信息,另一种是作为查询字符串附加在URL后面。本文后面再说明。

6.2 Session类

Session对应的类为javax.servlet.http.HttpSession接口,以下是常用方法。

  1. setAttribute(String name, Object value) 设置Session属性。value参数可以为任何Java Object。通常为Java Bean,value信息不宜过大
  2. getAttribute(String name)返回Session属性
  3. getAttributeNames()返回Session中存在的属性名
  4. removeAttribute(String name)移除Session属性
  5. getId()返回Session的ID。该ID由服务器自动创建,不会重复
  6. getCreationTime()返回Session的创建日期。返回类型为long,常被转化为Date类型
  7. getLastAccessedTime()返回Session的最后活跃时间。返回类型为long
  8. getMaxInactiveInterval()返回Session的超时时间。单位为秒。超过该时间没有访问,服务器认为该Session失效
  9. isNew()返回该Session是否是新创建的
  10. invalidate()使该Session失效

6.3 Session属性设置

Session对象是在客户端第一次请求服务器的时候创建的。 创建方式如下:

//创建session对象
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = request.getSession();
System.out.println("session.getId(): " + session.getId());
System.out.println("session.getAttributeNames(): " + session.getAttributeNames());
System.out.println("session.getCreationTime(): " + session.getCreationTime());
System.out.println("session.getLastAccessedTime(): " + session.getLastAccessedTime());
System.out.println("session.getMaxInactiveInterval(): " +session.getMaxInactiveInterval());
System.out.println("session.getServletContext(): " + session.getServletContext());
session.getId(): 5FAB534DE1BB362E739F208498578A7C
session.getAttributeNames(): java.util.Collections$3@4639027
session.getCreationTime(): 1590634891473
session.getLastAccessedTime(): 1590634942162
session.getMaxInactiveInterval(): 1800
session.getServletContext(): org.apache.catalina.core.ApplicationContextFacade@aa46edb

Session也是一种key-value的属性对,通过getAttribute(Stringkey)和setAttribute(String key,Objectvalue)方法读写客户状态信息。如下:

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = request.getSession();
session.setAttribute("loginTime", new Date());
System.out.println("登录时间:" + session.getAttribute("loginTime"));
登录时间:Thu May 28 11:11:26 CST 2020

6.4 Session生命周期及有效期

Session保存在服务器端。为了获得更高的存取速度,服务器一般吧Session放在内存里。每个用户都会有一个独立的Session。如果Session内容过于复杂,当大量客户访问服务器时可能导致内存溢出。因此,Session里的信息尽量精简。

Session在用户第一次访问服务器的时候自动创建。需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session。如果尚未生成Session,也可能使用request.getSession(true)强制生成Session。

Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。用户每访问一次,无论是否读写Session,服务器都认为该用户的Session活跃(active)了一次。

由于有越来越多的用户访问服务器,因此Session也会越来越多。为防止内存溢出,服务器会把长时间没有活跃的Session从内存中删除。这个时间就是Session的超时时间。如果超过了超时时间没有访问过服务器,Session就自动失效。

Session的超时时间为maxUnactiveInterval属性,可以通过getMacInactiveInterval()获取,通过setMaxInacyiveInterval(longinterval)修改。

Session的超时时间也可以在web.xml中修改。另外,通过调用Session的invalidate()方法可以是Session失效。

6.5 URL地址重写

前面说过,URl地址重写是对客户端不支持Cookie的解决方案。

URL地址重写的原理是将该用户Session的id信息重写到URL地址中。服务器能够顾解析重写后的URL获取Session的id。这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。HttpServletResponse类提供了encodeURL(Stringurl)实现URL地址重写。

<td>
    <a href="<%=response.encodeURL("index.jsp?c=1&wd=Java") %>"> 
    Homepage</a>
</td>

即在文件名的后面,在URL参数的前面添加了字符串";jsessionid=xxx"。其中xxx为Session的id。分分析一下可以知道,添加的jsessinid字符串即不会影响请求的文件名,也不会影响提交的地址栏参数。用户单击这个链接的时候会把Session的id通过URL提交到服务器上,服务器听过解析URL地址获得Session的id。 

TOMCAT服务器判断客户端浏览器是否支持Cookie的依据是请求中是否含有Cookie。尽管客户端可能会支持Cookie,但是由于第一次请求时不会携带任何Cookie(因为并无任何Cookie可以携带),URL地址重写后的地址中仍然会带有jsessionid。当第二次访问时服务器已经在浏览器中写入Cookie了,因此URL地址重写后的地址中就不会带有jsessionid了。

6.6 Session对浏览器的要求

虽然Session保存在服务器,对客户端是透明的,他的正常运行任然需要客户端浏览器的支持。这是因为Session需要使用Cookie作为识别标志。HTTP协议是无状态的,Session不能依据HTTP链接来判断是否为同一客户,因此服务器先给客户端浏览器发送一个名为JSESSIONID的Cookie,他的值为该Session的id(也就是HTTPSession.getId()的返回值)。Session依据Cookie来识别是否为同一用户。

该Cookie为服务器自动生成的,它的maxAge属性一般为-1,表示仅当前浏览器内有效,并且个浏览器窗口间不共享,关闭浏览器就会失效。

因此同一机器的两个浏览器窗口访问服务器时,会生成两个不同的Session、但是由浏览器窗口内的链接、脚本等打开的新窗口(也就是说不是双击=桌面浏览器图标打开的窗口)除外。这类子窗口会共享父窗口的Cookie,因此会共享一个Session。

 6.7 Session中禁止使用Cookie

Java Web规范支持通过配置的方式禁用Cookie。下面举例说一下怎样通过配置禁止使用Cookie。

打开项目sessionWeb的WebRoot目录下的META-INF文件夹(跟WEB-INF文件夹同级,如果没有则创建),打开context.xml(如果没有则创建),编辑内容如下:

<?xml version='1.0' encoding='UTF-8'?>

<Context path="/sessionWeb"cookies="false">

</Context>

或者修改Tomcat全局的conf/context.xml,修改内容如下:

<!-- The contents of this file will be loaded for eachweb application -->

<Context cookies="false">

    <!-- ... 中间代码略 -->

</Context>

部署后TOMCAT便不会自动生成名JSESSIONID的Cookie,Session也不会以Cookie为识别标志,而仅仅以重写后的URL地址为识别标志了。

注意:该配置只是禁止Session使用Cookie作为识别标志,并不能阻止其他的Cookie读写。也就是说服务器不会自动维护名为JSESSIONID的Cookie了,但是程序中仍然可以读写其他的Cookie。

七、cookie 和session 的区别

上面已经说了cookie 和session 的联系,下面总结下两者的区别:

1、cookie数据存放在客户的客户端上,session数据放在客户端上。
2、cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗
考虑到安全应当使用session。
3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
考虑到减轻服务器性能方面,应当使用cookie。
4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

八、分布式session处理

8.1 问题引出

单服务器web应用中,session信息只需存在该服务器中,这是我们前几年最常接触的方式,但是近几年随着分布式系统的流行,单系统已经不能满足日益增长的百万级用户的需求,集群方式部署服务器已在很多公司运用起来,当高并发量的请求到达服务端的时候通过负载均衡的方式分发到集群中的某个服务器,这样就有可能导致同一个用户的多次请求被分发到集群的不同服务器上,就会出现取不到session数据或者session数据不一致的情况,于是session的共享就成了一个问题。

假如session是保存在服务器1上的,这时同一个用户的请求若被反向代理路由到其他服务器上,因为只有服务器1保存了session,这时就会导致应该用户需要重新登录。

8.2 session分布式解决方案

方案一:session同步

含义:每台服务器之间互相同步session,使得每台服务器都拥有所有的session

好处:

  1. 简单易实现,不需要修改姓名代码
  2. 即使某台服务器宕机也不会有影响

缺陷:

  1. 服务器间同步session数据会占用网路资源,且网络不好时有延时
  2. 每台服务器都保存所有session,会占用很多内存

方案二:客户端存储

含义:服务器session会占很多内存,可以将session存储到客户端的cookies中

好处:

  1. 减轻了服务器的压力
  2. 简单易实现

缺陷:

  1. 客户端每次都要带上session进行请求,传输数据增多,会占用网络带宽
  2. session只存储在cookie中很容易被篡改或窃取
  3. 因为cookie的大小是有限制的,因此session要存在cookie中也需要精简,大小会有限制

方案三:反向代理Hash一致性

含义:就是通过反向代理让同一个用户的请求都路由到同一个服务器上,这样只需要在这台session即可

实现:有两种实现

  1. 反向代理层使用用户ip来做hash,以保证同一个ip的请求落在同一个服务器上
  2. 反向代理使用http协议中的某些唯一的业务属性来做hash,例如sid,city_id,user_id等,能够更加灵活的实施hash策略,以保证同一个浏览器用户的请求落在同一个web-server上

好处:

  1. 只需要改nginx配置,不需要修改项目代码

  2. 兼顾负载均衡,只要hash属性是均匀的,多台服务器的负载是均衡的

缺陷:

  1. 如果某台服务器重启,一部分session会丢失,产生业务影响,例如部分用户重新登录;但是和session有效期过了影响一样,不会造成重大线上事故

方案4:集中存储

含义:将session集中存储在数据库或者缓存

实现:有两种实现

  1. 数据库存储(MySQL等)
  2. 缓存(Redis等)

好处:

  1. session数据将更安全

  2. 服务器重启或扩容也不丢失

缺陷:

  1. 增加了一次网络调用,并且需要修改应用代码

总结:

前两种方法都是易实现和操作的,不考虑其缺陷的可以采用前两种方案。

方案3当然看起来更“智能”,让方向代理“不务正业”了一把,尤其还要引入sid,user_id等业务信息,实际上已经造成了耦合,真要采用方案3,最好还是使用客户端的ip去实现。

方案四也是不错的方案,也是最安全的方案,但因session是命中率很高的数据,经常会使用,所以还是使用Redis等缓存更合适。

九、HTTPS 的实现原理

随着 HTTPS 建站的成本下降,现在大部分的网站都已经开始用上 HTTPS 协议。大家都知道 HTTPS 比 HTTP 安全,也听说过与 HTTPS 协议相关的概念有 SSL 、非对称加密、 CA 证书等,但对于以下灵魂三拷问可能就答不上了:

  1. 为什么用了 HTTPS 就是安全的?

  2. HTTPS 的底层原理如何实现?

  3. 用了 HTTPS 就一定安全吗?

本文将层层深入,从原理上把 HTTPS 的安全性讲透。

介绍前先了解下对称加密、对称加密相关知识。

9.1 对称加密、对称加密

情景

一对异地恋人,尽管相距很远,但是却不影响彼此之间的爱慕之情,彼此喜欢通过网络来交流。

对称加密

一天男要给女发送一份非常私密的内容。男不希望通讯过程中被人截取而泄密秘密。这个时候,自然想到的方法就是对通讯内容进行加密,当然除了加密外,还需要让礼能够解密。

男认为可以生成 一个秘钥 ,然后用这个秘钥对内容进行加密,然后把生成的秘钥和加密的算法告诉女,然后她再用我告诉她的秘钥和加密算法进行解密。

上面的加密解密过程就属于对称加密,需要一个秘钥和一个对称加密算法。

常见的对称加密算法有:

  • DES
  • 3DES
  • AES

非对称加密

男把自己的想法告诉了女,女听完后,提出了一个问题——“你怎么把秘钥给我”。男想了会儿,的确,不可能通过网络发给她,万一被人把你的秘钥被人偷看了怎么办。

于是男想到了非对称加密。指的是加、解密使用不同的密钥,一把作为公开的公钥,另一把作为私钥。公钥加密的信息,只有私钥才能解密。反之,私钥加密的信息,只有公钥才能解密。 

非对称加密算法 不同于 对称加密,它有一个公钥(publicKey)和一个 私钥(privateKey)并且只知道公钥是无法推算出私钥。 通过公钥加密的内容,只有私钥才可以解开,而通过私钥加密的内容,只有公钥才可以解开。

非对称加密,需要一对秘钥和一个非对称加密算法。 常见的非对称加密算法有:

  • RSA
  • DSA 

加密流程是:

  • 首先生成一对秘钥,然后把公钥发给对方。公钥被看到也没有关系。
  • 然后,接收方拿到公钥后,对内容进行加密,然后传给你,利用通过公钥加密的内容,只有私钥才可以解开的特性,只有持有私钥的一方才能解开。
  • 最后,收到公钥后就可以通过私钥来解开发送的内容了。

举个例子,你向某公司服务器请求公钥,服务器将公钥发给你,你使用公钥对消息加密,那么只有私钥的持有人才能对你的消息解密。私钥只能由一方保管,不能外泄。公钥可以交给任何请求方。与对称加密不同的是,公司服务器不需要将私钥通过网络发送出去,因此安全性大大提高。

非对称加密优缺点:安全性更高,公钥是公开的,密钥是自己保存的,不需要将私钥给别人。缺点:加密和解密花费时间长、速度慢,只适合对少量数据进行加密。对称加密相比非对称加密算法来说,加解密的效率要高得多、加密速度快,但安全性差。

9.2 HTTPS 的实现原理

大家可能都听说过 HTTPS 协议之所以是安全的是因为 HTTPS 协议会对传输的数据进行加密,而加密过程是使用了非对称加密实现。但其实,HTTPS 在内容传输的加密上使用的是对称加密,非对称加密只作用在证书验证阶段。

HTTPS 的整体过程分为证书验证和数据传输阶段,具体的交互过程如下:

① 证书验证阶段

  1. 浏览器发起 HTTPS 请求

  2. 服务端返回 HTTPS 证书

  3. 客户端验证证书是否合法,如果不合法则提示告警

② 数据传输阶段

  1. 当证书验证合法后,在本地生成随机数

  2. 通过公钥加密随机数,并把加密后的随机数传输到服务端

  3. 服务端通过私钥对随机数进行解密

  4. 服务端通过客户端传入的随机数构造对称加密算法,对返回结果内容进行加密后传输

为什么数据传输是用对称加密?

首先,非对称加密的加解密效率是非常低的,而 http 的应用场景中通常端与端之间存在大量的交互,非对称加密的效率是无法接受的;

另外,在 HTTPS 的场景中只有服务端保存了私钥,一对公私钥只能实现单向的加解密,所以 HTTPS 中内容传输加密采取的是对称加密,而不是非对称加密。

 为什么需要 CA 认证机构颁发证书?

HTTP 协议被认为不安全是因为传输过程容易被监听者勾线监听、伪造服务器,而 HTTPS 协议主要解决的便是网络传输的安全性问题。

首先我们假设不存在认证机构,任何人都可以制作证书,这带来的安全风险便是经典的 “中间人攻击” 问题。
“中间人攻击” 的具体过程如下:

过程原理:

  1. 本地请求被劫持(如 DNS 劫持等),所有请求均发送到中间人的服务器

  2. 中间人服务器返回中间人自己的证书

  3. 客户端创建随机数,通过中间人证书的公钥对随机数加密后传送给中间人,然后凭随机数构造对称加密对传输内容进行加密传输

  4. 中间人因为拥有客户端的随机数,可以通过对称加密算法进行内容解密

  5. 中间人以客户端的请求内容再向正规网站发起请求

  6. 因为中间人与服务器的通信过程是合法的,正规网站通过建立的安全通道返回加密后的数据

  7. 中间人凭借与正规网站建立的对称加密算法对内容进行解密

  8. 中间人通过与客户端建立的对称加密算法对正规内容返回的数据进行加密传输

  9. 客户端通过与中间人建立的对称加密算法对返回结果数据进行解密

由于缺少对证书的验证,所以客户端虽然发起的是 HTTPS 请求,但客户端完全不知道自己的网络已被拦截,传输内容被中间人全部窃取。

浏览器是如何确保 CA 证书的合法性?

1. 证书包含什么信息?

  • 颁发机构信息

  • 公钥

  • 公司信息

  • 域名

  • 有效期

  • 指纹

  • ……

2. 证书的合法性依据是什么?

首先,权威机构是要有认证的,不是随便一个机构都有资格颁发证书,不然也不叫做权威机构。另外,证书的可信性基于信任制,权威机构需要对其颁发的证书进行信用背书,只要是权威机构生成的证书,我们就认为是合法的。所以权威机构会对申请者的信息进行审核,不同等级的权威机构对审核的要求也不一样,于是证书也分为免费的、便宜的和贵的。

3. 浏览器如何验证证书的合法性?

浏览器发起 HTTPS 请求时,服务器会返回网站的 SSL 证书,浏览器需要对证书做以下验证:

(1)验证域名、有效期等信息是否正确。证书上都有包含这些信息,比较容易完成验证;

(2)判断证书来源是否合法。每份签发证书都可以根据验证链查找到对应的根证书,操作系统、浏览器会在本地存储权威机构的根证书,利用本地根证书可以对对应机构签发证书完成来源验证;

(3)判断证书是否被篡改。需要与 CA 服务器进行校验;

(4)判断证书是否已吊销。通过 CRL(Certificate Revocation List 证书注销列表)和 OCSP(Online Certificate Status Protocol 在线证书状态协议)实现,其中 OCSP 可用于第 3 步中以减少与 CA 服务器的交互,提高验证效率

以上任意一步都满足的情况下浏览器才认为证书是合法的。

这里插一个我想了很久的但其实答案很简单的问题:
既然证书是公开的,如果要发起中间人攻击,我在官网上下载一份证书作为我的服务器证书,那客户端肯定会认同这个证书是合法的,如何避免这种证书冒用的情况?
其实这就是非加密对称中公私钥的用处,虽然中间人可以得到证书,但私钥是无法获取的,一份公钥是不可能推算出其对应的私钥,中间人即使拿到证书也无法伪装成合法服务端,因为无法对客户端传入的加密数据进行解密。

4. 只有认证机构可以生成证书吗?

 如果需要浏览器不提示安全风险,那只能使用认证机构签发的证书。但浏览器通常只是提示安全风险,并不限制网站不能访问,所以从技术上谁都可以生成证书,只要有证书就可以完成网站的 HTTPS 传输。例如早期的 12306 采用的便是手动安装私有证书的形式实现 HTTPS 访问。

 本地随机数被窃取怎么办?

证书验证是采用非对称加密实现,但是传输过程是采用对称加密,而其中对称加密算法中重要的随机数是由本地生成并且存储于本地的,HTTPS 如何保证随机数不会被窃取?

其实 HTTPS 并不包含对随机数的安全保证,HTTPS 保证的只是传输过程安全,而随机数存储于本地,本地的安全属于另一安全范畴,应对的措施有安装杀毒软件、反木马、浏览器升级修复漏洞等。

用了 HTTPS 会被抓包吗?

HTTPS 的数据是加密的,常规下抓包工具代理请求后抓到的包内容是加密状态,无法直接查看。

但是,正如前文所说,浏览器只会提示安全风险,如果用户授权仍然可以继续访问网站,完成请求。因此,只要客户端是我们自己的终端,我们授权的情况下,便可以组建中间人网络,而抓包工具便是作为中间人的代理。通常 HTTPS 抓包工具的使用方法是会生成一个证书,用户需要手动把证书安装到客户端中,然后终端发起的所有请求通过该证书完成与抓包工具的交互,然后抓包工具再转发请求到服务器,最后把服务器返回的结果在控制台输出后再返回给终端,从而完成整个请求的闭环。

既然 HTTPS 不能防抓包,那 HTTPS 有什么意义?
HTTPS 可以防止用户在不知情的情况下通信链路被监听,对于主动授信的抓包操作是不提供防护的,因为这个场景用户是已经对风险知情。要防止被抓包,需要采用应用级的安全防护,例如采用私有的对称加密,同时做好移动端的防反编译加固,防止本地算法被破解。

总结

以下用简短的 Q&A 形式进行全文总结:

Q: HTTPS 为什么安全?
A: 因为 HTTPS 保证了传输安全,防止传输过程被监听、防止数据被窃取,可以确认网站的真实性。

Q: HTTPS 的传输过程是怎样的?
A: 客户端发起 HTTPS 请求,服务端返回证书,客户端对证书进行验证,验证通过后本地生成用于改造对称加密算法的随机数,通过证书中的公钥对随机数进行加密传输到服务端,服务端接收后通过私钥解密得到随机数,之后的数据交互通过对称加密算法进行加解密。

Q: 为什么需要证书?
A: 防止” 中间人 “攻击,同时可以为网站提供身份证明。

Q: 使用 HTTPS 会被抓包吗?
A: 会被抓包,HTTPS 只防止用户在不知情的情况下通信被监听,如果用户主动授信,是可以构建 “中间人” 网络,代理软件可以对传输内容进行解密。

十、HTTP和HTTPS的区别与联系

HTTPS协议可以理解为HTTP协议的升级,就是在HTTP的基础上增加了数据加密。

HTTPS 协议(HyperText Transfer Protocol over Secure Socket Layer):一般理解为HTTP+SSL/TLS,通过 SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密。

在数据进行传输之前,对数据进行加密,然后再发送到服务器。这样,就算数据被第三者所截获,但是由于数据是加密的,所以你的个人信息让然是安全的。这就是HTTP和HTTPS的最大区别。

当然,还有一些细小的区别:

(1)当你使用Chrome浏览器访问一个HTTP网站的时候,你会发现浏览器会对该HTTP网站显示“不安全”的安全警告,提示用户当前所访问的网站可能会存在风险。而假如你访问的是一个HTTPS网站时则不会提示。

(2)HTTPS网站将会作为搜索排名的一个重要权重指标。也就是说HTTPS网站比起HTTP网站在搜索排名中更有优势。

十、跨域

出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。

什么是跨域?

当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。

 java 后端 实现 CORS 跨域请求的方式

  1. 返回新的CorsFilter

  2. 重写 WebMvcConfigurer

  3. 使用注解 @CrossOrigin

  4. 手动设置响应头 (HttpServletResponse)

  5. 自定web filter 实现跨域

注意:

  • CorFilter / WebMvConfigurer / @CrossOrigin 需要 SpringMVC 4.2以上版本才支持,对应springBoot 1.3版本以上

  • 上面前两种方式属于全局 CORS 配置,后两种属于局部 CORS配置。如果使用了局部跨域是会覆盖全局跨域的规则,所以可以通过 @CrossOrigin 注解来进行细粒度更高的跨域资源控制。

  • 其实无论哪种方案,最终目的都是修改响应头,向响应头中添加浏览器所要求的数据,进而实现跨域

1.返回新的 CorsFilter(全局跨域)

在任意配置类,返回一个 新的 CorsFIlter Bean ,并添加映射路径和具体的CORS配置路径。

@Configuration
public class GlobalCorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        //1. 添加 CORS配置信息
        CorsConfiguration config = new CorsConfiguration();
        //放行哪些原始域
        config.addAllowedOrigin("*");
        //是否发送 Cookie
        config.setAllowCredentials(true);
        //放行哪些请求方式
        config.addAllowedMethod("*");
        //放行哪些原始请求头部信息
        config.addAllowedHeader("*");
        //暴露哪些头部信息
        config.addExposedHeader("*");
        //2. 添加映射路径
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        corsConfigurationSource.registerCorsConfiguration("/**",config);
        //3. 返回新的CorsFilter
        return new CorsFilter(corsConfigurationSource);
    }
}

2. 重写 WebMvcConfigurer(全局跨域)

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                //是否发送Cookie
                .allowCredentials(true)
                //放行哪些原始域
                .allowedOrigins("*")
                .allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"})
                .allowedHeaders("*")
                .exposedHeaders("*");
    }
}

3. 使用注解 (局部跨域)

在控制器(类上)上使用注解 @CrossOrigin:,表示该类的所有方法允许跨域。

@RestController
@CrossOrigin(origins = "*")
public class HelloController {
    @RequestMapping("/hello")
    public String hello() {
        return "hello world";
    }
}

在方法上使用注解 @CrossOrigin:

@RequestMapping("/hello")
@CrossOrigin(origins = "*")
//@CrossOrigin(value = "http://localhost:8081") //指定具体ip允许跨域
public String hello() {
   return "hello world";
}

4. 手动设置响应头(局部跨域)

使用 HttpServletResponse 对象添加响应头(Access-Control-Allow-Origin)来授权原始域,这里 Origin的值也可以设置为 “*”,表示全部放行。

@RequestMapping("/index")
public String index(HttpServletResponse response) {
    response.addHeader("Access-Allow-Control-Origin","*");
    return "index";
}

5. 使用自定义filter实现跨域

首先编写一个过滤器,可以起名字为MyCorsFilter.java

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
@Component
public class MyCorsFilter implements Filter {
  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    HttpServletResponse response = (HttpServletResponse) res;
    response.setHeader("Access-Control-Allow-Origin", "*");
    response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
    response.setHeader("Access-Control-Max-Age", "3600");
    response.setHeader("Access-Control-Allow-Headers", "x-requested-with,content-type");
    chain.doFilter(req, res);
  }
  public void init(FilterConfig filterConfig) {}
  public void destroy() {}
}

在web.xml中配置这个过滤器,使其生效

<!-- 跨域访问 START-->
<filter>
 <filter-name>CorsFilter</filter-name>
 <filter-class>com.mesnac.aop.MyCorsFilter</filter-class>
</filter>
<filter-mapping>
 <filter-name>CorsFilter</filter-name>
 <url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 跨域访问 END  -->

十一、 http1和http2的区别

1、http2采用二进制格式而非文本格式。

2、http2是完全多路复用的,而非有序并阻塞的。

3、http2采用了报头压缩,降低了成本。

4、http2让服务器可以主动将响应推送到客户端缓存中

5、安全:由于安全已经成为重中之重,所以 HTTP2.0 一般都跑在 HTTPS 上

http2为什么要使用二进制?

比起文本协议,二进制协议解析起来更高效、错误更少、更紧凑

http为什么要多路复用?

http1有个问题叫做线端阻塞,它是指一个链接一次只提交一个请求,这样效率比较高,多了就会变慢了。 http1有试过用流水线来解决这个问题,但是效果并不理想,一个数据量大或者速度慢的响应,会阻碍排在它后面的请求,此外,由于网络媒介和服务器不能很好的支持流水线,导致部署起来困难重重。而多路传输能很好的解决这个问题,因为它能同时处理多个消息的请求和响应,甚至可以在传输过程中将一个消息跟另一个消息混杂在一起,所以客户端只需要一个链接就能加载一个页面。

消息头为什么要压缩?

假定一个页面有很多个资源需要加载, 而每一次请求都有很大的消息头, 至少要7到8个来回去“在线”获得这些消息头。这还不包括响应时间——那只是从客户端那里获取到它们所花的时间而已。这全都由于TCP的慢启动机制,它会基于对已知有多少个包,来确定还要来回去获取哪些包 – 这很明显的限制了最初的几个来回可以发送的数据包的数量。相比之下,即使是头部轻微的压缩也可以是让那些请求只需一个来回就能搞定——有时候甚至一个包就可以了。这种开销是可以被节省下来的,特别是当你考虑移动客户端应用的时候,即使是良好条件下,一般也会看到几百毫秒的来回延迟。 说白了就是为了速度更快,消耗的资源更少。

十二、SOCKET

socket属于网络的那个层面?

网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket。Socket通常用来实现客户方和服务方的连接。Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。

socket连接就是所谓的长连接,客户端和服务器需要互相连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉的,但是有时候网络波动还是有可能的。

在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。Socket偏向于底层。一般很少直接使用Socket来编程,框架底层使用Socket比较多。在设计模式中,Socket其实就是一个外观模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

Socket适用场景:网络游戏,银行持续交互,直播,在线视屏等。http连接就是所谓的短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断开等待下次连接。因此,http适用场景:公司OA服务,互联网服务,电商,办公,网站等等。

 补充一、在微服务中你是如何实现不同服务间session 共享的?

在微服务中,一个完整的项目被拆分成多个不相同的独立的服务,各个服务独立部署在不同的服务器上,各自的 session 被从物理空间上隔离开了,但是经常,我们需要在不同微服务之间共享 session。

常见的方案就是 Spring Session + Redis 来实现 session 共享,也就是前文介绍的方案4的一种形式。将所有微服务的 session 统一保存在 Redis 上,当各个微服务对 session 有相关的读写操作时,都去操作 Redis 上的 session 。这样就实现了session 共享,Spring Session 基于 Spring 中的代理过滤器实现,使得 session 的同步操作对开发人员而言是透明的,非常简便。

同时,Spring Session已经集成了redis,可以很方便的将session存到redis中从而实现单点登陆/登出的效果,但是从微服务的角度来说,为了降低系统间的耦合度,一般会单独建一个Redis服务来搞session共享。

1、pom 文件中引入以下包

<!--spring boot 与redis应用基本环境配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring session 与redis应用基本环境配置 -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

2、application.properties配置好 redis

spring.redis.database = 0
spring.redis.host = 192.168.xx.xx
spring.redis.port = 6379
spring.redis.password = test
spring.redis.pool.max-active = 200
spring.redis.pool.max-wait = -1
spring.redis.pool.max-idle = 10
spring.redis.pool.min-idle = 0
spring.redis.pool.timeout = 1000

在需要共享 session 的服务的启动类上,加上 @EnableRedisHttpSession 注解即可:

@EnableRedisHttpSession
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
public class PhoneApplication {
    public static void main(String[] args) {
        SpringApplication.run(PhoneApplication.class, args);
    }
}

 补充二、什么是DoS、DDoS、DRDoS攻击?如何防御?

(1)

DoS(Denial of Service,拒绝服务攻击),它的原理很简单,就是用我们手里的机器去给服务器发请求,如果我们手头的服务器各方面性能都比服务器的主机的性能好,那么当我们发送大量请求给服务器,占用服务器的资源,导致服务器没有能力去处理其他用户请求。

防御:

第一种方法,缩短等待时间,尽早删除在等待队列中等待非法请求数据包;第二种方法是在第一次握手报文不放入等待队列,我们将一个32位的无符号整数作为第二次握手的Seq返回给客户端,该整数通过将用户请求的参数(包括请求的地址、端口等),加上服务器的一个序列号等做了一次运算得到。如果是正常用户,它在收到这个整数之后加1作为ACK返回给服务器,服务器在拿到数据后,对这个ACK减1再进行逆运算得到各个参数,最后发出去的数据进行比对,如果完全一致,说明是一个有效的响应,存放到相应的数据结构,反之则不是。通过该方法,非法请求就不能占用系统资源了。

(2)

DDoS(Distributed Denial of Service,分布式拒绝服务),它是DoS家族里很难防范的一种攻击方式,攻击者首先控制大量肉鸡,然后向目标服务器发送海量请求,导致目标服务器不可用。这里我们不禁要问攻击者是如何获取大量肉鸡的呢?攻击者会对某些APP或网站植入一些恶意代码,譬如说curl www.mukedada.com,用户在使用这款APP或网站时,会自动请求www.mukedada.com这个网站,如果这款APP或网站的活跃用户数很多,那么这个网站就会受到很多莫名的请求。这就要求我们在平时上网过程中不要浏览一些不知名的网站和下载来源不明的APP。

防御:

首先,作为用户我们尽量从正规渠道下载APP以及文明上网,尽量不让自己做肉鸡;其次,作为服务厂家,当服务器受到DDoS攻击时,我们尽量提升服务器的处理能力,当我们服务器的处理能力大于攻击者的能力时,这些攻击对服务器就不算啥事;最后,一般情况下,我们的服务器的处理能力一般低于攻击者的能力,针对这种情况,目前有以下几种方法:1.设置访问频率阈值,当某个IP单位时间内访问次数超过这个阈值就拒绝;对于某个服务,如果瞬时请求过大,选择直接拒绝保证其他服务的正常工作;这种方式可能导致正常用户也无法使用。2.验证码,为了防止暴露方式带来的误伤正常用户,目前服务商在收到大量请求时,会向"用户"发送验证码,如果是真实用户则会输入验证码,而是肉鸡的话,则不会,通过该方法则分辨出真实用户和肉鸡。

(3) 

DRDoS((Distributed Reflection Denial of Service,分布式反射拒绝服务),攻击者将不是将请求直接发送给被攻击者,而是发送给一个第三方,通过第三方中转到被攻击者,这就是"Reflection"的体现。它的流程具体如下:攻击者将请求包的源IP篡改成要被攻击者的IP,目标IP是第三方IP,那么第三方的恢复报文的目标IP就变成被攻击者的IP,这样一来,被攻击者就会收到大量请求导致服务不可用。

需要注意的是:1. 攻击者往往选择的是基于UDP协议的系统,因为UDP协议是不可靠的,能够伪造源IP; 2. 攻击者往往会选择那些响应包大于请求包的服务。基于此,一般被被攻击的服务包括DNS服务、Memcached服务等。
为了加深大家理解,我就以Memecached服务为例。首先,Memcahced运行在11211端口,支持TCP和UDP一些,也就是说攻击者能够伪造源IP,同时,Memcached支持最大键值但数据对1M存储,意味着攻击者用较小的请求包让攻击者收到变大了几十倍的数据包。

  对于这种攻击,有没有方法来防范呢?首先,扩大服务器的宽带;其次,尽可能选择TCP协议;最后,对于一些被放大的返回包直接进行丢弃。

补充三、 SYN攻击原理

在三次握手过程中,Server发送SYN-ACK之后,收到Client的ACK之前的TCP连接称为半连接(half-open connect),此时Server处于SYN_RCVD状态,当收到ACK后,Server转入ESTABLISHED状态。

SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server回复确认包,并等待Client的确认,由于源地址是不存在的,因此,Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。

SYN攻击时一种典型的DDoS攻击,检测SYN攻击的方式非常简单,即当Server上有大量半连接状态且源IP地址是随机的,则可以断定遭到SYN攻击了。

针对这个特点可以实现一个 TCP 代理(防火墙),发现有发送 SYN 但是不给 ACK 的行为就对目标 IP 地址禁用一段时间。这个策略平时可以配置成开关,等到被攻击的时候打开。另一方面,可以适当提升连接数支持。

补充四、DNS过程

1.用户主机上运行着DNS的客户端,就是我们的PC机或者手机客户端运行着DNS客户端

2.浏览器将接收到的url中抽取出域名字段,就是访问的主机名,, 并将这个主机名传送给

DNS应用的客户端。

3.DNS客户机端向DNS服务器端发送一份查询报文,报文中包含着要访问的主机名字段

(中间包括一些列缓存查询以及分布式DNS集群的工作)

4.该DNS客户机最终会收到一份回答报文,其中包含有该主机名对应的IP地址。

5.一旦该浏览器收到来自DNS的IP地址,就可以向该IP地址定位的HTTP服务器发起TCP

连接。

补充五、 http为什么会出现大量的time-wait?

当服务器出现大量tcp连接,尤其出现大量TIME_WAIT连接的时候,导致后面的连接进不去,出现服务没有响应的情况,客户端会感觉服务器响应速度变慢。

首先使用命令查看当前的各种状态的数量:

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'    

执行后,一般结果如下:

  • TIME_WAIT 8
  • CLOSE_WAIT 323
  • SYN_SENT 1
  • ESTABLISHED 6171
  • ESTABLISHED      表示正在通信
  • TIME_WAIT           表示主动关闭
  • CLOSE_WAIT        表示被动关闭

主动关闭和被动关闭怎么理解?

TCP终止连接的四次挥手还记得么?

对于基于TCP的HTTP协议,关闭TCP连接的是Server端,这样,Server端会进入TIME_WAIT状态,可想而知,对于访问量大的Server,会存在大量的TIME_WAIT状态,维护这些状态给Server带来负担。所以服务端的响应会变慢。

解决出现大量TIME_WAIT情况的方法

1:加多台server 做负载均衡,其实也是变向的增加client端可用的同连接数。

2:确保客户端和服务器在循环使用几个虚拟IP地址,来增加更多的不同连接组合。即使没有出现遇到端口耗尽的问题,也要注意有大量的连接处于打开状态的情况,或处于等待状态的连接分配了大量控制块情况下。也会导致有些系统速度严重减缓。

3:编辑内核文件/etc/sysctl.conf,加入以下内容:

1

2

3

4

net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;

net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

net.ipv4.tcp_fin_timeout 修改系默认的 TIMEOUT 时间 

http1.0 默认是短连接,client端主动发起,server端服务完毕后,将连接关毕,client端也借此判断数据发送完毕。所以http1.0时代,服务端容易有较多的time_wait。

  • 如果http 1.0协议,client希望使用长连接,需要在header中设置connection:keep-alive 

http1.1默认是长连接

  • 如果client不希望使用长连接,需要在header中设置connection:close
  • 如果server不希望使用长连接,也需要在reponse中设置connection:close

http header中keep-alive参数用于设置客户端希望服务端保持连接的秒数 。

补充六、 反爬虫的机制,有哪些方式? 

1、U-A校验 

最简单的反爬虫机制应该是U-A校验了。

UA的全称是User Agent,它是请求浏览器的身份标志,很多网站使用它来作为识别爬虫的标志,如果访问请求的头部中没有带UA那么就会被判定为爬虫。

浏览器在发送请求的时候,会附带一部分浏览器及当前系统环境的参数给服务器,这部分数据放在HTTP请求的header部分。

一般网站如果不设置U-A是上不去的,通过requests库设置合法的U-A就能进行请求。

一般来说第三方库发送请求会有一个默认的U-A,这个U-A并不合法,如果我们直接用这个U-A,就等于直接告诉别人,我就是爬虫,快来禁我!

U-A设置方式:

def download_page(url):
    headers={
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36'
    }
    data = requests.get(url,headers=headers)
    return data

但由于这种要针对这种反爬虫机制十分容易,即随机UA,所以这种反爬机制使用的很少。

当然我们如果反复访问同一个网站,却一直使用同一个U-A,也是不行的。可以弄一个U-A池,然后每次访问时都从中随机抽取一个U-A。 

访问频率限制

真人浏览网页的速度相对程序是很慢的,但是爬虫不一样。如果有人一秒钟访问了100次同一个网站,那几乎毫无疑问,这就是爬虫。一般来说,面对这种情况,我们有两种办法来解决。

第一种办法很简单。既然访问太快会被禁,那我访问慢点就好了啊。我们可以在每次访问完网站之后就设置一个time.sleep,限制访问速度。最好是用一台机器从慢到快访问,找到被封的阈值,然后以稍微低一点的速度进行访问。

第二种方法就是换ip。网站一般是通过ip来识别访问者的身份的,所以我们只要不停地更换ip,就可以伪装成不同的人。同一个ip一秒钟访问了100次很不正常,但是100个ip一秒钟访问100次就很正常了。那么我们如何更换ip呢?其实也不用真正更换我们的ip,而是通过代理ip转发我们的请求。不少网站提供了很多免费的代理ip,我们只要把它们爬下来,以备不时之需。不过很多代理ip寿命都不长,所以需要时常进行检测。requests设置代理ip也很简单。

proxies = {"http": "http://42.228.3.155:8080",}
requests.get(url, proxies=proxies)

Cookie和验证码

Cookie就是指会员制的账号密码登陆验证,这就可以通过限制单账号抓取频率来限制爬虫抓取,而验证码完全是随机的,爬虫脚本无法正确识别,同样可以限制爬虫程序。

有些网站,不管你做什么,登录还是访问页面,都需要输入验证码进行验证。在这种情况下,我们必须识别出验证码,才能爬取网站内容。有些简单的字母加数字的验证码可以用ocr进行识别,其他一些滑动验证之类的就需要其他的技巧来破解。

登录验证

登录很多时候是服务于网站功能的,反爬虫不过是顺带的目的。我们可以通过按F12检查开发者工具来看网站在登录时会发送什么数据,然后通过requests的相关函数模拟登陆。

 补充七、 短域名解决方案

什么是短域名?

顾名思义,短域名即是长度较短的网址。通过短链接技术,我们可以将长度较长的链接压缩成较短的链接。并通过跳转的方式,将用户请求由短链接重定向到长链接上去。

为什么要使用短域名?(短域名的好处)

1.更容易记忆;
2.更快的速度;
3.更多的文字表现;
4.更友好的视觉体验;
5.更好的SEO;

原理:

① 当我们在浏览器里输入一个短地址(如http://hatzjh.cn/RlB2PdD) 时

② DNS首先解析获得 http://hatzjh.cn/ 的 IP 地址

③ 当 DNS 获得 IP 地址以后(比如:...),会向这个地址发送 HTTP GET 请求,查询短码 RlB2PdD

④ http://hatzjh.cn/ 服务器会通过短码 RlB2PdD 获取对应的长 URL

⑤ 请求通过 HTTP 301 转到对应的长 URL http://www.hatzjh.com/firm2019
注:301 是永久重定向,302 是临时重定向。短地址一经生成就不会变化,所以用 301 是符合 http 语义的。同时对服务器压力也会有一定减少。

【短域名的算法】

算法一(自增序列算法)

设置 id 自增,一个10进制 id 对应一个62进制的数值,1对1(如十进制的10000对应62进制的CcQ等);

这里也可以使用base64等,原理相似;
优点:唯一性;范围很是庞大,500多亿次才可能出现重复,一个长域名可以对应多个短域名;
缺点:难统计;短域名的生成有一定的规律;

算法二

将长网址 md5 生成 32 位签名串,分为 4 段,每段 8 个字节
• 对这四段循环处理, 取8 个字符,将他看成 16 进制与 0x3fffffff(30使) 与操使;即超过30 位的忽略处理
• 这30 位分成6段,每5 位的数字作为字母表的索引取得特定字符, 依次进行获得 6 位字符串
• 总的 md5 串可以获得4 个6位字符串,取里面的任意一个就可作为这个长 url 的短 url 地址
注:这里也可以使用Hash值等,原理相似;
优点:不会从一位递增到多位,没有规律可言;
缺点:存在重复的可能性(虽然很小),短码位数比较固定,长域名对应的短域名固定;

算法三
随机数算法:每次对候选字符进行任意次随机位数选择,拼接之后检查是否重复
若要求位数为2,则其对应短地址为计算过程如下:
①设置字符序列“0123456789abcdefghijklmnopqrstuvwxyz”
②根据字符个数设置最大值为35,最小值为0,取2次随机数假设为:6,17
③依次取上述字符的6位和17位,则为6h
其生成之后的短网址为xx.xx/6h
优点:算法简单,便于理解;
缺点:随着短域名的增多,效率低下;

posted @ 2022-06-25 14:02  沙滩de流沙  阅读(162)  评论(0编辑  收藏  举报

关注「Java视界」公众号,获取更多技术干货