一点一滴成长

导航

http随笔

一、http报文结构和方法

  http请求报文格式,分为三部分:开始行(请求行)+ 首部(请求头)+ 实体(请求体)。可以看到,开始行中包含请求方法 + 请求路径 + HTTP版本。请求头之后有一个空行表示头的结束,请求实体里可以是表单数据或JSON数据等。

  

  http应答报文格式也分为三部分:状态行 + 首部(响应头)+ 实体(响应体)

 

 

  以下为http请求、响应报文结构:

  可以看到,HTTP报文中包括Start Line、Header、Body三部分,其中第一行为Start Line(请求报文为请求行,应答报文为状态行),Header中每条内容也以换行结束,当遇到一个空行(两个/r/n)就表示Header的结束,Body的开始。

  URL的格式:

  通常浏览器通过URL获取的第一个资源是HTML网页,在网页中,如果嵌入了JavaScript、CSS、图片、视频等其他资源,浏览器会根据资源的URL再次向服务器请求对应的资源。

  http1.1中请求的方法有:

  GET:请求资源,如果请求的资源时文本,服务端就直接返回对应的文本资源,如果是像CGI(Common Gateway Interface 通用网关接口)那样的程序,则返回执行后的输出结果。
  POST:向服务器传输实体的主体(虽然GET也可实现,但一般是使用POST)。
  HEAD:获得报文首部,类似GET但不返回报文主体部分,可用于获得URI的有效性及资源更新的日期时间等。
  OPTIONS:获得对请求URI指定的资源支持的方法,如GET、POST、HEAD等。
  PUT:向服务器传输文件,类似FTP上传文件一样,一般网站不开放该方法,除非配合WEB应用验证机制或架构设计采用REST标准的网站。
  DELETE:删除文件。
  TRACE:追踪路径,客户端使用该方法发送请求时在Max-Forwards首部字段中填入数值,每经过一个服务器该值会减1,若该值变为0则当前服务器返回状态码200响应。
  CONNECT:请求用隧道协议连接代理,主要使用SSL和TLS将通信内容加密后经网络隧道传输,使用connect方法的格式为:CONNECT 代理服务器名:端口号 HTTP版本,如:

  

   下面是使用浏览器访问http://192.168.70.5:8080页面中浏览器发送数据的抓包,前面为带参数name=leon,后面为不带参数,可以看到URL中的主机地址放到了Host首部中,路径和参数被存放在请求行中,而对于POST模式,URL中的参数则会被放到Body中。

     

    POST与GET的区别:POST的主要目的并不是像GET那样获取响应的主体内容,如下图所示,POST主要是向服务端发送实体主体的数据,而GET请求一般不会使用实体主体。也就是说POST主要用来向服务端推数据,GET用来从服务端拉数据。

  

    由于post和get的使用差异才导致了二者的以下区别:

    1)、get发送的数据有长度和类型限制,post则没有:因为发送数据时,POST是向实体主体body添加发送数据,GET方法是向请求的URL中添加发送数据(比如以参数形式),URL的长度是受限制的(最大2048个字符)。

    2)、因为GET发送的数据是在URL中,所以GET只接受字符数据,POST则没有限制,在发送密码等敏感信息时不要使用GET,以免其在URL中直接展示。

    3)、对于浏览器来说,它会因为POST的特性而对其在浏览器上的表现作出额外的限制:POST请求不会被缓存(除非手动设置);POST请求不会保留在浏览器历史记录中;POST请求不能被收藏为书签;

    4)、还是对于浏览器来说,GET请求在三次握手之后直接发送GET请求头和数据(服务器返回200 OK响应),POST请求会在三次握手之后先发送post请求头,在服务器返回100 Continue响应后浏览器再发送数据,这之后服务器返回200 OK响应。因为POST会分两次传输,所以一般认为POST比GET要慢。但是在网上看到有人说并不是所有浏览器都会在POST的时候发送两次,Firefox就只发送一次。

 

