侯海平
安徽财贸职业学院 信息工程学院,安徽 合肥 230061
线上业务的处理能力越来越受到企业关注,大量线下业务都逐步迁移到线上进行处理,微服务架构也成为在线流量大、并发性高业务场景中的必选架构,大量业务系统由单体架构向微服务架构迁移[1]。无论是单体架构系统还是微服务架构系统都需要对业务资源实施授权访问机制,防止不具备权限的用户账号访问不匹配的内容资源。微服务架构下系统资源分散、系统开发技术多样化、系统模块相对独立给授权认证带来困难。
目前应用领域还存在大量的单体应用,例如一些传统的业务系统如OA、财务系统、图书借阅系统等,它们在用户数量相对有限和稳定的情况下依然能够满足业务运营的日常需求,因此依然有存在和使用的价值。所谓单体架构是指所有的业务系统功能都集成在一个开发项目中,这些业务代码最终将被打包到一个程序中,该程序运行在操作系统中以一个进程为载体运行。例如Java 项目会使用Maven包管理工具将项目打包成一个war 包或一个jar包,然后以一个进程的形式运行在Tomcat 服务器程序中或直接运行在Java 虚拟机中。如果是.net(微软平台下)项目则会以一个dll 文件作为项目入口,运行在IIS 服务器程序中。通常这些单体架构只会映射到一个IP 和端口上,对外界提供服务。
在单体架构中,验证用户账号、密码的组件与鉴别用户身份的组件,以及与提供受限资源的组件都是在同一个Web 程序中,因此只需考虑同一用户同一session 会话周期即可。一次鉴权过程主要包括2 个主要活动:第一,用户提交账号和密码登录,服务验证账号和密码;第二,用户成功登录后,访问受限资源,服务器鉴权后提供受限资源给用户。
认证活动的步骤:
步骤1:用户在浏览器上输入账号和密码提交至服务器进行验证;
步骤2:服务器收到用户请求的账号和密码,对其进行验证;
步骤3:服务器验证通过后,将建立属于该用户的session 会话,将向session 中写入授权凭证;
步骤4:服务器向用户返回登录成功的消息,并向用户所在浏览器的cookie 写入与服务器一致的sessionid。
鉴权活动的步骤:
步骤5:用户在登录后,向服务器发起访问受限资源的请求;
步骤6:服务器根据用户提交的cookie 中sessionid 辨认用户身份,通过之前存放的session 凭证,来验证用户权限的合法性;
步骤7:服务器鉴权通过后,将用户需要的受限资源返回给用户,流程结束。
可以看到整个流程中,服务器要使用session 会话对象,只要有新的用户进来,服务器就要创建新的session 对象,并且对通过验证的用户在session对象中存入身份凭证,这些数据都保存在服务器内存中。整个服务器对session 对象的管理建立过期机制,用户如果长时间不访问服务器,服务器将自动销毁该用户session 对象,如果用户一直保持访问服务器,服务器将不断延长session 对象的生命周期。
服务器要想识别出不同用户,依靠的是用户浏览器的cookie 对象,用户每次访问服务器时,浏览器都会自动将客户端cookie 主动提交至服务器。服务器通过cookie 中的sessionid 找出用户,根据用户找出对应的凭证,再根据凭证确定用户是否具备访问某资源的合法性。
随着业务流量持续上升,这些单体架构系统需要进行集群化部署。使用同一个域名指向多个不同服务器地址,一般可以采用Nginx 反向代理架构,实现多台服务器对外提供服务。例如一台服务器可以同时支撑200 个客户端进行并发访问,现在发现流量高峰时有600 个客户端并发访问,则需要3 台服务器对外提供服务。集群部署架构图如图1 所示。
图1 单体架构系统的集群部署
1.3.1 session 同步问题
当客户端对Nginx 服务器进行访问时,Nginx服务器会将请求转发至某一台服务器,可以采用轮询、同一客户端IP 转发至同一服务器、权重设置等方式实现负载均衡,从而将请求流量分摊到不同服务器,达到支持大并发量的目的。根据session 生成的基本原则,可以看出:当用户访问服务时,属于某一用户的session 只能保存在某一台服务器上;当用户下次访问时,就可能切换到其他单体系统的服务器,当前服务器则没有属于该用户的session 对象,因为属于这个用户的session 保存在上一个服务器里,把这种问题可以称之为“session 不同步”。虽然可以通过“同一客户端IP 转发至同一服务器”原则来保证原用户导向原服务器,但是一旦这个服务器挂起,则又会出现上述“session 不同步”的问题。
1.3.2 session 存储问题
当用户量上升时,session 对象依然存储在服务器上,对服务器的负担依然没有降低,如果单台服务器挂起时,session 也随之消失。
第一类终端表现为传统APP。从Android Native APP、iOS APP 到考虑各类平台快速部署和兼容问题提出的Web APP,再到兼顾Native APP和Web APP 优点的混合终端都以各自的方式接入到服务器系统。
第二类终端表现为依托第三方认证的APP。近几年,随着微信和支付宝的快速发展,一类依托微信建立微信公众号、小程序和支付宝小程序也广受企业用户欢迎,他们既要与当前系统服务器通信,还要调取微信和支付宝授权信息。
第三类终端表现为服务器API 消费者。如提供数据作为一种基础应用提供给其他第三方消费,此类供调取方消费的API 需求也越来越多。大数据时代到来各类数据中心积累的数据越来越多,应充分利用这些数据并将数据安全保障的对社会公开。
以Spring Cloud 技术体系为例,通常这一架构构成主要有:注册中心集群、配置中心集群、网关集群、业务系统1 集群、业务系统2 集群、业务系统3集群……。具体如下图2 所示。
图2 微服务通用架构
注册中心:目前主流的注册中心组件有zookeeper、eureka、nacos 等,有了注册中心之后,所有的服务都需要到注册中心进行登记,所有的服务调用都需要通过注册中心发现,进行统一管理,即使服务部署在不同的机器上,注册中心也可以进行统一管理,且可以很好的实现负载均衡。
配置中心:配置是指服务的统一约定、环境参数等信息,建立配置中心的目的是让这些配置可以统一管理、动态刷新、实现不同环境下配置切换,每一个服务都可以将自己的配置信息放置到配置中心。配置中心组件有:config-server、nacos 等。
网关:所有的请求都必须通过网关才可以进入,网关相当于客户端访问服务的路由器[2],对于客户端来说屏蔽了服务提供者的内部地址,还可以通过网关的过滤器实现请求的拦截、实现响应的前置处理和后置处理等[3]。主流的网关组件有:zuul、Spring Cloud Gateway 等。
业务系统:一般业务系统会将用户中心独立出来变成一个基础子系统独立部署,然后再将其他业务系统按照微服务划分服务的原则,独立出若干个子系统,每个业务子系统都会注册到注册中心,都会通过配置中心拉取配置信息,同时要想访问业务子系统都会从网关接入。
2.3.1 跨域
整个微服务架构中包含了很多业务服务器,服务器之间不可能采用相同域名或IP,这就要求对外提供服务时,需要支持跨域,而跨域之后cookie 是不能进行共享的,也就是服务A 并不能拿到服务B的cookie 信息,这也是Web 安全规则中要求的。
2.3.2 服务无状态
多个服务之间相互通信,由于不能共享cookie,就无法记住某一次请求在不同服务之间是否为同一个用户,这些服务无法保留请求的状态信息,或者即使保留了服务的状态也是没有实际意义的,因为这些信息不能代表是同一个用户。
2.3.3 终端不支持cookie
Native App 以及第三方API 请求的终端并不能支持cookie 机制,也就是说这些架构设计开发的应用不能记住服务器回写的终端数据,因此不能记住用户身份,即使通过OkHttp 等框架可以实现记住cookie 信息,也会对客户端架构设计增加很多开发成本,如果开发者没有遵循开发原则,也会导致无法识别用户身份信息。
Shiro 是权限管理中非常出色的框架之一,它提供了认证、授权、加密、会话管理、Web 集成、缓存等功 能 。 Shiro 主 要 包 括 Subject、SecurityManager、Realm 等主要组件,开发通过实现自定义的Realm就可以实现授权和鉴权。
shiro 的鉴权是通过为需要限制访问的资源方法 添 加 @RequiresUser、@RequiresRoles、@Requires Permission 等注解来实现的[4]。这些注解本质上是通过AOP 来实现的,通过给这些注解传递参数,使用@RequiresUser 表示只允许指定的用户访问,使用@RequiresRoles 表示只允许指定的角色访问,使用@RequiresPermission 只允许拥有某权限的用户访问。当请求需要访问这些受限资源方法时,AOP 就会来判断这些限制访问的资源是否允许访问。
JWT 是一种基于JSON 的开放标准,定义了一套在不同实体之间传递安全信息的数据格式标准,最终由客户端向服务端发送一个token(令牌)表示客户端用户的身份信息,整个信息传输又是建立在加密算法的基础上,这样既保证token 的安全,又能让服务器识别出用户身份。这个token 信息包含3个部分:head 部分(声明token 类型和加密算法)、payload 部分(数据主体:用户、过期时间等)、signature 部分(验证数据是否被篡改的签名)。它的优点在于不需要依赖cookie 和session,JWT 非常适合微服务架构和不支持cookie 的各类终端。
3.3.1 单体下使用Shiro 和JWT
首先,需要实现HostAuthenticationToken 接口定义JwtToken 类,用于按照JWT 数据标准标识用户身份信息。
其次,定义类JwtFilter 继承BasicHttpAuthenti cationFilter 类,对每一个携带jwtToken 的请求进行认证和授权,只有通过认证的才可以授权放行。
最后,定义JwtRealm 类继承AuthorizingRealm,实现认证和授权核心机制。具体认证和授权流程如图3 所示。
图3 单体架构下使用shiro 和jwt 实现认证和授权
步骤1-4:客户端用户输入账号和密码发起登录请求,服务器使用Shiro 框架对账号和密码进行验证,验证通过发放JwtToken。
步骤5:用户携带合法JwtToken 访问受限资源,经过JwtFilter 过滤器,JwtFilter 对请求进行拦截。
步骤 6:JwtFilter 对 JwtToken 进行验证,将JwtToken 传递给 Shiro 和 JWT 组件。
步骤 7:Shiro 和 JWT 使用 Realm 对 JwtToken进行验证,验证通过返回,并对用户进行授权。
步骤8:JwtFilter 放行对受限资源的请求,转发请求至受限资源。
步骤9:受限资源的权限注解被调用,将使用Shiro 的 isPermitted()方法验证权限。
步骤10:Shiro 验证权限通过。
步骤11:返回受限资源到用户端。
3.3.2 多服务架构改造
基于单体架构认证和授权的基本原理,改造成适合微服务架构的认证和授权机制。首先,创建类JwtGateWayFilter 实现 GatewayFilter 接口,注意与之前单体架构实现的接口不同,GatewayFilter 接口是来自于微服务架构中的网关组件,目的是对用户发起的请求中JwtToken 信息进行验证;其次,继续保留JwtToken 类的定义,用于认证和授权过程中token 的传递;最后,为每一个微服务创建JwtFilter 类继承BasicHttpAuthenticationFilter 类,创建 JwtRealm 类继承AuthorizingRealm 类,实现授权机制。
与单体架构不同之处:
(1)全局过滤器添加在微服务的网关上,用于判断用户是否获得合法认证做验证。
(2)每一个微服务需要进行授权操作。
(3)每一个微服务独立实现鉴权机制。
整个流程图如图4 所示。
图4 微服务架构下使用shiro 和jwt 实现认证和授权
步骤1-4:登录通过认证,获得token(JWT)。
步骤5:用户访问业务资源;
步骤6:网关使用JwtGatewayFilter 对token 进行验证,如果验证通过放行请求。
步骤7:业务集群使用Shiro 和JWT 对token 进行验证并授权,放行请求至受限资源,返回受限资源信息。
步骤8:返回受限资源消息至用户。
微服务架构下的业务系统各自相对独立,每一个业务系统都独立部署,各个服务之间是处在一种跨域的环境中,加上客户端技术架构多样化,这就要求对传统Web 认证和授权机制进行重构。既要支持业务集群的分布式环境,又要简化认证授权流程,同时还需要减少服务器开销,最终还需要保证Web 服务的安全性和可靠性。