单点登录的通用架构实现

图片来自pixabay.com的skeeze-272447会员

在互联网应用开发中,应用服务的用户登录和权限控制是至关重要的一个环节,直接影响到应用的安全运营,也影响到用户的隐私保护,因此用户登录和权限控制是互联网应用开发的必要功能组件, 其涉及到认证、授权、鉴权和权限控制四个功能环节。本文将梳理互联网应用开发中的单点登录(single-sign-on)技术架构实现,单点用户登录也被称为统一用户登录。

  • 注1:本文讨论的互联网应用开发,主要是指web应用和移动应用开发两个技术领域。
  • 注2:本文所讨论的认证、授权、鉴权和权限控制的概念定义来自其这篇文章。因此,在阅读本文之前,建议先请阅读前篇文章。

1. 什么是单点登录

单点登录不仅仅是实现用户登录这单一功能,它更是一个技术架构,一个解决方案,能够提供统一的认证、授权、鉴权和权限控制,提供相应的组件,方便用户的使用,以及应用的开发快速接入。所以,单点登录不是一个简单的登录模块。

单点登录,从字面来说,是让用户使用同一账号可以访问不同应用服务,而且用户只需登录一次(输入一次用户名和密码)便可实现多次授权。单点登录能够很大程度上方便用户的登录操作。这是从用户的角度出发来看单点登录。

单点登录还需要考虑的另外一个重要需求,是方便应用的对接使用,单点登录架构必须提供相应的客户端组件和配置,使得应用可以简单、方便、快速地接入单点登录功能,同时实现安全的权限控制。

因此,单点登录作为一个安全技术架构,其主要包括用户登录和权限控制两大功能需求,前者涉及认证和授权环节,后者涉及鉴权和权限控制环节。

2. 用户登录

用户登录的过程一般会涉及到用户认证和授权两个环节,这两个环节往往一并发生,即在确认用户身份的同时,也完成用户的授权。

根据登录功能模块的提供方,可以将登录划分为第一方登录和第三方登录两种类型,

  • 第一方登录:应用的登录服务由自己提供。由于是己方登录和己方应用,所以登录服务和应用服务之间相互可信,这大大方便了授权过程。
  • 第三方登录:应用的登录服务由第三方提供,使用第三方的用户账号登录,比如通过微信、QQ、支付宝账号进行登录等等。目前各大互联网开放平台大多采用业界标准的OAuth 2.0授权码模式实现第三方登录。

第一方登录和第三方登录,在用户认证技术的实现上没有太大差别,但是授权流程有区别。具体的授权流程和区别在下文的架构实现中讨论。

2.1 认证方式

在互联网开发中,常见的认证方式有,

  • 用户名和密码
  • 手机短信验证码
  • 手机应用二维码扫描
  • 用户手势
  • 基于时间序列和用户相关的一次性口令

本文为了方便讨论,将使用简单的http基本认证(http basic authentication)方式进行用户登录,即用户名和密码的方式。更多的认证方式,一般可以通过identity provider的扩展接口来实现。

2.2 授权方式

一旦用户登录成功,即可获取到相应的授权信息。

在互联网应用开发领域,授权的实现技术主要包括如下几种,

  • 通过web服务器的session机制,一个访问会话保持着用户的授权信息
  • 通过web浏览器的cookie机制,一个网站的cookie保持着用户的授权信息
  • 颁发授权令牌(token),一个合法有效的令牌中保持着用户的授权信息

前面两者常见于web开发,需要有浏览器的支持。而对于移动应用及其其它无法使用cookie的场景中,大多可以采用token的实现并通过header方式携带该授权信息在请求中。

本文为了同时支持web应用和移动应用的授权,授权信息将以授权令牌(token)形式颁发,对于web应用,该授权令牌通过cookie携带于请求中,而对于移动应用,则通过header方式携带于请求中。

2.3 第一方登录架构实现

由于是己方登录实现,所以登录服务和应用服务之间相互可信,这大大方便了授权过程。