二、cookie、session、token

  1、HTTP的无状态  

  在HTTP1.1之前,每次请求-应答数据都要建立-关闭一个连接,服务器认为每次请求都是一个全新的请求,无论该请求是否来自同一地址,因此,HTTP通信是无状态的,当前请求并不会记录它的上一次请求信息。因为HTTP通信是无状态的, 所以当一个用户登录成功后,如果他继续访问其他页面,Web程序如何才能识别出该用户身份?在HTTP1.1中默认允许TCP连接保持,以节省建立连接所耗费的时间,但HTTP协议依然保持无状态的特性,那么如何使用http将上下文请求(多次请求)进行关联呢?答案就是使用cookie。cookie分为会话(内存)cookie和持久(硬盘)cookie,比如网站自动登录功能就是利用的持久cookie,而一些网站在关闭后(不关闭浏览器)再次打开依然是登陆状态,这就是利用会话cookie的效果。

  HTTP 2.0进一步提高了传输效率:支持浏览器同时发出多个请求,但每个请求需要唯一标识,服务器可以不按请求的顺序返回多个响应,由浏览器自己把收到的响应和请求对应起来,这样浏览器发出一个请求后,不必等待响应,就可以继续发下一个请求。

  2、cookie

  使用cookie登录相当于是有状态的http,每次请求(甚至关闭浏览器后的再次请求)服务器都可以通过cookie数据来确认用户身份,相当于服务器在维护着一个seesion。一般的流程是客户首次登录输入用户名密码,服务器登录验证后生成一个sessionID并保存,然后将这个sessionID通过cookie返回给客户端,客户端每次请求都携带保存sessionID的cookie,服务器拿着客户端发来的cookie与保存的seesionID进行验证用户的权限。果用户在一段时间内没有访问服务器,那么SessionID会自动失效,下次即使带着上次分配的Session ID访问,服务器也认为这是一个新用户,会分配新的Session ID。

  我们把这种基于唯一ID识别用户身份的机制称为Session。当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识(称为session id),如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(检索不到,会新建一个),如果客户端请求不包含session id,则为客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。cookie数据存放在客户的浏览器上,session是相对于服务器而言,session数据放在服务器上。

  3、token

  token 的认证方式是无状态的,服务端不保存登陆状态,也就是服务器不保存sessionID来维护与客户端的session。使用token的流程是客户首次登录输入用户名密码,服务器登录验证后签发一个token给客户端(比如说用HMAC-SHA256算法,加上一个他人不知道的密钥,对数据做一个签名,把这个签名和数据一起作为token ,由于密钥别人不知道,所以无法伪造token)。客户端也会将token保存起来,以后发送的每一个请求都携带token,服务器接收请求后拿到 token 去解析认证(比如用同样的HMAC-SHA256 算法和同样的密钥,对数据再计算一次签名, 和客户发来的token 中的签名做个比较,如果不相同就是认证失败)。

  使用token的优势:

  服务器不必保存token来查询,而是直接对客户端发来的token进行验证。
  可以携带其他信息,比如携带具体权限信息之类的,避免了再去查库。
  如果使用了多台服务器的负载均衡,两条请求发给了不同的服务器的话使用cookie就不能达到效果。
  请求中发送token而不再是发送cookie能够防止CSRF(跨站请求伪造)。即使在客户端使用cookie存储token,cookie也仅仅是一个存储机制而不是用于认证。
  请求需要跨域的接口的时候 cookie 就力不从心了,不同域就不会携带 cookie。
  cookie主要是与浏览器交互,对于移动端不太好支持,移动端使用token 就方便得多。

三、http首部和响应状态码

  1、http1.1首部字段

   http首部字段由字段名+字段值构成,中间用分号“:”分隔,如果包含多个字段值的话用逗号“,”分隔,如:“Content-Type: text/html”,"Keep-Alive: timeout=15, max=100"。

  通用首部字段:

  
  请求首部字段:

  
  响应首部字段:

  
  实体首部字段:

  

   如果按照缓存代理和非缓存代理的行为,可以将http首部字段分为端到端(End-to-end)首部和逐跳(Hop-by-hop)首部,逐跳首部只对单次转发有效,会因通过缓存或代理而不再转发。自http/1.1起,如果要使用hop-by-hop首部,需提供Connection首部字段,http/1.1中的逐跳首部字段有以下8个,除这8个首部字段外其它首部字段都属于端到端首部:

    

  关于http首部字段的详细说明参考<<图解http>>。 

  2、http响应状态码

  

