单点登录技术概述

2019-01-21
9 min read

根据维基百科的解释,单点登录(Single sign-on,SSO)的作用是:

用户只需要登录一次就可以访问所有相互信任的应用系统

打开你手机中安装的任意一款软件,比如购物软件,你可以查看个人信息、查看个人的购物记录或者选择商品并下单;比如微信,你同样可以查看自己的个人信息、查看朋友圈或者向好友发送消息;再比如你打开豆瓣,你发现你可以在豆瓣电影中搜索某部电影的评价、你可以在豆瓣读书下搜索某部图书的价格、你也可以在豆瓣中写日记。今天我们所使用的绝大部分软件都有各种不同的功能(特别是国内的软件),这其中的大部分功能只有在我们登陆后才能使用。

因为专业细分,同一款软件内不同的模块可能由不同的团队在维护与开发,从软件工程的角度来看,软件模块的切分与隔离也是非常合理的。不同的模块有些功能是重复的,比如对用户有效性的验证:你查询个人信息的时候因为隐私原因,你必须先登录;你向朋友发送消息时系统也要验证用户的有效性,否则拒绝发送信息,不登录你甚至连历史记录都看不见。如果用户每打开一个模块就需要重新登录一次,我想除了与政府业务相关的软件,这款软件应该早就没人用了吧 😄。

解决这类问题一个常见的方法是,用户登录成功后在一个所有模块都能访问到的地方保存一份加密的用户登录凭证(Ticket),其他服务使用这个凭证来验证用户信息。如果某个服务发现凭证无效,这个服务就会拉起登录页面,强制用户登录,并随后保存新的有效凭证供其他服务使用。

这里先大致描述一下 SSO 的工作流程:

  1. 用户打开软件,企图查询自己的购物记录,购物模块需要验证用户的有效性
  2. 购物模块没有发现能验证用户有效身份的数据,拉起登录界面让用户登录(登录过程由软件的登录模块完成)
  3. 用户输入有效的用户名与密码(或者其他登录方式),登录模块发送加密的用户名和密码给 SSO 后台服务
  4. SSO 解密用户数据并验证用户名和密码,如果验证通过则下发登录凭证给软件(软件的登录模块)
  5. 用户再次企图查询自己的购物记录,购物模块从登录模块中获取登录凭证并携带着凭证去购物模块的后台请求用户购物记录
  6. 购物模块后台在查询用户历史记录前先拿着登录凭证到 SSO 后台验证这个凭证的有效性,如果 SSO 返回有效,则购物后台查询并返回用户数据供前端显示,否则拒绝提供服务
  7. 其他模块在需要验证用户信息时会从登录模块获取凭证,随后将这个凭证发送给 SSO 进行验证,验证通过则提供服务,否则拒绝

从上面的讲述中可知,一个 SSO 系统有两个职责,一个是登录,一个是验证,其中最主要的功能是验证用户的登录凭证。

SSO 是 IO 密集型服务(相对的是 CPU 密集型,即计算密集型),SSO 拿到凭证后一般解密完就可以实现凭证的有效性验证。为了提供高并发、低时延的服务,SSO 系统一般是典型的分布式系统。大型公司会在节假日等特殊日期展开活动,短期内将有远超平时的流量请求,所以 SSO 系统的可伸缩性非常重要。

广义上讲 OAuth(SAML) 是单点登录的一种形式,例如,你只要登录了微信就可以授权并以微信的身份登录微信中大部分的小程序。

SSO & Kerberos & OAuth 2.0

Kerberos 是由 MIT 开发的可以在非安全网络环境下对用户进行安全认证的授权协议。SSO 所使用的几乎所有认证概念在这个协议中都有所体现,大部分 SSO 系统可以认为是对这个协议一个子集的实现。本文不对 Kerberos 做详细的介绍,下面有几个讲述 Kerberos 的博客可供参考:

  1. https://blog.csdn.net/wulantian/article/details/42418231
  2. https://www.cnblogs.com/ulysses-you/p/8107862.html

