复习一下网络协议。

TCP/UDP 协议

TCP 协议

面向连接

TCP 协议是面向连接的,可靠的传输层协议,可以保证数据正确性和合法性,传输的可靠到达,流量控制。
在建立连接的时候,TCP 进行三次握手,释放连接的时候,进行四次挥手。

三次握手顺序如下:

  1. 客户端->服务端:我将发起连接。 服务端确定了自己的接收能力是 OK 的
  2. 服务端->客户端:我已经收到了你的请求。 客户端确定了自己的发送能力和接收能力
  3. 客户端->服务端:我接收到了你的回应,马上发送。 服务端确定了自己的发送能力。

四次挥手:

  1. 客户端->服务端:我没有数据发送了,你先发送剩下的,完事告诉我。
  2. 服务端->客户端:好的,我发送完了告诉你。
  3. 服务端->客户端:我发送完了,你收到信息就可以断开连接了。
  4. 客户端->服务端:好的,你也断开吧。客户端在等待一定时间保证网络上没有未到达的数据包后,断开连接。

为什么要四次挥手,因为客户端请求断开连接的时候,只是说明自己没有数据要发送了,但是自己还可以接受,服务端也可能还有数据没有发送完成。
所以 TCP 协议是面向连接的,它保证双方都有接受和发送能力。

数据可靠性和流量拥塞控制

  1. 校验和 有一个校验和字段,发送方将所有报文分段反码相加,结果存在校验和字段中,接收方收到后同样进行计算,相同则正确。
  2. 序列号 TCP 给每个字节都编号,进行去重、完整性、顺序性的应用。
  3. 确认应答 每次收到请求,接收方都会进行确认,给发送方发送确认报文,里面的确认值表明这个值之前的数据都收到了。
  4. 超时重传 经过一段时间后,发送方没有收到接收方的确认报文,会重新发送。
  5. 流量控制 接收方收到报文后,进行确认应答时,会将自身缓冲区剩余大小放到窗口段中,设置0的话,发送方不再发送数据,同时等待窗口更新的通知,过了超时时间还没有收到的话,会发送窗口探测,查看窗口是否更新完成可以继续发送。
  6. 拥塞控制、慢启动。还有一个拥塞窗口的概念,每次发送,都是发送流量控制窗口和拥塞窗口的最小值。每次拥塞窗口都是从1开始,指数级增长,慢慢发送的越来越多,超过阈值就进行线性增长,出现重传的话,阈值设置为当前拥塞窗口大小的一半,同时进行再次慢启动。

UDP 协议

UDP 协议无法保证可靠性,顺序性,所以传输效率高,处理速度快,所以通常音频和视频数据,可以使用 UDP 协议。

HTTP 协议

工作在应用层,基于 TCP 协议实现。

简单来说,服务器在响应头中给浏览器发送 Set-Cookie 段,后期浏览器根据段中的配置,取出 cookie 回传给服务器。每次可以发送多个段,每个段中包含:

* 名称-值    cookie 的名称和值
* Domain 域 = 域名    当前 cookie 可以被发送到哪些域或者子域下面 默认是当前域
* Path = 路径    所属的路径
* Expires = Date    过期时间未指定过期时间,代表生命周期为当前浏览器,关闭后删除,指定之前的时间,cookie 会立即删除。
                 还有一点,假如未设置过期时间,存储在浏览器内存中,否则存储在硬盘上。
* Secure    有这个字符串的话,代表只有 https 安全通信时才发送 cookie
* HttpOnly 有这个字符串的话,代表此 cookie 只能用于 http 或 https 传输,脚本 js 等是不能访问的。

浏览器回传给服务器的时候,在请求头发送 Cookie 段,只包含 名称=值 对,多个以分号相隔。
下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// 服务器发送给浏览器,只注重 set-cookie 段
HTTP/1.1 200 OK

Content-type: text/html

Set-Cookie: name=value

Set-Cookie: name2=value2; Expires=Wed, 09Jun 2021 10:18:14 GMT

/// 浏览器回传
GET /spec.html HTTP/1.1

Host: www.webryan.net

Cookie: name=value; name2=value2

