应用拆分微服务后,一个不可避免的问题就是权限问题。拆分后的各个微服务如何处理权限,怎么处理才能保证满足业务的需求,怎么处理才能保持架构的简单及可维护?今天的文章,让我们来深入微服务架构下的权限处理问题,看看这个没有最佳实践的领域,如何能够针对业务需求来设计的较为优雅。
1.先来理解几个名词
关于权限,可能有很多相关名词,而且这些名词也很可能会让大家混淆或是疑惑(为了避免让大家产生疑惑,我就不列举这些名词了)。
但本文中会用到几个核心的有关权限的词,理解这几个词对于理解本文必不可少,所以关于权限处理至少有下述三个名词需要大家先来理解。(1)认证。(2)鉴权。(3)授权。先来说说认证。我们都会用到各种各样的系统,并且在进入系统之前都需要你输入用户名/密码,点击登录后,后台做的事就是认证,及验证你的用户名/密码是否正确,能否登录系统。这就是认证做的事。再来说说鉴权。同样的系统,不同的人登录成功后,拥有的权限是不一样的,在你进行操作时,后台会验证你是否能够进行相应的操作,这就是鉴权,及校验用户是否有相应资源的操作权限。最后来说说授权。不同的用户拥有不同的权限是通过系统授权来实现的,授权就是授予相关用户或角色资源操作的权限。当然授权可能还包含对第三方系统的授权,本文暂不讨论此类场景。其实对于大多数应用系统来说,权限处理最关键的就是要解决上述三个词所描述的问题,即系统权限处理要做的事就是认证、鉴权和授权。
2.认证、鉴权和授权,在单体应用中如何实现?
在上文中,我们说明了权限问题的核心就是解决认证、鉴权和授权问题。想要知道微服务架构中如何处理权限问题,不如让我们先看看在单体应用中是如何处理上述问题的?让我们来分别说明。(1)认证单体应用中,对于用户登录,会先校验用户的登录信息(用户名/密码),这其中可能会涉及到密码的加密处理,一般的判断是加密后相应的密码是否与数据库中存储的密码相等,如果相等的话,则登录成功。用户登录成功的话,一般会返回用户登录成功相关凭证。如果是JWT的话,则会返回Token,如果采用的是会话的话,则会通过Set-Cookie返回SessionId给客户端。
(2)鉴权单体应用一般会通过拦截器(Spring Security、Apache Shrio本质上都是拦截器),拦截用户请求,这里同样会根据鉴权方案是JWT还是HTTP会话分别处理,如果是JWT的话,则会通过解密来获得用户信息,如果是采用的会话的方式,则会根据会话ID,从存储中(这里一般是Redis)来获得用户信息,不论是哪种方案,最终都是根据用户信息来对相应的请求进行权限校验。这里需要注意的是,鉴权一般有两种方式,但都是基于角色的。一种是基于角色的隐式鉴权,即根据角色直接判断是否拥有相应资源的操作权限,比如你的角色是管理员,你就可以删除用户,你的角色是普通用户,你只能查看用户信息。这种一般在简单的系统中比较适用,常用的方式是通过注解,表明哪个接口可以给哪个角色访问。但这种方式在复杂的系统中就会变得难以维护。另一种是基于角色的精准鉴权,此种鉴权方案,一般会给角色分配明确的权限,相应的鉴权方式为根据用户角色查出具体的权限集合,然后进行进一步判断。此种方式在复杂系统中更加有效方便。
(3)授权在单体应用中,授权即修改用户相关角色信息,或者修改角色相关权限信息。一般在用户重新登录后,最新的权限信息生效。
3.在微服务架构下如何实现上述功能?
其实在微服务中,权限处理的认证、授权功能实现,跟单体应用没什么区别。
从微服务的基于业务拆分的原则上,对于用户相关的业务,我们都会单独创建一个用户服务,用户服务作为一个单独的微服务,在实现认证、授权功能时,和单体应用保持一致即可,即相应的用户登录信息校验、用户角色修改、角色的权限修改,在用户服务中独立实现即可。所以在微服务架构下权限处理唯一与单体应用不同的便是鉴权方式。鉴权方式为什么变得不同了呢?因为鉴权不再集中在单体应用中了,鉴权被分散在了各个微服务中。所以现在问题的关键就变成了从哪里获取用户相关信息,然后在哪里进行鉴权。下面让我们来看几种微服务架构下的鉴权方案:第一种方案为,从用户服务获取用户信息,然后各个微服务分别鉴权。如下图所示:
上图中有三个微服务,分别是仓储服务、订单服务、以及用户服务,对于每个微服务的访问都需要进行鉴权,相应的鉴权方式为,所有对微服务的访问在经过网关后都统一先访问用户服务获取用户相关信息,包括角色信息、角色权限信息,然后分别在各个微服务中进行鉴权,判断用户是否有权限进行相应的操作。
上述方式的优点是实现简单,对比单体应用的实现来说,只是换了个地方获取用户信息,权限相关的判断还是保留在各个微服务中。对应的缺点也很明显,用户服务需要保证高性能、高可靠以及可扩展,并且随着服务的增多,每个服务都需要与用户服务交互,服务间调用可能会显得略加繁琐,不够简单优雅,同时每个微服务中的权限判断逻辑也都重复冗余。综上,个人并不是很推荐这种方式。其实对于第一种方案,我们略作修改,就可以得到一个略微优雅的方案,如下图,看第二种方案:
第二种方案为从用户服务获取用户信息,然后在网关进行鉴权,这样整个架构看起来是不是就清爽了许多?(美好的东西总是看起来简单的)
大家可能看到,这里将请求往具体的微服务透传的时候,带上了用户信息,这个用户信息可以放在HTTP Header中。为什么透传的时候要带上用户信息呢?因为后端具体的微服务业务可能需要获取当前用户的信息,但这里并不是用来鉴权,只是业务逻辑中用到了而已。第二种方式的优点是架构简单,对于鉴权来讲服务间调用交互更加简洁,并且后端微服务不需要冗余鉴权业务逻辑。缺点同样是用户服务需要保证高性能、高可靠以及可扩展,并且鉴权逻辑需要在网关实现。微服务的理想状态是高内聚低耦合,每个服务专注于自己的事,各个服务间功能独立,独立演进,所以针对微服务鉴权还有没有更好的方案?请看第三种方案:
鉴权要做的事是判断用户有没有相应资源的操作权限,所以从微服务的基于业务拆分的原则基础上,鉴权放在用户服务是不是更合适?所以上述方案将鉴权放在了用户服务来实现,我们不再需要调用用户服务来获取用户信息然后进行鉴权了,直接将用户的请求转发到用户服务,在用户服务中统一处理鉴权。此种方案下网关要做的事也将更加专注,只做请求的转发即可,后端各个微服务也只需专注于自己的业务即可。
此种方案,在我看来是比较推荐的方案,并且应该能满足微服务架构下大多数系统的权限处理需求。当然缺点是对用户服务的高性能、高可靠以及可扩展提出了更高的要求。除了上述三种鉴权方式,微服务架构下可能还有其他鉴权方式,比如共享用户信息缓存的方式,这种方式下,各个微服务统一从缓存获取用户信息,然后分别在各自进行鉴权。基于此种方式也很容易想到一个优化的变种,即从网关获取缓存的用户信息,然后在网关进行鉴权。
诸如此类的鉴权方式,我个人是不太推荐的,当然采用此类鉴权方式的团队可能也有他们的考虑,更多的可能是出于性能的考虑。至于我为什么不推荐诸如此类的方式呢,我觉得我们在做系统架构的时候需要有自己的原则,其实对于很多问题我们可以有许多种解决方法,哪种方案比哪种好可能并不好说,甚至团队在方案的选择上还会有所争执,但如果在架构设计上团队有自己的指导原则的话,可能能够帮助我们在方案的选择上达成共识。个人在架构设计上的原则包括但不限于可维护原则、可扩展原则、简单性原则等,所以与其说此类方案不好,倒不如说这并不符合我的设计原则。
写在最后应用拆分微服务后,一个不可避免的问题是权限问题。
权限问题要解决的核心问题是认证、鉴权和授权。在说明微服务架构下如何处理权限问题之前,我们先看了单体应用下是如何处理权限问题的认证、鉴权和授权的。对于微服务来讲,权限问题的处理与单体应用中的处理最大的不同是鉴权的方式,至于认证、授权的实现与单体应用并无太大不同,在微服务中的用户服务下实现即可。微服务下鉴权处理为什么变得不同了呢?因为鉴权不再集中在单体应用中了,鉴权被分散在了各个微服务中。
所以问题的关键是微服务下该从哪里获取用户相关信息,然后在哪里进行鉴权。我们详细介绍了微服务架构下三种鉴权的实现,第一种方式是从用户服务获取用户信息,然后在各个微服务中鉴权。第二种方式是从用户服务获取用户信息,然后在网关统一鉴权。第三种方式不再需要获取用户信息,直接通过网关转发请求,在用户服务进行统一鉴权,各个服务的职责划分更加明确,也是个人推荐的实现方式。除此之外,还介绍了一些其他的鉴权实现方案,问题的解决方案并不唯一,有时候也很难说出哪种更好,我们要有自己的设计原则。
聊技术,不止于技术。