1xx :接收的请求正在处理。
200 OK:请求正常处理。
204 No Content:请求处理成功,且相应无资源返回。
206 Partial Content:请求处理成功,且该请求是范围请求。
301 Moved Permanently:永久性重定向即页面永久性移走,浏览器收到后会自动跳转到一个新的URL地址,且应该将保存的旧地址替换为新地址。
302 Found:页面临时性重定向,浏览器收到后会自动跳转到一个新的URL地址。
303 See Other:类似302功能,但它明确表示客户端应该使用GET方法重定向到指定资源。301、302标准是禁止将POST方法改变成GET方法的,但实际上几乎所有的浏览器都对301、302响应做出把POST改成GET后再次发送请求的做法。
304 Not Modified:资源已找到,但附带条件未满足。附带条件指采用GET方法的请求报文中If-Match、If-None-Match、If-Modified-Since等。
307 Temporary Redirect:类似302功能,但它明确表示客户端不应使用GET方法重定向到指定资源。
400 Bad Request:请求报文中存在语法错误,浏览器会像200 OK一样对待该状态码。
401 Unauthorized:发送的请求需要有http认证(BASIC认证、DIGEST认证)的信息,若之前已进行过一次请求,则表示用户认证失败。浏览器初次接收到401响应会弹出认证用的对话框。
403 Forbidden:访问被拒绝。
404 Not Found:资源未找到。
500 Internal Server Error:服务器执行出错。

502 Bad Gateway 网关错误。
503 Service Unavailable:服务器超负载或停机维护。如果知道服务器恢复的时间最后写入RetryAfter首部字段再返回给客户端。

504 Gateway Timeout网关超时

四、HTTPS

  现代加密方法中一般加密算法是公开的,但解密用的秘钥是保密的,比如发送方使用加密算法和秘钥对数据进行加密,接收方使用秘钥和解密算法对数据进行解密,这种发送方和接收方使用同一个秘钥的方式称为对称秘钥加密(共享秘钥加密)。对称加密的关键是怎样保证将秘钥安全的发送给对方,这可以通过非对称加密来实现。

  非对称秘钥加密(公开秘钥加密)使用两把秘钥,一把私有秘钥只有自己知道,一把公开秘钥用来发送给对方:甲方生成私钥和公钥后向乙方发送公钥,乙方使用公钥对数据进行加密后将其发送给甲方,甲方再根据私钥对数据进行解密,甲方发送给乙方的公钥或者乙方发送给甲方的加密数据即使被第三方截取到也不能对数据进行解密,因为解密需要私钥。

  对于HTTPS来说,服务端就是上面所说的甲方,客户端就是乙方,二者正常通信之前要使用公开秘钥加密方式生成秘钥(客户端向服务器发送的加密数据就是以后通信要使用的秘钥),然后通信就靠这个秘钥来加密(共享秘钥加密)。HTTPS中的这种加密手段是通过SSL协议完成的,TSL是以SSL为原型开发的协议,目前主流使用的版本是SSL3.0和TLS1.0(SSL1.0和2.0被发现存在问题,TLS目前还有1.1和1.2版本)。

  如下所示,本来客户端A是要跟服务端B进行HTTPS通信的,但是A的请求被中间的黑客截获或者其伪装成了B,A在请求公钥的时候黑客会向其发送伪造的公钥来冒充B,对于B来说黑客会向其冒充A,这样使A、B之间通信的数据被黑客获取。

   

      所以SSL握手中如何确保客户端收到的公钥就是来自确信的服务端呢?可以使用数字证书认证机构(CA,Certificate Authority)颁发的证书来解决该问题。服务端向CA发送公钥,CA将该公钥进行hash运算得到一个hash值,然后使用自己的私钥来对该值加密(称为签名),签名会被放到一个数字证书中,该数字证书中还包含服务端的公司信息、证书的有效时间等,CA将生成的证书颁发给服务端。在SSL中服务端向客户端发送公钥的时候还会发送数字证书,客户端收到后会使用浏览器内嵌的CA公钥(大多数浏览器会内置常用认证机构的公开秘钥)来对数字证书中的公钥进行解密,如果解密出来的值与服务端发送的公钥的hash值相同的话则说明公钥可信,没有被篡改过。

  证书的另一个作用是可确认对方服务器背后的运营企业是否真实存在,一般这种网站会以绿色背景显示其域名,或有一个小锁的图标,点击它会提示当前连接是安全的,还可以查看证书的组织名和颁发机构等信息。使用由非知名的CA颁发的证书或者用OpenSSL自构建的证书(自签证书,即用自己的私钥签署的证书),浏览器会弹出以下的提示,因为浏览器没有内置该机构的公钥,无法对服务端发来的公钥进行验证,一般在非生产或非公开服务中可以使用自签证书。

   

  

  可以说HTTPS = TLS/SSL + HTTP,如下所示,它通过加密解决了攻击者窃听和篡改请求或响应的数据,通过使用证书解决了无法验证通信方身份的问题。

   

  HTTPS会将整个HTTP报文(开始行、头部、实体)进行加密,但是TLS握手中的一些信息不会进行加密,如客户端发送的Client Hello消息和服务器返回的Server Hello消息,而Client Hello消息中有个 server name 字段,它就是请求的域名地址,所以可以在抓包的时候看到请求的域名,如下所示。

   

  HTTPS相对HTTP多出了SSL通信(七次握手)以及加密解密运算,所以其可能会比HTTP要慢2到100倍,可以使用SSL加速器这种(专用服务器)硬件来改善该问题。HTTPS通信中的SSL握手通信具体步骤可以在《图解HTTP》书中查看。

  HTTPS默认端口号为443,HTTP协议代理服务器常用端口号:80/ 8080/ 3128/ 8081/ 9098。

  使用Postman或Apipost进行https通信的话,可以在Settings设置中设置不启用SSL 证书验证(默认)来忽略对服务端证书的验证。

  使用Libcurl进行https通信的话,需要在编译libcurl的时候选择支持SSL,比如下面的使用CMake工具来生成sln项目(该项目即为生成libcurl库的项目)。在代码中可以添加如下两行来设定为不验证证书和HOST:

  

