在互联网应用开发中,应用服务的用户登录和权限控制是至关重要的一个环节,直接影响到应用的安全运营,也影响到用户的隐私保护,因此用户登录和权限控制是互联网应用开发的必要功能组件, 其涉及到认证、授权、鉴权和权限控制四个功能环节。本文将梳理互联网应用开发中的单点登录(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的方式获取授权,整个流程交互如下,
- 用户通过浏览器访问web服务
- 后端服务中一个通用的web filter校验请求,若发现请求中没有授权令牌,则跳转至用户登录模块,让用户进行登录
- 用户输入用户名和密码,若登录成功,则将授权令牌存储在浏览器的cookie中
- 将用户跳转回用户的访问页面,此时请求中cookie将自动带上授权令牌
- 后端服务的web filter校验请求cookie中的授权令牌,若合法且有效,则用户可以正常访问后端服务,否则返回错误消息,提示用户再次登录。
- 用户注销登录时,web应用服务将存储在浏览器cookie中的授权令牌清除,并注销redis中的令牌。
对于移动应用,通过用户名和密码的方式获取授权,整个流程交互如下,
- 用户打开移动应用,应用提示用户登录
- 用户输入用户名和密码,若登录成功,则将授权令牌返回给移动应用,存储在手机上的应用存储空间。
- 进入应用后,移动应用访问后端服务,授权令牌携带在请求header中
- 后端服务中一个通用的web filter对请求header中的授权令牌进行校验,若合法且有效,则用户可以正常访问后端服务,否则提示用户登录。
- 用户注销登录时,移动应用将授权令牌清除,并注销redis中的令牌。
由于移动应用无法像web应用一样,通过浏览器cookie的方式自动携带授权信息,移动应用需要自己处理授权信息的存储和请求发送。
2.4 第三方登录架构实现
相比于第一方登录,第三方登录最大的好处在于,可以让用户使用一个常用的第三方账号直接登录,例如微信、QQ、淘宝账号等,省去了用户注册账号的步骤,也免去用户记忆各种应用账号密码的烦恼,方便了用户的快速使用,进而降低用户接入成本。
对于第三方登录,目前各大互联网开放平台大多采用业界标准的OAuth 2.0授权码模式。对于OAuth 2.0的授权码模式和更多详细介绍将有另外一篇文章讨论,这里对其不扩展。
代码实现(待开发)
一个简单的架构实现如下,
图中亮红色线和方格为单点登录架构所需实现的组件和功能,其包括,
- OAuth 2.0授权服务器:提供用户登录授权,实现跳转返回,授权令牌(token)的颁发、校验和注销
- Token接口:用于客户端调用,通过授权码换取授权令牌
- Web filter:获取请求中的授权令牌并校验,若合法则通过请求,否则返回错误消息。
图中有两种应用类型,web应用和移动应用,两种获取授权方式基本一样。
对于web应用,整个流程交互如下,
- 用户通过浏览器访问Web服务
- 后端服务中一个通用的web filter校验请求,若发现请求中没有授权令牌,则跳转至第三方用户登录服务
- 在第三方登录服务中,用户输入用户名和密码,若登录成功,则跳转回web应用,并返回授权码
- Web应用将获取到的授权码,调用后端Token接口,该接口将根据授权码+应用ID+应用Secret,向第三方申请授权令牌,若一切正常,第三方颁发授权令牌给Web应用,Web应用将获取到的授权令牌存储在浏览器的cookie中。
- 将用户跳转回用户的初始访问页面,此时请求中cookie将自动带上授权令牌
- 后端服务的web filter校验请求cookie中的授权令牌,若合法且有效,则用户可以正常访问后端服务,否则返回错误消息,提示用户再次登录。
- 用户注销登录时,web应用服务将存储在浏览器cookie中的授权令牌清除,并注销redis中的令牌。
移动应用和web应用的流程类似,整个流程交互如下,
- 用户打开移动应用,应用提示用户登录,并将用户导向第三方登录服务
- 在第三方登录服务中,用户输入用户名和密码,若登录成功,则跳转回移动应用,并返回授权码给移动应用。
- 移动应用将获取到的授权码,调用后端Token接口,该接口将根据授权码+应用ID+应用Secret,向第三方申请授权令牌,若一切正常,第三方颁发授权令牌给移动应用,移动应用将获取到的授权令牌存储在手机上的应用存储空间。
- 进入应用后,移动应用访问后端服务,授权令牌携带在请求header中
- 后端服务中一个通用的web filter对请求header中的授权令牌进行校验,若合法且有效,则用户可以正常访问后端服务,否则提示用户登录。
- 用户注销登录时,移动应用将授权令牌清除,并注销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. 快速接入第三方用户 |