... views
这段时间对之前微服务安全相关的一些想法进行了进一步总结和归纳,理清了在之前文章里面没有想得太清楚的地方,例如服务间的认证与鉴权以及用户身份在服务调用链中的传递。
在这一系列文章里,我将尝试分为三个部分对微服务安全进行系统阐述:用户访问认证与鉴权,服务间认证与鉴权,外部系统访问控制。
微服务架构的引入为软件应用带来了诸多好处:包括小开发团队,缩短开发周期,语言选择灵活性,增强服务伸缩能力等。与此同时,也引入了分布式系统的诸多复杂问题。其中一个挑战就是如何在微服务架构中实现一个灵活,安全,高效的认证和鉴权方案。
相对于传统单体应用,微服务架构下的认证和鉴权涉及到场景更为复杂,涉及到用户访问微服务应用,第三方应用访问微服务应用,应用内多个微服务之间相互访问等多种场景,每种场景下的认证和鉴权方案都需要考虑到,以保证应用程序的安全性。本系列博文将就此问题进行一次比较完整的探讨。
一个完整的微服务应用是由多个相互独立的微服务进程组成的,对每个微服务的访问都需要进行用户认证。如果将用户认证的工作放到每个微服务中,存在下面一些问题:
由于在微服务架构中以 API Gateway 作为对外提供服务的入口,因此可以在 API Gateway 处提供统一的用户认证,用户只需要登录一次,就可以访问系统中所有微服务提供的服务。
HTTP 是一个无状态的协议,对服务器来说,用户的每次 HTTP 请求是相互独立的。互联网是一个巨大的分布式系统,HTTP 协议作为互联网上的一个重要协议,在设计之初要考虑到大量应用访问的效率问题。无状态意味着服务端可以把客户端的请求根据需要发送到集群中的任何一个节点,HTTP 的无状态设计对负载均衡有明显的好处,由于没有状态,用户请求可以被分发到任意一个服务器,应用也可以在靠近用户的网络边缘部署缓存服务器。对于不需要身份认证的服务,例如浏览新闻网页等,这是没有任何问题的。但 HTTP 成为企业应用的一个事实标准后,企业应用需要保存用户的登录状态和身份以进行更严格的权限控制。因此需要在 HTTP 协议基础上采用一种方式保存用户的登录状态,避免用户每发起一次请求都需要进行验证。
传统方式是在服务器端采用 Cookie 来保存用户状态,由于在服务器是有状态的,对服务器的水平扩展有影响。在微服务架构下建议采用 Token 来记录用户登录状态。
Token 和 Seesion 主要的不同点是存储的地方不同。Session 是集中存储在服务器中的;而 Token 是用户自己持有的,一般以 cookie 的形式存储在浏览器中。Token 中保存了用户的身份信息,每次请求都会发送给服务器,服务器因此可以判断访问者的身份,并判断其对请求的资源有没有访问权限。
Token 用于表明用户身份,因此需要对其内容进行加密,避免被请求方或者第三者篡改。 JWT(Json Web Token) 是一个定义 Token 格式的开放标准(RFC 7519),定义了 Token 的内容,加密方式,并提供了各种语言的 lib。
JWT Token 的结构非常简单,包括三部分:
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
这三部分使用 Base64 编码后组合在一起,成为最终返回给客户端的 Token 串,每部分之间采用".“分隔。下图是上面例子最终形成的 Token 采用 Token 进行用户认证,服务器端不再保存用户状态,客户端每次请求时都需要将 Token 发送到服务器端进行身份验证。Token 发送的方式 rfc6750 进行了规定,采用一个 Authorization: Bearer HHTP Header 进行发送。
Authorization: Bearer mF_9.B5f-4.1JqM
采用 Token 方式进行用户认证的基本流程如下图所示:
单点登录的理念很简单,即用户只需要登录应用一次,就可以访问应用中所有的微服务。API Gateway 提供了客户端访问微服务应用的入口,Token 实现了无状态的用户认证。结合这两种技术,可以为微服务应用实现一个单点登录方案。
用户的认证流程和采用 Token 方式认证的基本流程类似,不同之处是加入了 API Gateway 作为外部请求的入口。
用户登录
用户请求
用户权限控制有两种做法,在 API Gateway 处统一处理,或者在各个微服务中单独处理。
客户端发送的 HTTP 请求中包含有请求的 Resource 及 HTTP Method。如果系统遵循 REST 规范,以 URI 资源方式对访问对象进行建模,则 API Gateway 可以从请求中直接截取到访问的资源及需要进行的操作,然后调用 Security Service 进行权限判断,根据判断结果决定用户是否有权限对该资源进行操作,并转发到后端的 Business Service。
假设系统中有三个角色:
这些角色对资源的操作权限都可以映射到 HTTP Verb 上,如下表所示。
Role | Resource | Verbs |
---|---|---|
order_manager | /orders | ‘GET’ ‘POST’ ‘PUT’ ‘DELETE’ |
order_editor | /orders | ‘GET’ ‘POST’ ‘PUT’ |
order_inspector | /orders | ‘GET’ |
这种实现方式在 API Gateway 处统一处理鉴权逻辑,各个微服务不需要考虑用户鉴权,只需要处理业务逻辑,简化了各微服务的实现。
如果微服务未严格遵循 REST 规范对访问对象进行建模,或者应用需要进行更细粒度的权限控制,则需要在微服务中单独对用户权限进行判断和处理。这种情况下微服务的权限控制更为灵活,但各个微服务需要单独维护用户的授权数据,实现更复杂。
由于微服务进行权限判断时需要用户身份信息,该方案需要处理的另一个问题是如何把登录用户的信息从 API Gateway 传递到微服务中。如果是基于 Http,可以采用 Http header 实现,如果是其他协议,则需要在消息体中增加用户身份相关的字段。