//libcurl
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false);

//curlpp
request.setOpt(new curlpp::options::SslVerifyPeer(false));
request.setOpt(new curlpp::options::SslVerifyHost(false));

    如果要使用CA证书(下载地址 http://curl.haxx.se/ca/cacert.pem ),使用如下设置:

//libcurl
curl_easy_setopt(this->curl_handle_, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(this->curl_handle_, CURLOPT_CAINFO, "cacert.pem"/*证书在当前目录*/); //指定证书路径

五、用户身份认证

  用户身份认证也称为http认证,主要有BASIC认证(基本认证)、DIGEST认证(摘要认证)、SSL客户端认证、FormBase认证(基于表单认证),bearer 认证,此外还有Windows统一认证,包括Keberos认证、NTLM认证。

  1、BASIC认证流程:客户端请求资源时服务器会返回带401响应状态码和WWW-Authenticate首部字段的响应,该字段内包含认证方式(BASIC)和认证信息。客户端收到401后在Authorization字段上添加用户ID和密码(一般浏览器会提示用户输入用户名和密码),二者应以冒号连接,并以Base64编码后发送。认证成功后服务器返回状态码200,失败会返回401。

     

 

    BASIC认证的缺点是每次传输都需要携带用户名和密码来识别用户,而且密码明文传输(只是经过了Base64编码)。Base64就是一种基于64个可打印字符来表示二进制数据的方法,具体转换步骤: 

  第一步,将待转换的字符串每三个字节分为一组,每个字节占8bit,那么共有24个二进制位。
  第二步,将上面的24个二进制位每6个一组,共分为4组。
  第三步,在每组前面添加两个0,每组由6个变为8个二进制位,总共32个二进制位,即四个字节。
  第四步,根据Base64编码对照表(0对应'A',1对应'B',...63对应'/')获得对应的值。

  2、DIGEST认证同BASIC认证一样采用“质询/响应”的方式,但增加了密码加密。其认证流程是:客户端请求资源时服务器会返回带401响应状态码和WWW-Authenticate首部字段的响应,该字段内包含认证方式(DIGEST)和认证信息,且该字段内必须包含realm和nonce这两个字段,客户端会向服务器回送这两个字段值,其中realm通常设为服务器的域名,nonce是一个随机数。客户端收到401后在Authorization字段上添加username、realm、nonce、uri、response(根据用户名、密码等字段值组合后进行MD5运算的值)字段值后发送(一般浏览器会提示用户输入用户名和密码)。服务器收到客户端响应后确认认证的正确性(通过客户端的用户名获得保存的密码,同样与其它字段组合后获得MD5值进行对比),通过的话就返回客户端请求的资源。

    DIGEST认证存在的问题是现在服务器对于用户密码一般都不是明文保存,而是采用MD5值或MD5 + SALT的方式,这样客户端和服务器的密码就不能统一。所谓的MD5 + SALT就是指服务器不是直接计算密码的MD5值,而是再加上另外一个SALT值来计算出MD5值来保存,这个SALT值一般是服务器随机生成的一个长度够长的值,所以只有服务器知道,这样更加安全,避免黑客获得用户密码的散列值后通过查散列值字典(例如MD5密码破解网站),得到某用户的密码。

    DIGEST认证的另一个问题是虽然它能提供防止密码被窃听的保护机制,但不存在防止用户伪装的的保护机制。

  3、SSL客户端认证,前面说过HTTPS中使用证书来确认服务端的真伪,HTTPS中还可以使用客户端证书来对客户进行验证。一般是将客户端证书发给客户,客户将证书安装到自己的电脑(浏览器)上,当客户端请求服务器时服务器会发送Certificate Request报文,要求客户端提供客户端证书,用户选择客户端证书后将证书信息以Client Certificate报文方式发送给服务器,服务器验证通过后方可领取证书内客户端的公开秘钥,然后开始HTTPS通信。

    SSL客户端认证可以通过客户端证书来确认当前就是实际用户,但缺点是需要从认证机构购买客户端证书,服务器运营者也可以自己搭建认证机构,但要维持安全运行就会产生相应的费用,一般银行等机构会使用该认证机制。

  4、基于表单认证(Cookie认证):BASIC认证的缺点是每次传输都需要携带用户名和密码来识别用户,可以使用基于表单的认证,当用户输入正确的用户名密码登录成功后,后续的浏览页面通过Cookie来验证用户。基于表单的认证并不是HTTP协议中的标准,一般的流程是客户端将ID和密码等登陆信息以HTTPS通信来发送给服务器,服务器验证客户端的登陆信息后会发放用以识别用户的Session ID,且将用户的认证状态与Session ID绑定后记录在服务端,向客户端返回响应时会在首部字段Set-Cookie内写入Session ID(Session ID应该使用难以揣测的字符串,且服务器也需要进行有效期管理,保证其安全性,另外,为减轻跨站脚本攻击(XSS)造成的损失,建议事先在Cookie内加上httponly属性),客户端收到Session ID后会将其作为Cookie保存在本地,下次向服务器发送请求时,浏览器会自动发送Cookie,所以Session ID也随之发送,这样服务器可以通过接收到的Session ID识别用户和其认证状态。

    现在的web网站多为使用基于表单的认证,即使用Session和Cookie来保存用户登录状态。关于cookie的具体使用,可以参见这篇 Servlet文章 中session和cookie部分。

  5、Bearer认证类似于Basic认证,不同的是客户端发送的首部Authorization的值为token(令牌),如下所示。令牌在服务器组装完成后,会发送到客户端,客户端把令牌保存起来(文件、localstorage、cookie),后续的请求将令牌发送给服务器,而服务器需要验证令牌是否正确。

       

      Bearer认证主要用于第三方登录,比如使用微信进行网站的登录,百度贴吧和百度搜索二者能够登录状态互通这种。

      Bearer认证中的token可以通过OAuth2的授权流程获得,也可以使用JWT(Json Web Token)。

      Bearer认证使用的是HTTP头Authorization,而不是cookie,所以不存在跨域问题。跨源资源共享(Cross-Origin Resource Sharing, CORS)即跨域,比如两个页面的协议、域名、端口、子域名不同。

posted on 2019-05-21 15:54  整鬼专家  阅读(270)  评论(0编辑  收藏  举报