OAuth 协议用于实现两个不同服务之间的数据访问。联合登录是 OAuth 的一个应用场景。打开JD APP,进入登录页面你会发现你可以使用几种不同的方式进行登录,比如微信。使用微信登录 JD 有一个前提,用户的微信账号和 JD 账号要有个关联的关系,不然 JD APP 无法确定用户的身份。假设这个关系已经存在了,那么 JD APP 在登录时需要从微信那边获取用户的信息以判断用户身份,这个获取用户身份信息的过程所使用的协议一般就是 OAuth2.0。

这里对 OAuth2.0 有简短的介绍。

分布式系统简介

推荐两本书籍:

  1. 《大型网站技术架构:核心原理与案例分析》,作者:李智慧
  2. 《构建高性能Web站点》,作者:郭欣

后一本比前一本多了更多细节,所需要的背景知识也更多。

单机性能终究是有限的,而且短期内单机性能的增长也很有限,当业务快速增长时(特别是突发事件),仅依靠提高单机性能来提高系统处理能力是非常不现实的。分布式系统使用大量常见微机集群来提供服务,需要时增加机器数量既可提高系统性能。几年前阿里巴巴提出了企业去 IOE (IBM 的小型机、Oracle 的数据库、EMC 的专业存储设备),其实现方式就是用常见的硬件与开源软件去替换昂贵的 IOE 设备与软件。分布式技术可以在大大降低软硬件投入的同时获得不弱于 IOE 设备的性能,并获得更好的灵活性。

分布式系统的基础原理并不复杂,使用开源工具构建分布式系统也非常方便。分布式系统的开源实现非常多,根据业务需求选择合适的工具是非常重要的能力,所以较为深入的理解分布式原理还是比较重要的。下面简要介绍部分分布式系统中常用的技术。

负载均衡

分布式系统使用机器集群来提供服务,不同的请求可能由不同的机器进行处理,将大量请求均匀的分发给各个机器的行为称为负载均衡

以 Web 服务为例,用户点击页面链接时就可以使用负载均衡技术了。

用户点击 Web 链接时系统会先查询 DNS 以获得链接的 IP 地址。大部分 ISP 的 DNS 服务器可以为同一个域名配置多个 IP (A 或 AAAA 记录),DNS 使用轮询算法,每次 DNS 查询返回下一个 IP,就可以将请求分配到不同的机器中。但是这种方式不具灵活性,不同 ISP 提供的 DNS 服务不同,且如果有一台机器发生了故障,刷新 DNS 也需要一段时间,这段时间里部分用户是无法获得服务的。按地理区域进行 DNS 解析也是常用的 DNS 负载均衡手段,不同区域的 DNS 在解析相同 URL 时返回离用户最近的服务器 IP 地址,在实现负载均衡的同时也提高了用户体验。

假设 Web 服务已经完成了 DNS 查询,并开始服务请求,那随后大量的报文就发往对应机器了。此时的负载均衡可以按照 OSI 7层网络分层进行划分。

应用层负载均衡

HTTP 协议要求 HTTP 报文首部行必须包含 host 字段以指明需要访问的服务,web 服务器可以以 host 为准将服务分发给不同的处理机器。这是应用层负载均衡的一个例子。上面讲的 DNS 轮询也可划分为应用层负载均衡技术。

302 重定向也是一种简单的负载均衡方式。以上两种方式都依赖一台转发机器,这台机器一般会成为整个系统的性能瓶颈。

常见的工具如:Nginx

网络层负载均衡

同一集群的机器有不同的 IP 地址,请求报文到达指定机器 P 后此机器会修改报文的目的地址,将报文地址设定为同一集群中的一台,响应的所有报文会经由机器 P 进行修改再返回给客户端。

链路层负载均衡

链路层负载均衡修改的是以太网帧中的 MAC 地址。请求报文到达交换机后,交换机将以太网帧中的目的 MAC 地址修改为当前集群机器中的一台,处理完后处理机直接将以太网帧交给交换机就可以了,这就消除了上面几种负载均衡方式的转发机瓶颈问题。

负载均衡示例

按地域部署机房

