如今微服务架构已经是互联网应用的事实标准。相比传统单体应用,微服务架构给我们带来了诸多好处:快速迭代、松耦合、独立部署、高可靠、技术栈选择灵活等等,但与此同时它也给我们引入了分布式系统的复杂性和由此带来的各种挑战。其中一个挑战就是如何在微服务架构下保证应用访问的安全。
应用安全是个很宽泛的领域,本文将只讨论认证和授权。相对于单体应用,微服务架构下的认证和授权涉及的场景更多更复杂:用户访问、外部系统调用、微服务内部调用等。如何针对各种场景设计一整套灵活、安全、高效的认证和授权方案对微服务开发者来说无疑是个巨大的挑战。
认证和授权是两个不同的概念,却很容易被混淆。理清他们的职责范围和关联关系有助于开发者进行清晰的架构思考与设计。
认证(authentication)是指用户证明自己身份的过程,它解决的是 “我是谁?”的问题。一般应用系统里面会给每个用户分配一个UID,在这个应用系统中UID就代表用户的身份,决定了该用户能做什么和不能做什么。但光有UID还不够,因为谁都可以声称自己是该UID的主人,所以用户在登录应用系统的时候除了出示UID,还要出示密钥(credential),只有当出示的密钥和用户注册时设置的密钥匹配的时候才算认证通过。
授权(authorization)则是指用户证明自己身份后,应用系统进一步判断用户可以访问哪些系统资源的过程,它解决的是“我能做什么?”的问题。授权往往是伴随认证来完成的。
由于HTTP是无状态的,请求与请求之间相互独立,所以用户登录后必须要使用一种方式来记住这个登录的状态,并且把这个用户的一连串的交互请求关联起来,这样用户就不必每个请求都带上密钥了,这就是会话(SESSION)的最大作用。当然会话还可以存储其他用户的信息,比如语言偏好、时区设置等,这里只关注其在用户认证和授权方面的作用。因此,一般来说一套完整的认证和授权方案应当包含三个部分:用户认证、会话管理和权限检查(如下图所示)。
针对Web应用,近十几年来业界出现过很多认证方法,下面列举一些常用的:
微软出品,与Windows账号体系(包括AD)完美结合,使用它用户可以以Windows用户的身份来自动登录你的应用,它能给Windows用户提供了一个比较好的单点登录的体验。它的底层支持Kerberos /NTLM协议,细节请参考官方文档 ,这里就不介绍了。以前IIS盛行的时候用的比较多,现在淘汰的差不多了。
这种方式以前用的比较多,比如好多古老的路由器登录的时候都会弹出下图这样的登录框,那就是使用的HTTP Basic认证:
这个协议通过HTTP头携带Base64编码后的用户名密码,示例:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
细节请参考rfc7617。
这种方式实现起来非常简单,当然缺点也很明显,Base64 只能称为编码,而不是加密,所以最好使用HTTPS传输。现在基本没有Web应用采用HTTP Basic认证了,但还可以把它用于内部API调用的认证。
摘要认证是针对Basic认证存在的诸多问题而进行的改良方案,主要通过传递用户名,密码等计算出来的摘要来解决在网络上明文发送密码的问题,示例:
Authorization: Digest username="Mufasa",
realm="[email protected]",
uri="/dir/index.html",
algorithm=MD5,
nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
nc=00000001,
cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ",
qop=auth,
response="8ca523f5e9506fed4657c9700eebdbec", //用户名,密码等计算出来的摘要
opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"
细节请参考rfc7616。
现在基本也没有Web应用采用HTTP Digest认证,但还可以把它用于内部API调用的认证。
我们平时在登陆各大互联网站时所看到的登陆页面基本都是基于Form认证,业界没有关于Form认证的标准,但大家的实现却大同小异, 基本的流程是:
这种方式跟HTTP Basic一样是明文传输的密钥,所以最好用HTTPS来传输。HTTP Form认证如今仍然Web应用最流行的身份验证方式。
一般的HTTPS网站只开了SSL单向认证,也就是说只有客户端校验服务器端证书(也就是检验身份)的过程。如果打开SSL双向认证,服务器端就可以通过客户端传过来的证书检验客户端的身份了,这就是SSL客户端认证。SSL客户端认证的基本原理是在SSL协议的握手阶段做这么几个步骤:
细节请参考rfc5246。SSL客户端认证可以用于内部服务API调用认证,也可以用于一些用户免登陆的场合,比如好多大公司办公电脑上都要预装一个证书,其实就是一个SSL客户端证书,用于IT系统的免登陆。
英文全称Hash-based MessageAuthentication Code ,它的基本原理是通过检查密钥和消息体的Hash摘要来做认证。认证的基本流程:
细节请参考rfc2104。目前这种认证技术广泛应用于Open API的认证,比如AWS、阿里云的API都是采用的这种认证技术。
下面对这些常用认证技术进行一个简单的比较和总结:
认证方法 |
密钥传输方式 |
支持密钥类型 |
适用场景 |
Integrated Windows Authentication |
密码加密的Challenge |
密码 |
Windows应用、使用Windows账号体系的企业应用 |
HTTP Basic |
明文 |
密码 |
内部API调用鉴权 |
HTTP Digest |
HASH摘要 |
密码 |
内部API调用鉴权 |
HTTP Form认证 |
明文 |
密码、一次性Token(OTP)、指纹、虹膜、面部特征、多因子认证等。 |
Web应用用户登录 |
SSL 客户端认证 |
SSL证书+签名 |
SSL证书 |
内部API调用鉴权、应用用户登录 |
HMAC |
HASH摘要(连同消息体) |
access key |
外部API调用鉴权 |
谈认证就不得不提单点登录技术(英文叫Single Sign On),这里把它单独拿出来讲,是因为它和普通认证技术的应用场合不同。
所谓单点登录就是让用户只需要登录一次就可以访问多个应用系统的技术。比如一个公司内部可能有邮箱系统、网上办公系统、财务系统等等,如果这些系统都是独立的,公司的员工就需要在每一个系统都申请一个账户,使用每个系统都需要单独登录。这样显然是低效而麻烦的,更好的解决方案应该是用户在内网中只需要登录一次,所有的子应用系统都能认证其身份,从而免去重复登录的麻烦。互联网的第三方账号登录则是另一个应用场景,中小型网站或者App获取用户信任的成本比较高,如果它允许用户使用已有的知名App或者网站的登录服务则会更容易获得用户的信任。在这种场景中,这些“不受信”App或者网站会先让用户跳转到他/她所信任的App或者网站去登录(可能是自动登录,也可能需要输入密码),然后再返回该“不受信”App或者网站,并且自动获得已登录用户的身份使用该App或者网站。
单点登录技术在大型企业IT系统内部应用非常广泛,它在互联网网站或者移动App中也被广泛采用,比如支付宝/微信/QQ账号授权登录,Google账号授权登录等。业界单点登录技术有不少标准,下面将介绍几个目前最有影响力的。
SAML是OASIS安全服务技术委员会的一个产品,始于2001年,它是一种使用XML在应用系统间之间交换身份验证和授权数据的单点登录技术。SAML里最核心的概念是”断言“(assertion)。断言是什么? 就是做出判断的语言。比如Tom有读取OSS数据的权限,这就是一个断言。在单点登录场景里断言的作用就在于证明你是谁和你有什么权限的。断言有了,但服务端为什么会信任你给的断言,如何才能防止别人假冒你提供伪造的断言来欺骗服务端呢?这个时候就用到了防假冒、篡改和重放攻击的利器-基于非对称加密的数字签名。通过给断言加上签名,再结合数字证书系统就确保了SAML断言的安全。
SAML现在版本是2.0,它的基本协议流程如下图所示(注:SAML协议包含各种场景下的不同流程,这里只是择取了其中一种具代表性的流程作为示例):
流程说明:
更多协议细节请参考 http://saml.xml.org/saml-specifications。
多年来SAML一直是企业级软件系统实现单点登录的主流标准。
到了云计算和移动互联网的时代,SAML标准已经跟不上时代的步伐,比如对移动端和物联网设备的支持非常不友好。这个时候就出现了一些新的单点登录技术,比如OAuth 1.0/OAuth 2.0,OpenID/OpenID Connect。严格意义上说OAuth2是个授权(Authorization)相关的协议,它的出现主要是为了解决Open API授权第三方访问的问题,但是由于其灵活性和巨大影响力,它也被广泛应用于单点登录的场合。
OAuth 有1.0和2.0版,其中2.0更安全也是目前使用最多的,这里只介绍2.0。OAuth 2.0协议的单点登录流程如下图(注:不同类型客户端的登录流程不尽相同,这里只是选取了其中适合Web应用的Authorization Code Flow来作为示例):
流程说明:
技术细节请参考https://oauth.net/2/。
目前OAuth2被各大互联网公司广泛使用,好多公司把它做成单点登录和Open API授权的一体方案,比如支付宝、微信等。
毕竟OAuth2主要是用来解决分布式系统之间的授权问题的,不是解决认证问题的,客户端拿到的令牌只是对API访问的授权,并不能代表用户的身份,这才有了OpenID(第三代的OpenID 改名叫OpenID Connect)。OpenID Connect 是基于 OAuth 2.0 (RFC 6749)实现的单点登录协议,它的基本原理是在OAuth2基础上提供了ID Token来解决第三方客户端标识用户身份认证的问题。ID-Token使用JWT格式来包装,这得益于JWT(JSON Web Token)的自包含性,紧凑性以及防篡改机制,使得ID-Token可以安全的传递给第三方客户端程序并且容易被验证。
OpenID Connect协议流程和OAuth2基本一样,只是最后一步认证服务器多颁发一个ID-Token,这样客户端就可以从该ID-Token中解出用户的身份信息了,另外标准也定义了获取用户信息的接口。技术细节请参考https://openid.net/specs/openid-connect-core-1_0-final.html
OpenID Connect是目前互联网中应用最广泛的单点登录协议,很多大型互联网公司比如阿里云、AWS,Google,Saleforce都支持该协议。
下面将三大单点登录技术进行一个简单的比较:
|
SAML |
OAuth2 |
OpenID Connect |
支持认证 |
是 |
“伪”认证 |
是 |
支持授权 |
是 |
是 |
是 |
用户身份的凭证 |
断言(Assertion),XML |
无 |
ID-Token, JWT |
凭证/Token传递方式 |
HTTP重定向、SAML SOAP绑定、HTTP POST绑定等 |
HTTP重定向、直接HTTP发送 |
HTTP重定向/直接HTTP发送 |
适用场景 |
企业级单点登录 |
OpenAPI授权+单点登录 |
网站/App第三方账号登录、企业级单点登录 |
授权分为用户授权和第三方应用授权。用户授权是指单个应用内用户的权限控制过程,而第三方应用授权则是指用户授权第三方应用访问他们在服务提供方的资源的过程。
实现用户授权的方法很多,但可以归类为以下四类:Discretionary Access Control (DAC)、Mandatory Access Control (MAC) 、Role-Based Access Control (RBAC)和Attribute-Based Access Control (ABAC)。
Discretionary Access Control (DAC)一个典型的例子就是UNIX/Linux文件权限机制,一个文件的权限由三个位组成:用户的权限、用户所在组的权限和其他人的权限。
Mandatory Access Control (MAC)的典型例子就是军队或者政府里面,每个文件/资源被设置了一个“保密”级别,而每个用户也被赋予了一个级别。当这个用户要访问这个文件的时候,系统只需要比较这两个级别,只有用户的级别大于等于文件的级别的时候才被允许访问。
Role Based Access Control (RBAC)是目前应用系统中使用最广泛的授权方法。这种方法会给每个用户赋予多个角色,比如匿名用户、普通用户、管理员、超级管理员等,不同的角色可以访问的资源范围不同。而用户登录后就只被允许访问自己角色范围内的资源,任何超出范围的访问都应该被拒绝。RAM和OAM都是一种基于RBAC的授权机制。
Attribute-Based Access Control (ABAC)比RBAC更强大,决定权限的不仅包括用户的角色,还可以包括用户的其他属性(如类型、部门、终端类型等),还可以包括资源对象的属性,甚至还可以包括其他环境条件如当前时间等。举个ABAC的例子,某个薪资系统只允许当地HR部门的正式员工在上班时间访问,这里面就考虑了员工的属性(部门,类型,工作地时区),对象的属性(薪资系统)和环境条件(所在时区、当前时间)。ABAC系统里权限的规则一般采用XACML语言来描述。
第三方应用授权的主流标准就是OAuth(当前版本是OAuth2.0)。OAuth(开放授权)是一个开放标准,它允许用户授权第三方网站访问他们在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容。
OAuth2协议包括四种授权流程,其中最具代表性的Authorization Code Flow的基本流程前面做了介绍,下表列举了四种授权流程的不同应用场合:
授权流程 |
适用场景 |
Authorization Code Flow |
有后端的Web应用、移动端App(最好使用PKCE-enhanced Authorization Code Flow ) |
Implicit Flow |
没有后端的单页面应用(SPA) |
Client Credentials Flow |
纯后端服务、调度任务、命令行脚本等 |
Resource Owner Password Grant |
用户信任的应用系统(比如企业内部IT系统) |
会话管理的方法可以归纳为以下两种:
单体应用时代,使用最广泛的就是基于SESSION的会话管理机制,如下图所示:
其基本原理如下:
对于用Java的同学,Session的机制早就写进Servlet规范里了的,所以几乎所有的基于Java的Web容器都实现了Session机制,而且原理都大同小异。
单体应用时代,session是存在本地的,不便于共享。那么在分布式架构下,我们可以简单的通过Session 复制或者Session共享存储的方式来在多个服务间共享用户的登录状态。
Session 复制依赖于应用服务器,需要应用服务器有 Session 复制能力,不过现在大部分应用服务器如 Tomcat、JBoss、WebSphere 等都已经提供了这个能力。该方法的一大缺陷在于当节点数比较多时,大量的 Session 数据复制会占用较多网络资源,其伸缩能力有限,只适合于小规模的web应用。
而Session共享存储则选择将Session存放在外部进行集中管理,可以是数据库,也可以是分布式缓存,如 Memchached、Redis 等。这种方案的可伸缩性相对比较好,使用比较广泛。
由于Session是有状态的,所以它与微服务时代无状态的架构思想产生了巨大冲突,于是就有了基于Token的会话管理方案。基于Token的会话管理基本原理如下:
流程说明:
这里Token可以是任何形式,比如16进制字符串或者JWT。严格意义上来说SESSION ID也算一种Token,但由于其是Web容器原生的所以被单独列为一类了。JWT自身可以携带用户信息,所以使用JWT作为Token可以避免服务端存储用户的登录状态,达到真正意义上的“无状态化”和“去中心化”管理,所以在业界被广泛采用。
JWT 是一种自包含的JSON格式的Token,它采用数字签名来确保Token的完整性(支持各种签名算法:HMAC+SHA、RSA+SHA、EC+SHA等,细节参考JWS),也可以进一步加密来保证Token的私密性(这里将忽略加密部分,细节请参考JWE)。
一个JWT由三部分组成:HEADER,PAYLOAD和SIGNATURE。下图是个JWT的样例:
JWT的头部主要包含两个信息:Token的类型“JWT”和签名的算法(样例中为HMAC SHA256)。整个头部信息是个经Base64Url编码后的JSON
PAYLOAD这部分就是我们需要JWT携带的用户信息部分,JWT的术语里把这里每个Key-Value对都称作声明(Claim)。声明还分标准中注册的声明、公共的声明和私有的声明三种,但都没有那个声明是强制必须的。整个PAYLOAD也是个经Base64Url编码后的JSON
使用指定签名算法对前两部分加服务端的私钥(secret)一起进行签名就得到SIGNATURE部分。比如这个样例里的签名计算公式就是:
下面将两种会话管理方案进行简单的比较:
|
基于Session |
基于分布式Session |
基于Token(JWT) |
是否有状态 |
有 |
有 |
无 |
会话生成 |
Web容器原生支持 |
Web容器原生支持 |
自定义 |
会话存储 |
本地或内存 |
共享存储 |
不需要 |
处理会话超时 |
Web容器原生支持 |
Web容器原生支持 |
自定义 |
伸缩性(Scalability) |
差 |
较好 |
好 |
跨域访问支持 |
差 |
差 |
好 |
适用场景 |
传统单体应用 |
分布式系统用户认证 | 分布式系统用户认证、 API调用认证 |
微服务架构下,一个应用是由多个相互独立的微服务组成,它们可能部署在不同的机器、子网甚至跨数据中心,服务之间的调用也都不再是本地调用,而是远程HTTP调用。下面是一个微服务架构的示意图:
这种环境下我们该如何对每个微服务的访问进行权限控制?是否可以在每个微服务中都植入认证和授权模块呢?理论上是可以这么做,但这样做会带来下面这些问题:
那么,微服务架构下认证和授权实现的正确姿势是什么呢?个人认为这里并没有什么统一的方案,因为没有任何一个方案适合所有的业务场景。这里将抛砖引玉,归纳一下微服务的认证和授权架构设计的一些最佳实践:
微服务的基本理论之一就是职责分离,认证和授权本身就是和业务无关的独立的功能,所以非常适合服务化。这里推荐将认证和授权剥离成单独的服务,将它放置在API网关处对请求进行统一的拦截,做到对内部各个微服务透明。
认证和授权相关的元数据如果分散到各个微服务中将很难进行管理,无法想像添加一个用户及相关权限需要到成百上千个服务的数据库里面去添加相应元数据。服务化后元数据很容易实现统一管理,这一条没什么可说的。
现代微服务系统的安全访问一般来说需要考虑三种使用场景:
三种场景的认证手段绝大部分时候是不一样的。比如用户访问需要使用Http Form认证,需要支持使用密码和刷脸登录,但外部系统调用则需要支持OAuth2协议。内部服务调用认证方式的选择则相对灵活,但也不能基于指纹刷脸之类需要交互的方式。但各种不同来源的请求进入到微服务系统后最好换成统一的身份凭证,再在系统内各服务间传递,否则让所有微服务同时处理各种不同的身份凭证将是一件很容易出错、很难维护的事情。
综合上面几个要点,微服务下的认证和授权架构应该看起来像这样(注意为了概括方便,图中系统间交互并没有指定具体协议或者技术):
说明:
本文先介绍了认证和授权的基本概念;接着分别列举了认证、授权和会话管理领域的各种常用技术;最后抛砖引玉地提出了微服务的认证和授权架构设计的一些最佳实践。个人水平有限,如有纰漏还请指正。另外最后一部分纯属个人观点,供大家讨论而已。
Integrated Windows Authentication
HTTP Basic : rfc7617
HTTP Digest : rfc7616
TLS 1.2 Protocol : rfc5246
HMAC : rfc2104
SAML Spec
OAuth2 Protocols
OpenID Connect Specs
JWT Protocol