在第一方登录的场景下,一般可以通过两种方式获得授权,

  • 若应用通过浏览器访问,则可以通过同域cookie方式实现共享授权
  • 若应用不是浏览器访问,或者无法通过cookie技术,则可以直接通过用户名和密码来换取授权。需要注意的是,应用服务可以拿到用户名和密码等敏感信息,这是基于可信应用的先决条件。

代码实现(待开发)

一个简单的架构实现如下,

图中亮红色线和方格为单点登录架构所需实现的组件和功能,其主要包括,

  • 单点登录模块:提供用户登录界面,实现跳转返回,授权令牌(token)的颁发、校验和注销
  • Web filter:获取请求中的授权令牌并校验,若合法则通过请求,否则返回错误消息。

对于web应用,通过共享cookie的方式获取授权,整个流程交互如下,

  1. 用户通过浏览器访问web服务
  2. 后端服务中一个通用的web filter校验请求,若发现请求中没有授权令牌,则跳转至用户登录模块,让用户进行登录
  3. 用户输入用户名和密码,若登录成功,则将授权令牌存储在浏览器的cookie中
  4. 将用户跳转回用户的访问页面,此时请求中cookie将自动带上授权令牌
  5. 后端服务的web filter校验请求cookie中的授权令牌,若合法且有效,则用户可以正常访问后端服务,否则返回错误消息,提示用户再次登录。
  6. 用户注销登录时,web应用服务将存储在浏览器cookie中的授权令牌清除,并注销redis中的令牌。

对于移动应用,通过用户名和密码的方式获取授权,整个流程交互如下,

  1. 用户打开移动应用,应用提示用户登录
  2. 用户输入用户名和密码,若登录成功,则将授权令牌返回给移动应用,存储在手机上的应用存储空间。
  3. 进入应用后,移动应用访问后端服务,授权令牌携带在请求header中
  4. 后端服务中一个通用的web filter对请求header中的授权令牌进行校验,若合法且有效,则用户可以正常访问后端服务,否则提示用户登录。
  5. 用户注销登录时,移动应用将授权令牌清除,并注销redis中的令牌。

由于移动应用无法像web应用一样,通过浏览器cookie的方式自动携带授权信息,移动应用需要自己处理授权信息的存储和请求发送。

2.4 第三方登录架构实现

相比于第一方登录,第三方登录最大的好处在于,可以让用户使用一个常用的第三方账号直接登录,例如微信、QQ、淘宝账号等,省去了用户注册账号的步骤,也免去用户记忆各种应用账号密码的烦恼,方便了用户的快速使用,进而降低用户接入成本。

对于第三方登录,目前各大互联网开放平台大多采用业界标准的OAuth 2.0授权码模式。对于OAuth 2.0的授权码模式和更多详细介绍将有另外一篇文章讨论,这里对其不扩展。

代码实现(待开发)

一个简单的架构实现如下,

图中亮红色线和方格为单点登录架构所需实现的组件和功能,其包括,

  • OAuth 2.0授权服务器:提供用户登录授权,实现跳转返回,授权令牌(token)的颁发、校验和注销
  • Token接口:用于客户端调用,通过授权码换取授权令牌
  • Web filter:获取请求中的授权令牌并校验,若合法则通过请求,否则返回错误消息。

图中有两种应用类型,web应用和移动应用,两种获取授权方式基本一样。

对于web应用,整个流程交互如下,

  1. 用户通过浏览器访问Web服务
  2. 后端服务中一个通用的web filter校验请求,若发现请求中没有授权令牌,则跳转至第三方用户登录服务
  3. 在第三方登录服务中,用户输入用户名和密码,若登录成功,则跳转回web应用,并返回授权码
  4. Web应用将获取到的授权码,调用后端Token接口,该接口将根据授权码+应用ID+应用Secret,向第三方申请授权令牌,若一切正常,第三方颁发授权令牌给Web应用,Web应用将获取到的授权令牌存储在浏览器的cookie中。
  5. 将用户跳转回用户的初始访问页面,此时请求中cookie将自动带上授权令牌
  6. 后端服务的web filter校验请求cookie中的授权令牌,若合法且有效,则用户可以正常访问后端服务,否则返回错误消息,提示用户再次登录。
  7. 用户注销登录时,web应用服务将存储在浏览器cookie中的授权令牌清除,并注销redis中的令牌。

