登录和授权
登录
身份认证的过程,在输入账号和密码以及点击登录按钮这一操作流程,就是将「你本人」和所输入的「账号」建立联系的过程。
Cookie
起源:购物车,当时的「购物车」不存在服务器,电商网站的开发商觉得用户在没有真正决定购买的情况下不需要将购物车的信息放进服务器里,只存在本地就好,于是电商网站开发商决定做一个浏览器来实现这一功能,也就是说,电商网站——浏览器是一体的,并实现了 Cookie 的功能满足购物车的需求,这就是最早的 Cookie。
Cookie 的工作机制
由于是客户端保存信息,所以保存的信息由服务器决定,服务器返回的信息客户端保存就行了。
如,在购物车里添加一个苹果,客户端访问服务端 /cart 这个 path,并附带苹果的数量这个参数,表示「我要放进购物车里 1 个苹果」,服务器处理完返回 200,并附带 Set-Cookie 的 Header 表示让客户端存起来。
下次客户端再访问 shop.com 的时候会附带 cart="apple=1",这是一个自动的过程,并由浏览器实现。
如下次购物车再添加一个香蕉,浏览器会自动附带 cart="apple=1" 的信息去请求,服务端会知道客户端已经「存储」了一个苹果,处理后返回 200 更新 Set-Cookie 的 Header,此时客户端更新 Cookie。
该过程服务端什么都没记,全都由客户端存储,Cookie 谁来修改?是服务端,每次都是服务端来修改 Cookie 信息,客户端被动的更新、存储。这就是早期的 Cookie 的工作机制。
现在逐渐抛弃用 Cookie 机制来做登录和认证。
使用 Cookie 管理登录状态
客户端使用账号密码访问服务端,服务端确认后,会创建一个会话(Session),会话记录了客户端可能是什么状态、用户代理等信息。然后服务端将 Session 返回给客户端。
客户端下次访问服务端的时候会附带 sessionid,假如客户端想要请求用户信息,服务端凭借 sessionid 会得知,该客户端在登录状态,可以返回给他要的用户信息。
关于登录状态的管理,服务端是需要插手干预的。
Cookie 的作用
- 会话管理:登录状态、购物车等;
- 个性化:用户偏好、主题;
- Tracking:分析用户行为(能够得知用户访问哪些网站)。
XSS(Cross-site scripting)
跨站脚本攻击,假如网站的 javascript 是坏人写的,有可能会将本地的 Cookie 转发出去,这是看不见摸不着的,Cookie 的泄漏也就代表某些敏感信息的泄漏。
应对策略就是在 Cookie 的 Header 后加 HttpOnly 的限制,这样本地脚本看不到该 Cookie,该 Cookie 只用于 HTTP 的信息交换。
XSRF(Cross-site request forgery)
跨站请求伪造,由于 Cookie 是一个自动的过程,攻击者可以利用 Cookie 访问授信网站伪装成授信用户进行一些操作。其中一个防范措施就是带上 Referer 的 Header 告诉服务器我是从哪个页面来的,假如来源的这个页面不授信,则服务器拒绝处理。
授权
赋予某个人具有某个权限,能执行什么操作,在计算机世界里,这个特殊的权限叫做令牌,就像古代的刽子手本身不具备权限杀人,但是皇上赋予了刽子手行刑的权力,那么刽子手便可以杀人了,皇上就是授权方。
Basic
基本的授权方式,用得较少但是实用,格式如下:
Authortization: Basic
Bearer
需带着令牌的授权方式,格式如下:
Authortization: Basic
token 需要找授权方获得。
OAuth2
提供第三方认证的机制,是一个授权框架,它可以使第三方的应用程序或者客户端获得对应 HTTP 服务器上用户账号信息的访问权限。
如,在登录掘金的时候,除了从正常的账号密码登录以外还可以选择三方登录,如 GitHub,在点击 GitHub 登录的时候就会弹出下面的小窗口进行授权。
OAuth2 认证流程
认证流程如下图:
a. 在这个过程中,本人和 GitHub 是第一、二方,而掘金是第三方,所以这个流程的意义就是:GitHub 授权掘金,赋予掘金可以访问 GitHub 账号信息的权限,从而登录掘金的网站。
在该流程中,有一个重要的角色:client_id,它是三方网站开发者在开发登录模块是去 Github 申请下来的。相当于标记或者身份证的作用。凭借 client_id 与 Github 沟通,Github 会返回如头像、账号名字、URL 等信息。
b. 当点击上图的 Authorize Xitu
后,少顷,便跳回掘金主页,账号会显示来自 GitHub 的账号。在跳回掘金主页的时候 GitHub 会返回 Authorization code 如下图:
Q: 为什么返回的不是 token 而是 Authorization code?
A: 因为 OAuth2.0 整个过程不是强制基于 HTTPS 的,那么在返回的过程可能被拦截,假如返回的是真正有意义的信息,那么该有意义的信息可能被窃取;还有一个原因就是浏览器是不可靠的,用户用什么浏览器以及操作系统都是未知的,所以在返回的过程中依然存在风险,所以 GitHub 返回的仅仅是一个授权码,仅仅是一个证明,表示用户同意了 GitHub的授权而已。
c. 接下来在看不见摸不着的背后,掘金带着 Authorization code 和掘金的 Server 端沟通,掘金的 Server 端带着 Authorization code 和 client_id 和 GitHub 请求, client_secret 是三方在向 GitHub 申请应用的时候一起申请下来的。client_secret 是绝对保密的,一直存在 Server 端,而 Server 端和 GitHub 的连接也是 HTTPS 连接保证安全。当 Server 端同时具备:身份证明(client_secret)以及用户证明(Authorization code)后,GitHub 便可确认三方的请求连接足够安全,对方足够可靠,便可放心的将 token 返回给 Server 端,至此,整个 OAuth2.0 的认证流程结束,第三阶段流程如下图:
d. 当认证流程结束返回到掘金的主页时,此时掘金账号的头像亦是 GitHub 请求下来的,此时的请求流程便是:Server 端带着 token 请求 GitHub 的头像信息,然后进行显示,如下:
在某些安全意识不强的情况下,Server 端会把 token 返还给客户端,这种做法就不再具备 OAuth2.0 认证的安全性。
微信登录
微信登录,是一种第三方登录,亦是一个标准的 OAuth2.0 的认证过程,正确的微信登录流程是:
调用微信的 API 后打开微信的授权界面进行授权,此时是微信在对「你」进行第三方授权,确认返回后,微信会返回一个 Authorization code
,此时将 Authorization code 交给服务器,此时,客户端的任务结束,服务端的任务便遵循 OAuth2.0 的流程去和微信的服务器打交道(同上节 OAuth2.0 认证流程一样)。
自家产品中使用 Bearer token
在多数情况下, 调用登录接口并且登录成功的情况下,Server 端会返回 access_token / token,然后请求其他接口的时候 Client 端直接拿着这个 token 请求,这种做法是模仿了 OAuth2.0 中 access_token 的使用方法,但并不是 OAuth2.0 的过程。
refresh token
刷新 token 从 Server 端返回的格式大概是:
{
"token_type": "Bearer",
"access_token": "xxxx",
"refresh_token": "xxxx",
"expires_time": "xxxx"
}
refresh_token 的作用是,以此请求一个新的 access_token 和一个新的 refresh_token,那么旧的 token 会失效,refresh_token 存在的意义就是保证安全,因为 access_token 还是有概率会丢失,如果是授权登录,丢失 access_token 会导致用户再次认证、登录,这样的操作极不友好,所以需要借助 refresh_token 来获取新的 access_token 并且将旧的“作废”。这个过程还是尽量保证在 Server 端。
TCP/IP 协议族
TCP/IP 协议族并不是单指 TCP 和 IP 的构成,而是一系列协议组成的一个网络模型分层。
在很多有关网络的文章亦或是大学的计算机网络课程中,「分层」这个词出现的频率极高,如物理链路层、表示层、会话层等等。
Q: 那么为什么要分层?
A: Client 和 Server 进行通信的时候途中会经历很多中间节点,如下图:
报文经过的节点的路径并无规律可言,并且由于网络的不稳定性(如断电、中间节点的损坏)会导致某次数据传输失败,那么就需要重传数据,在传输过程中,重传是一定会发生的,假如数据很大的话,需进行分块传输,如下图(假如 ABC 是一个很大的数据):
上半部分是分块传输,下半部分是完整的数据传输,上半部分,假如 A、C 送达,但 B 未送达,也就是发送方未收到 B 的送达回应的时候会重传,直到成功送达,此过程中传输了 5 次,假如选择传送完整数据,可能会导致更多次的失败。
分块传输能保证数据完整且高效的送达,分块传输的出现也就导致整个网络需要分层。因为应用层并不只有 HTTP,还有 FTP、DNS、TELENET 等等。各个协议都有包分发的需求,所以按照编程的思想,公共的需求可以单独抽取,在网络里即是抽取一层专门处理包的分发,如下:
每次数据传输,HTTP 层将包给 TCP 层,TCP 进行分块然后传输,哪个部分丢失了,TCP 会对丢失的部分重传。该层即为传输层,TCP 能保证整个过程稳定传输。
但是并不是所有的数据都需要重传,如视频通话,在网络卡顿的时候,丢失的帧不需要重传直接显示最新的视频数据即可,对于语音、视频通话这种要求数据高速度传输和实时到达的需求,UDP 即可满足,UDP 的特点就是:尽最大可能传输,不保证到达,不重传也不需要验证。
UDP 和 TCP 共同的地方就是都需要将数据从一个主机找到另一个主机进行传输,UDP 和 TCP 这种公共的网络需求即可再单独抽取一层,该层即为网络层,如下:
传输层将数据分块传给网络层,网络层将分块的数据发往目标网络或主机,网络层只管传输,不参与数据的处理,也就是说,它并不知道各个数据块之间的联系,甚至失败、重传也不参与。确认数据的到达在传输层,假如 B 未到,传输层则需要“告知”发送方需要重传,直至 B 到达,传输层则将整包数据拼装好上传至应用层。
应用层、传输层、网络层并未提供实质的物理链路传输仅仅是定义了访问的接口,在整个网络传输的结构中,需要有一个实际的链路来支持传输,那么这个重任就交给数据链路层,以太网、路由器等等物理设备或网络就属于这一层,如下图:
以上就是传统四层。
因为应用层的各个协议/应用在传输数据的时候都需要对数据分块并且传输,所以传输层出现了,但是数据的传输不一定都要确认送达,只管尽最大可能传输就行(UDP),所以抽取只管负责寻址、寻找主机并且传输数据的一层,网络层也就出现了。
各个层级 | 协议/实现 |
---|---|
应用层(Application Layer) | HTTP、FTP、DNS |
传输层(Transport Layer) | TCP、UDP |
网络层(Internet Layer) | IP |
数据链路层(Link Layer) | 以太网、Wi-Fi |
数据流向:发送方是自上到下,接收方是自下至上。
TCP 连接
在计算机世界里,经常能听到「长链接」这个词,那么什么是长链接?在了解长链接之前,什么又是「连接」?
A:连接是 TCP 的连接,并且是有状态的连接,TCP 在发数据之前会将数据分成报文段,然后发向目标主机/网络,在此之前,需要和目标主机/网络先建立一个互相沟通的确认方案,接收方需要知道报文段如何拼装,当互相确认的时候,该过程就是 TCP 连接建立的过程,当确认被确定了,就可称作建立了一个连接。
TCP 连接建立与关闭
TCP 的建立过程如下图(三次握手):
请求方与被请求方的 TCP 通信流程是:
- 请求方「告知」被请求方:「我要给你发消息了」
- 被请求方「回应」请求方:「我知道了,我也要给你发消息了」
- 请求方「告知」被请求方:「我知道你也要给我发消息了」
当确认后双方都会互相等待发送消息,会占用网络资源,当不需要通信后,则需要 TCP 关闭,TCP 的关闭过程如下图(四次挥手):
- 请求方「告知」被请求方:「我不再发消息了」
- 被请求方「回应」请求方:「好的我知道了」,但被请求方不会立即「回应」:「我也不给你发消息了」,此时有可能还有未发完的消息。若有未发送的消息,先发送消息。
- 被请求方「告知」请求方:「我不再给你发消息了」
- 请求方「回应」被请求方:「好的我知道了」,双方扔弃资源。
长链接
什么是长链接?长链接就是强制连接不要被关闭。
Q: 为什么会出现长链接?
A: 因为并不是所有的设备(电脑、手机等)都在公网内,很多设备都在内网中,如:公司、小区的内网。手机就处在运营商部署的内网当中,内网内的设备想要和外界通信,需要运营商服务器分配的端口才能实现。内网内所有主机占用的端口都是网关的端口,服务器分配端口是相对耗费资源的,为了节省资源,服务器会检测长时间不发消息的设备会将其端口关闭,这就导致设备无法与外界通信,如手机软件不会收到推送、消息等。为了突破这个限制,需要用「欺骗」网关的方式 —— 心跳来建立一个长连接,周期时间内设备都会发送一个无用的 TCP 消息,网关会认为该端口一直被占用,所以不会关闭该端口。
HTTPS
HTTP over SSL
HTTPS 并不是单独的协议,而是 HTTP 建立在 SSL 上,就称作 HTTPS。
SSL: Secure Socket Layer(早期),现在为 TLS: Transport Layer Secure。
SSL 或 TLS 都是建立在 HTTP 之下增加的一个安全层,用于保障 HTTP 的加密传输。
HTTPS 的本质是在客户端和服务器之间协商出一个对称密钥,每次发送之前将内容加密,收到之后解密。
Q: 为什么不直接用非对称加密?
A: 非对称加密很慢,计算过程复杂。
HTTPS 连接
过程大概如下:
- 客户端请求建立 TLS 连接;
- 服务器发回证书;
- 客户端验证服务器证书;
- 客户端信任服务器后,和服务器协商对称密钥;
- 使用对称密钥开始通信。
具体:
客户端先请求和服务端建立连接,发送一个 Client Hello 的消息,表示「我要建立连接」,并附加 TLS 版本和客户端能接受的 Cipher Suite(加密套件,非对称以及对称加密以及哈希算法)信息和随机数;
服务端根据客户端的附带信息选择能支持的 TLS 版本、对应算法,再加上服务器的随机数和客户端发过来的随机数一并打包,随着 Server Hello 返回给客户端;
服务端向客户端发送一个证书,证书的核心是给客户端公钥,证书还包含服务器的地址、证书签名等信息;
-
客户端首先验证证书中的服务器地址以及证书签名等信息,假如能证书签名验证通过,能够证明服务器的信息是真实的,那么如何验证证书签名?在 3 中,证书中还会附带签名机构的信息,里面包含证书机构公钥、证书机构其他信息。假如证书机构公钥能够对证书签名进行验证,那么说明服务器的证书信息是真实的。但是该「真实」是有前提的,仅仅能证明该证书确实是证书机构主人所签发的,无法证明该「主人」就是真实的那个主人,此时证书机构也要提供一个证书机构的签发方信息,也就是跟证书,像循环一样,但到这一步,已无需再附带其他信息,因为该根证书是有可靠来源的,来自操作系统内部,无论是 macOS 还是 Windows 还是 Android、iOS 等都有根证书列表,跟证书列表是被操作系统的研发官方所认证的,如微软、Apple、Google 或各浏览器的官方等,所以只要浏览器和操作系统不被破坏那么这个根证书就是可信赖的。整个嵌套关系如下:
在浏览器中,亦可看到该证书层级嵌套。
顶层为根证书,下一层为证书机构颁发证书,最下层为 Facebook 服务器的证书。 客户端拿到服务器公钥后,会进行整个过程唯一一次非对称加密操作,客户端会对 Pre-master Secret 非对称加密(本质还是一个随机数),发给服务端。现在双方已经有足够信息「生产」对称密钥了,此时客户端、服务端会根据 Pre-master Secret、客户端随机数和服务端随机数生产出一个 Master Secret(它本身并不是密钥),Master Secret 可以生产出一个密钥,但密钥实际包括:客户端加密密钥、服务端加密密钥、客户端 MAC Secret 以及服务端 MAC Secret,MAC 指 HMAC(Hash-based Message Authenticate Code)改良版 hash,MAC Secret 用来做验证身份和签名的操作。
虽然两个随机数都是明文传的,但是通过数学原理依然可以达到安全保密的效果。
- 开始加密通信,将前面的 Client Hello、Server Hello、服务器证书、Pre-master Secret 等使用加密密钥加密使用 MAC Secret 进行类似签名的操作发给服务端,并且会通知服务端「我要开始加密通信了」,同理服务器也会发「我要开始加密通信了」,也会将一系列信息(还包含客户端发来的加密信息和签名信息)打包加密签名发给客户端,客户端同样的验证方式进行验证。至此,验证过程结束,接下来客户端就可以进行正常的 HTTPS 请求了,此为第一个 HTTPS 请求。
Q: 在 5 中为什么生产两个加密密钥?
A: 出于安全考虑,客户端发消息用客户端加密密钥、服务端发消息用服务端加密密钥能勾搭到相对安全的效果。