【答疑解惑】| 作者 / Edison Zhou
这是恰童鞋骚年的第267篇原创内容
之前有同事问为何要用基于JWT令牌的认证架构,然后近期又有童鞋在后台留言问微服务安全认证架构的实践,因此我决定花两篇推文来解答一下。为了答好这个话题,我们先来看看微服务的安全认证架构是如何演进而来的,从而更好地理解。
1单块阶段(上)
首先,我们有必要再次了解下认证和授权这两个基本概念:
认证,Authentication,识别你是谁。即在网站上用来识别某个用户是否是注册过的合法用户。
授权,Authorization,识别你能做什么。即在网站上用来识别某个用户是否有某方面的权限。
然后,这里还是引用我在波波老师的《Spring Boot与K8s云原生应用开发》课程中学到的一个案例,来学习网站安全架构的演进。
假设我们把时间倒退回2006年,我们有一个叫做MyShop的网站,它的安全架构大概是下面这个样子,我们暂且称之为v1版本:
MyShop v1版本的认证操作
可以看到,这个v1版本的传统安全认证架构,使用到了我们十分熟悉的Session + Cookie的模式来实现用户的认证。即当一个注册用户通过登录操作请求后,Web服务器通过向数据库进行校验用户名和密码,通过后就会向Session中添加一条记录,然后返回给浏览器。在返回给浏览器的报文中,会将sessionId放在Cookie里头。
MyShop v1版本的访问操作
这样一来,用户在登录之后再访问网站的时候,就会将带有sessionId的Cookie传给Web服务器,而Web服务器就可以通过Cookie中的sessionId去Session记录中检查,如果没有过时就认为其是认证的活跃用户。
在ASP.NET Core中,提供了一个管理Session的中间件,我们可以在StartUp中注册和使用这个中间件即可用来管理会话状态。
参考资料:ASP.NET Core中的会话和状态管理,传送门:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/app-state?view=aspnetcore-3.1
2单块阶段(下)
v1版本上线测试之后,测试人员发现存在一个问题:登录用户会间歇性地退出登录,而且会话还没有超时。经过分析后发现,原来的Session记录只会在登录过的那台Web服务器上存在,而MyShop是以集群方式来部署的,由前置的Nginx代理服务器进行负载均衡地请求转发。
针对这个问题,MyShop设计了下图所示的v1.1版本的认证架构,又称为黏性Session架构。也就是说,不过哪一次请求,Nginx都会将对应的sessionId发给对应的Web服务器进行处理,而不是均衡地轮流转发。换句话说,Nginx服务器将会维护sessionId与各个Web服务器的Session之间的关联,以保证在会话期间的Session绑定。
MyShop v1.1版本-黏性会话
就这样,一晃两三年又过了,MyShop v1.1的认证架构支持了它早期的快速发展。但是,随着业务和用户量的不断扩展,它也逐渐暴露出稳定性和扩展性方面的问题。这些问题,归根结底还是由黏性会话所造成的。
(1)稳定性:黏性会话会将用户会话绑定到某个服务器上,如果我们要对这个服务器进行一些升级或改造又或服务器延迟或宕机,那么此服务器上的一波认证用户信息就会瞬间消失,用户必须重新登录。
(2)扩展性:黏性会话使得Web服务器和Nginx负载均衡服务器上都保存了状态,整体上属于一个有状态架构。随着流量的增长,这些状态同时给Web服务器和负载均衡器都会带来较大的压力。和无状态的应用架构比起来,这种有状态的应用架构比较难以扩展。
一般来说,常见的解决黏性会话的解决方案有以下几种:
(1)会话同步复制:即各个Web服务器之间同步Session,但是会引入复杂性,整体的性能较低。
(2)无状态会话:即Session数据不存在服务器端,而是存在浏览器端,但是存在数据泄露风险,且浏览器端对于Cookie的大小有限制(4KB)。
(3)集中状态会话:即将Session集中存储在某个存储中,比如Memcached或Redis这种高性能缓存中。
因此,MyShop选择了集中状态会话的方式演进出了v1.5安全认证架构,如下图所示:
MyShop v1.5版本-集中状态会话
在v1.5版本中,Web服务器和Nginx服务器不再存储会话状态,转而交由Redis进行统一存储,从而提高了稳定性和扩展性。对于Redis来说,也可以采用高可用集群方案,业界也有很多可扩展的实践案例。
画外音:虽然是单块时代发展出来的技术,但是无状态会话和集中状态会话却是微服务安全认证架构的基础。
3微服务架构阶段(上)
时光飞逝,时间来到了2015年,这期间MyShop的业务量也迅速的飞涨,期间互联网的技术也发生了大变化。微服务架构、无线应用、SPA应用雨后春笋般的出现,MyShop的技术团队也准备陆续应用实践,进一步丰富和扩展业务渠道,赋能业务端。但是,微服务架构的安全认证授权也存在着一些挑战:
微服务认证授权挑战
(1)后台应用和服务众多,如何对每一个服务进行认证和鉴权?传统的用户名&密码以及Session/Cookie的方式还能够适用吗?
(2)前端的用户入口众多,如果每个入口都搞一套登录认证,显然成本高且难以扩展。有没有一种SSO单点登录的方案?
经过MyShop技术团队的分析,传统的用户名&密码+Session/Cookie的方式无法直接套用在微服务架构上,但是可以借鉴之前的思路,他们提出了面向微服务架构的v2.0安全认证体系,如下图所示:
MyShop v2.0版本-基于Token的认证
v2.0安全认证体系最大的变化就在于,将登陆认证抽取为一个独立的API微服务AuthService,拥有一个独立的UserDB。这个服务统一承担登陆认证、用户校验、令牌颁发等职责。此外,v2.0版本还引入了Token作为服务调用认证鉴权的主要凭证。这里的话,v2.0采用的是一个透明令牌(也称为引用令牌),即它是一个无意义的随机字符串。这个令牌跟Auth Service上的一次登陆会话相关联,后续也可以通过API去校验这个令牌的合法性。
画外音:其实这个Token令牌,就相当于一个SessionId。每个微服务拿到令牌,都可以去AuthService进行认证鉴权。
总结下来,v2.0的安全认证体系的步骤如下:
Step1.用户通过某种客户端(Web/SPA/H5/App等)进行登陆,AuthService通过比对用户数据库进行校验;
Step2.AuthService校验通过后会建立一个用户会话Session(此Session和之前版本的类似,存在一个过期时间,可以存储在AuthService所在的服务器上也可以存在Redis中),然后颁发一个Token给客户端;
Step3.客户端向后台微服务发请求,会带上刚刚得到的Token;
Step4.微服务接收到用户请求时,首先会向AuthService发出一个请求对这个Token进行合法性校验;
Step5.AuthService校验Token通过后会返回该用户的详情信息(如果微服务需要的话,可以拿到用户的一些比如角色之类的信息);
Step6.微服务进行自己的逻辑处理,最终返回数据给客户端;
画外音:v2.0版本可以看做是v1.5的一个升级改造,专门针对微服务架构的场景进行了扩展,可以应对微服务架构存在的挑战。它把登录认证、令牌颁发等工作封装在了AuthService中,其他微服务统一共用AuthService,经过扩展还可以实现SSO单点登录。
4微服务架构阶段(下)
v2.0认证架构虽然可以解决问题,但是又引发了另外的问题:
首先,每个微服务都需要实现部分认证鉴权的逻辑,使得微服务开发方无法聚焦于业务逻辑的开发。
其次,认证鉴权逻辑分散在每个微服务当中,一方面会带来不规范容易出错的问题,另一方面也会有潜在的安全风险(比如某些开发人员可能会忘记校验令牌)。
为了解决上面提到的问题,同时考虑到微服务拆分后引入微服务API网关,MyShop技术团队设计了下图所示的v2.5认证架构:Token+Gateway结合方式
MyShop v2.5版本-基于Token+Gateway的认证
从上图可以看出,该架构将每个微服务都要进行的部分认证鉴权的逻辑从微服务转移到了网关中。即网关处负责拿到令牌向AuthService进行鉴权,通过后再将请求转发到后端的微服务,微服务不再包含任何认证鉴权的逻辑。
总体上,通过引入网关进行令牌的鉴权之后,大大减少了后端微服务开发方的职责,使得他们更专注于微服务的业务逻辑的开发。此外,引入网关之后,网关可以统一处理登录客户端的校验,也便于实现SSO单点登录,也为MyShop后续的微服务化和业务成长提供了基础。
画外音:v2.5版本应该是目前大多数团队所采用的一种认证架构了。对,我司也是,不过Token类型使用的是JWT。
5小结
本文通过一个MyShop的案例的演化介绍了微服务的安全认证架构是如何演进而来的,但是v2.5版本(Token+Gateway方式)总体上还是比较重,每个请求都还是需要到AuthService上去做认证鉴权的操作,这对于AuthService来说算是压力比较大。针对这个问题,业界广泛采用JWT这种轻量级的解决方案来重构安全认证架构。
那么问题来了,JWT是什么?原理?实现方式?下一期骚年快答,为你解答这几个问题。
往期骚年快答
技术中台与业务中台有啥联系?
微服务架构中的BFF到底是个啥?
为何微服务项目都爱用单体仓库?
专注于开发技术与个人成长分享,
做对你有用的公众号!
????点个赞/在看如何?