移动应用和web应用的流程类似,整个流程交互如下,

  1. 用户打开移动应用,应用提示用户登录,并将用户导向第三方登录服务
  2. 在第三方登录服务中,用户输入用户名和密码,若登录成功,则跳转回移动应用,并返回授权码给移动应用。
  3. 移动应用将获取到的授权码,调用后端Token接口,该接口将根据授权码+应用ID+应用Secret,向第三方申请授权令牌,若一切正常,第三方颁发授权令牌给移动应用,移动应用将获取到的授权令牌存储在手机上的应用存储空间。
  4. 进入应用后,移动应用访问后端服务,授权令牌携带在请求header中
  5. 后端服务中一个通用的web filter对请求header中的授权令牌进行校验,若合法且有效,则用户可以正常访问后端服务,否则提示用户登录。
  6. 用户注销登录时,移动应用将授权令牌清除,并注销redis中的令牌。

2.5 第一方登录和第三方登录的授权区别

第一方登录和第三方登录之间最大的区别在于授权流程。

第一方登录在用户被认证之后,即刻颁发授权令牌,应用服务一般无需介入授权流程。而第三方登录在用户认证之后,先颁发给授权码给应用服务,应用服务还需根据这个授权码去换取授权令牌,换句话说,应用服务需介入授权流程。

这里有个问题是,为什么在第三方登录中,应用服务会被介入授权流程?主要原因是在第三方登录场景中,除了用户需要被认证,应用服务本身也需要被认证。而在第一方登录场景,只需用户认证,应用服务本身是可信的,其无需被认证。

2.6 登录架构实现小结

为了对比上述两个登录架构实现的不同之处,下面将各自的特点小结为下表,

第一方登录架构 第三方登录架构
登录功能 由己方提供 由第三方提供
使用第三方的用户账户
登录实现 通过http basic auth OAuth 2.0授权码模式
应用是否可信 可信,应用可以接触用户密码等敏感信息,应用本身无需认证 不可信
应用是否需要认证 否 是
应用是否可以接触用户密码等敏感信息 是 否
授权过程 登录成功后颁发token 1. 登录成功后返回授权码给应用
2. 应用根据授权码和应用ID换取token
Web页面的服务请求 通过cookie带上token 通过cookie带上token
移动应用的服务请求 通过header带上token 通过header带上token

3. 权限控制

在互联网应用开发中,安全控制在后端服务中实现。整个权限控制的过程一般会涉及到鉴权和权限控制两个环节。

鉴权的实现方式一般有两种,

  • 通过授权服务器:由于授权令牌是由授权服务器颁发的,所以由授权服务器校验也是自然而然的事情。这种方式会导致授权服务器的访问热点问题,为了缓解热点,缓存是必要配置。
  • 通过加解密:通过授权令牌的加解密方式,确认令牌的合法性。即,授权服务器颁发一个通过公钥加密的授权令牌,在校验的时候若能够通过私钥解密,则该令牌为合法令牌。这种方式有一个缺点是,一旦令牌注销失效后,信息无法及时通知到解密方。对令牌的注销时效性要求不高的场景下,可以使用这种方式,其大大缓解了授权服务器的访问热点问题。

若根据鉴权的架构方式,则可分为分布式和集中式两大类,

  • 分布式:通过后端web服务的filter实现分布式控制,在各个服务应用的运行实例中进行鉴权
  • 集中式:通过网关实现集中式控制,在访问流量的入口进行鉴权

鉴权后访问控制粒度从大到小有如下三种分类,

  • Http方法级别
  • API接口级别
  • API实现级别

下面将对鉴权的架构实现和控制粒度进行详细讨论。

3.1 分布式鉴权架构