Accept: */*

Cookie 可以做很多事情,因为浏览器已经保存起来了一些信息,比如用户账号缓存,记录用户行为,当然也有安全性缺失等缺点,比如 js 也可以更改存储的 cookie。

session

为了在 http 协议上,对客户保存状态,有两种方案,cookie 机制和 session 机制。

cookie 机制是在客户端保存状态的机制,而 session 是在服务端保持状态的一个机制。

当需要为客户端创建一个 session 的时候,服务器会检查客户端请求头中是否包含一个 session-id ,假如有的话就说明以前创建过 session ,根据 session-id 去查找到对应的 session ,假如没有的话,生成一个 session 和与之对应的 session-id,并把 session-id 返回给客户端保存起来。
通常,这个 session-id ,是保存在客户端的 cookie 里面的,但是有时候,客户端会把 cookie 禁止掉,这时候可以将 session-id 放到 url 后面上传。
至于在 session 里面可以存储什么,这个也是多种多样的。

HTTP 队首阻塞

在一个 tcp 连接中,只有多次请求会放到一个队列中,只有上一个请求收到回应完成后,才能发送下一个请求,这样假如前面的数据过大,会引起后面请求被阻塞。

HTTP 2.0

帧 消息 流

一个帧是传输数据的最小单位,里面有数据,有所属流的标识
流可以理解成针对于每个资源的请求
一个消息可以理解为一次 HTTP 传输的数据,里面可能包含多个流的多个帧。
每次传输一个消息,会一次传输多个流的多个帧,乱序传输,收到后会再次组合,这样实现了多个流即多个资源的并行传输,解决了 http 可能会出现的队首阻塞,但是 tcp 层的阻塞没有处理。

传输的数据被编码为二进制

数据被编码为二进制

多次连接复用一个 TCP 连接

建立一条 TCP 连接后,可能上面有多个数据流在并行传输。

首部压缩

不同于 http 1.x ,每次请求都有一个完整的 head 首部,http 2.0,保存着一个首部表,每次可能只对这个表进行键值对的更新,而且对于数据进行了压缩。

HTTPS:基于 HTTP 协议的加密实现

HTTP 协议的短板

  1. 因为 HTTP 协议是明文传输,所以有可能会被窃听
  2. 无法验证同新方的身份,可能遭遇伪装
  3. 无法证明报文的完整性,有可能已遭遇篡改

HTTPS 协议的解决方法

HTTPS 是 HTTP 通信接口部分使用 SSL 和 TLS 协议代替。
SSL 协议采用公开密钥加密的处理方式进行安全处理,HTTP 协议最开始直接和 TCP 通信,使用 SSL 后,变成先和 SSL 通信,再由 SSL 协议去和 TCP 进行通信。
采用 SSL 后,HTTPS 可以进行加密处理和认证,并进行完整性保护,即 HTTP + 加密 + 认证 + 完整性保护 = HTTPS。
所以说 HTTPS 并不是一种新的协议,而是 HTTP 采用 SSL 协议,SSL 协议提供这些加密等功能。

两种加密算法

共享密钥加密 和 公开密钥加密

  1. 共享密钥加密 也叫对称密钥加密,发送方和接收方都保存有一个密钥,加密和解密都用到密钥,但是如何传输密钥,传输过程中密钥也有可能会被监听窃取。
  2. 共开密钥加密 也叫非对称密钥加密,使用公开密钥加密,发送方使用接收方公布的公开密钥进行加密,接收方使用自己保存的私钥进行解密。

HTTPS 的具体实现方式

HTTPS 实现共享密钥加密和公开密钥加密结合的混合加密机制。
在传输共享密钥阶段,使用公开密钥加密方式进行交换密钥,之后简历通信交换报文,则使用共享密钥加密方式。

如何证明公开密钥的正确性

但是即使使用公开密钥加密,也有问题,那就是在交换公开密钥的时候,也无法证明公开密钥就是真正的密钥,有可能真正的公开密钥被替换掉了。
为了解决这个问题,引入了第三方数字证书认证机构,处于客户端和服务器双方都可信赖的第三方机构的立场上。

  1. 首先,服务器 A 运营人员向数字证书认证机构提出公开密钥的申请,把自己的公开密钥 a 发送给数字证书认证机构。
  2. 数字认证机构 B 用自己的私有密钥向服务器 A 的公开密钥 a 做数字签名,将这个公开密钥放入证书 b 。
  3. 服务器 A 会将这个证书 b 发送给客户端,客户端使用认证机构的公开密钥 b1,对这个证书的数字签名做验证,即验签操作,假如正确,客户端就认为服务器 A 发过来的证书中的公开密钥,是可以信任的。然后客户端发送数据的时候,使用服务器的公开密钥对报文加密后发送,服务器使用自己的私有密钥对报文进行解密。

这个过程有个前提,就是认证机构的 公开密钥 b1 ,是什么时候传输给客户端的,一般来说浏览器里事先导入了认证机构的公开密钥。

单向认证和双向认证

单向认证 客户端保存服务端的公钥,相当于只验证服务端是否安全。
双向认证 客服端保存服务端公钥,服务端保存客户端公钥,相互验证。

OC 如何实现 HTTPS

使用 NSURLSession

当使用 NSURLSession 发起时,会回调下面的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler
{
NSLog(@"%@",challenge.protectionSpace);

if (![challenge.protectionSpace.authenticationMethod isEqualToString:@"NSURLAuthenticationMethodServerTrust"])
return;
/*
NSURLSessionAuthChallengeUseCredential 使用证书
NSURLSessionAuthChallengePerformDefaultHandling 忽略证书 默认的做法
NSURLSessionAuthChallengeCancelAuthenticationChallenge 取消请求,忽略证书
NSURLSessionAuthChallengeRejectProtectionSpace 拒绝,忽略证书
*/

NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];

completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}

使用 AF

AF 内部已经对 HTTPS 进行了处理,单向认证的时候:

1
2
3
4
5
6
7
8
9
10
11
//在你封装的网络工具类请求前初始化时增加以下代码
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//设置证书模式,AFSSLPinningModeNone,代表前端包内不验证
//在单向认证时,前端不放证书,服务器去验证
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
// 如果是需要服务端验证证书,需要设置为YES
securityPolicy.allowInvalidCertificates = YES;
//validatesDomainName 是否需要验证域名,默认为YES;
securityPolicy.validatesDomainName = NO;
//设置验证模式
manager.securityPolicy = securityPolicy;

双向认证的时候:

1
2
3
4
5
6
7
8
9
10
11
12
13
//在你封装的网络工具类请求前初始化时增加以下代
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//设置验证证书,该模式下许愿把证书打包进项目里,进行验证
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
//证书的路径
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"xxx" ofType:@".cer"];
NSData *dataSou = [NSData dataWithContentsOfFile:cerPath];
NSSet *set = [NSSet setWithObjects:dataSou, nil];
securityPolicy.allowInvalidCertificates = YES;
securityPolicy.validatesDomainName = YES;
[securityPolicy setPinnedCertificates:set];
//将验证方式赋值给管理者
manager.securityPolicy = securityPolicy;

Charles 如何进行的 HTTPS 抓包

Charles 抓取 HTTPS 的前提是客户端必须信任 Charles 证书。
使用 Charles 后:

  1. Charles 作为中间人,代替客户端向服务端请求数据。
  2. 服务端返回公钥证书后,Charles 将公钥保存,然后用自己的公钥制作证书,将证书发给客户端。
  3. 客户端获取 Charles 公钥证书后,必须选择信任,不然会中断连接,然后将对称加密私钥通过 Charles 公钥进行加密后传给 Charles。
  4. Charles 用自己的 私钥进行解密,获取到 对称加密私钥,然后用服务器的公钥进行加密传给服务器。
  5. 服务器用自己的私钥进行解密,获取到 对称加密私钥。
  6. 至此 Charles 获取到了 对称加密密钥 和 服务器公钥,就可以进行发送数据和解密了。

总结

相对于 HTTP 协议,HTTPS 会花费更多的时间,消耗更多的 CPU 资源,所以比较好的方式是在需要更安全的地方,使用 HTTPS 做数据传输。
单向认证更加灵活,客户端不需要把相应的证书打包进入 APP 内,这个证书是有过期时间的。
双向认证更加安全。