很多网站提供了多地点 PING 服务,以站长工具为例,进入网页后选择 PING 检测并输入 www.baidu.com。从检测结果可知,从福建福州 PING 百度得到的 IP 是江苏宿迁电信的;哈尔滨联通访问到的是北京百度网讯科技;从广州电信访问到的是广州市百度节点;从德国访问的是香港的机房;从美国访问的是百度美国 BGP 节点,等等。大型网站在不同地理区域都部署的有机房,按照就近原则,这些机房大部分情况下服务相邻区域的访问。

不同区域的机房之间使用网络专线连接,用于内部数据的交换与请求。专线的速度要比 ISP 提供的普通网络快很多,而且不同机房之间按照权重与体量可以购买不同带宽的专线,可以在保证用户体验的前提下降低费用。

其他与地理位置相关的技术如使用 CDN 缓存静态文件等

单机房内负载均衡

单一机房内的负载均衡一般就是将请求分发给不同的机器或者容器进行处理,常见的技术有硬件和软件两大类。硬件负载均衡手段相对于软件而言前期投入较大,小型系统可以优先考虑软件负载均衡。

目前使用最广泛的三种负载均衡软件 Nginx/LVS/HAProxy,他们都是基于 Linux 的开源免费的负载均衡软件,这些都是通过软件级别来实现,所以费用非常低廉。参考

graph LR
	A(VIP: 192.168.31.5)
	A -.->B[Nginx 01,192.168.31.3]
	A -.->C[Nginx 02,192.168.31.4]
	B --> D[Web 01, 192.168.31.10]
	B --> E[Web 02, 192.168.31.11]
	C --> D
	C --> E
	D --> F[Database]
	E --> F[Database]
	

Keepalived

同一负载均衡集群中一般有多台物理机,如果其中一台物理机宕机(或者灰度发布需要摘除部分机器),我们需要一定的手段将这些机器从集群中暂时剔除,在机器恢复后再加入到集群中。

Keepalived 软件起初是专为 LVS 负载均衡软件设计的,用来管理并监控LVS集群系统中各个服务节点的状态,后来又加入了可以实现高可用的VRRP功能。因此,keepalived 除了能够管理 LVS 软件外,还可以作为其他服务的高可用解决方案软件。简单来说 Keepalived 软件可以监控集群中的机器,当某一台机器宕机时自动将其从集群中剔除。

如下图所示,前端有两台的 Nginx 负载均衡器,用来分发接收到客户端的请求。现在要在两个 Nginx 负载均衡器上做高可用配置,Nginx01 作为主节点,Nginx02 作为备节点,那么这两台机器中都需要配置 Keepalived。参考

Nginx

Nginx 是应用层负载均衡工具,可以针对 http 应用做一些分流的策略,比如针对域名、目录结构,它的正则规则比 HAProxy 更为强大和灵活,这也是它目前广泛流行的主要原因之一。

LVS

LVS 是网络层负载均衡工具,所以它几乎可以对所有应用做负载均衡,包括 http、数据库、在线聊天室等等。参考

HAProxy

HAProxy 与 Nginx 类似,但 HAProxy 可以工作在第四层所以功能比 Nginx 要多,但实际应用好像没有 Nginx 出名。

总结

不同阶段的系统需要使用不同的技术手段。刚起步的系统其访问量并不算大,使用 Nginx 或者 HAProxy 做单点负载均衡是比较好的选择,此时不需要特别专业的团队也不需要投入大量资金;第二阶段的系统访问量进一步增加,使用单点负载均衡已经不足以满足业务要求,此时比较好的选择是购买商业负载均衡工具,如果技术条件允许,可以考虑使用 LVS;第三阶段,公司应该有足够的实力选择合适的工具了,此时 LVS 会成为主流。

单机负载均衡

同一台机器内部也有负载均衡的影子,Nginx 可以配置多个 worker 进程,nginx 使用一定的策略让每个进程都工作在合适的状态。

网卡和 CPU 之间也有可以配置负载均衡。在网络流量非常大的情况下将网卡中断绑定到若干个 CPU 核心上可以降低单个 CPU 核心的负担以提高系统的性能;

对于数据库这样的应用,把磁盘控制器中断、网卡中断绑定到不同的 CPU 核心可以降低数据库的响应时间,提高数据库性能。linux 2.4 之后提供了 SMP IRQ Affinity 技术,可以绑定中断到指定的 CPU 以实现中断的负载均衡。