分布式鉴权的架构如下图,

在web应用开发中,可以通过web应用服务的filter功能,对所有请求中的授权令牌进行校验,实现分布式鉴权。分布式鉴权实现简单,可以快速实现并使用,但随着应用服务架构的水平和垂直扩展,filter的升级将会成为头痛的问题。

3.2 集中式鉴权架构

集中式鉴权的架构如下图,

通过网关,在访问流量的入口,对所有请求中的授权令牌进行校验,实现集中式鉴权。集中式鉴权减轻了应用服务的接入成本,但会增加了网关的性能负荷。

3.3 分布式和集中式鉴权的优缺点

分布式鉴权和集中式鉴权有各自的优缺点小结为下表,

分布式鉴权 集中式鉴权
优点 简单,可以分散校验热点 应用服务接入成本低,方便鉴权管理
缺点 filter版本升级困难,需要对所有应用服务进行依赖更新 需要网关的支持,并且鉴权操作将增加网关的性能负荷
应用场景 简单的互联网web应用开发 大型互联网web应用开发

3.4 访问控制的粒度

鉴权后就会得到请求访问的权限,接下来则是根据权限来控制请求访问的允许或禁止。

根据访问控制粒度从大到小,可划分出如下几个控制级别,

  • Http方法级别
  • API接口级别
  • API实现级别

3.4.1 Http方法级别

若web应用服务是按照Restful的规范来开发接口服务,则可以根据Restful的定义对Http的不同方法实现不同的访问控制。

根据Restful的规范定义,Http方法具有如下安全性和幂等性的特点,

HTTP方法 Restful定义 安全性 幂等性
GET 获取资源 是 是
POST 创建资源 否 否
PUT 更新资源 否 是
DELETE 删除资源 否 是
HEADER 资源元信息 是 是
OPTIONS 是 是

表中,安全性和幂等性的概念如下,

  • 安全性是指该方法不改变资源的状态,即不改变后台数据
  • 幂等性是指该方法执行的同一操作多次,其结果保持一致

可以看到,对于安全的HTTP方法,比如GET操作,其访问控制可以宽松。而对于一些非安全操作,则需要根据不同权限来控制访问请求。在实际应用过程中,可以执行如下的权限控制规则,

  • 匿名用户:可以执行GET请求
  • 普通登录用户:可以执行GET、POST、PUT请求
  • 管理员:可以执行所有类别请求

总的来说,根据Http请求的方法和URI进行访问控制。

3.4.2 API接口级别

这个权限控制的粒度深入到代码控制层(Controller),不同的权限,可以访问不同的API接口。

一个spring mvc的代码样例如下,

@Controller
public class TokenApp {

    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    public Principal getUserInfo(Principal me){
        return me;
    }

}

上面的代码表示,具有管理员角色的权限才能获取当前用户信息。

这个粒度的权限访问控制基本可以满足大多数应用场景需求。

3.4.3 API实现级别

在应用的实现级别进行控制,这个控制粒度非常细,其根据不同权限返回不一样的调用结果,例如,同样是查看学生列表,若是班主任,则返回一个班的学生列表,若是校长,则返回一个学校的学生列表。

4. 小结

本文主要从用户登录和权限控制两大功能方面梳理其架构实现,这些实现可以结合实际的应用场景,进行相应的匹配。

第一方登录 第三方登录
分布式权限控制 1. 简单web应用场景
2. 登录服务和应用服务相互可信
3. 登录服务和应用服务同域,可以授权共享
1. 简单web应用场景
2. 登录服务和应用服务相互不可信
3. 登录服务和应用服务不同域,各自独立开发部署,无法授权共享
4. 应用和登录服务模块彻底解耦,通过跨域跳转实现用户登录。
集中式权限控制 1. 大型web应用场景,有网关组件部署 1. 大型web应用场景,有网关组件部署
2. 快速接入第三方用户

5. 参考资料

  1. HTTP Authentication: Basic and Digest Access Authentication
  2. TOTP: Time-Based One-Time Password Algorithm