雷云祥,蒋其海,田 杰,段明秀
(吉首大学计算机科学与工程学院,湖南 吉首 416000)
在常见的分布式系统架构中,一般在每台服务器上都独立部署一套完整的权限系统,这样的架构会导致系统的维护成本偏高且效率低下.虽然基于Session的单点登录系统[1]在整个集群中只有唯一的一台权限控制服务器,但是通过身份验证的用户访问集群中的数据资源时,需要先在权限控制服务器进行鉴权,然后才能访问相应的数据资源.这样操作虽然实现了权限控制的相对独立,但向集群的所有数据请求都要先访问权限控制服务器,导致系统响应的实时性降低,而且当权限控制服务器死机时,整个集群将处于瘫痪状态.针对上述认证方式暴露的性能和效率低等问题,笔者拟在JSON网络令牌(JSON Web Token,JWT)技术的基础上通过自定义鉴权技术,将身份认证独立为认证服务器以实现用户身份的统一认证,并将用户鉴权分散到各个业务服务器以实现对数据请求的鉴权,即采用“集中认证,分散鉴权”模式,以期满足大部分系统在分布式环境下对权限管理的需要.
JWT是一种用于Web环境下各方之间传递信息的表述性声明规范,它定义了一种简洁的、自包含的方法,用于通信双方以JSON对象的形式安全地传递信息.JWT由Header头部、Payload载荷和Signature签名3个部分组成.载荷主要用来在不同服务器之间交互信息,它由默认预定义信息和自定义信息组成:默认预定义信息包括签发时间、过期时间等;自定义信息是业务处理中需要用到的信息,如用户账号信息和权限信息等.签名由Header、私钥和Payload组合加密生成,它实际上是一个加密字符串,其中私钥由创建Token的服务器提供.签名的作用是,当服务器接收Token后,通过拿取Header和Payload信息,再组合服务器本身的私钥生成一个新的签名,将新的签名和Token中的签名进行比对,查看是否一致,从而验证Token是否被篡改.
基于JWT技术的认证流程是,通过身份认证的用户将获取的Token存储在客户端,当客户端每次请求服务器资源时,在请求头中携带Token,供服务器实现鉴权[2].
Spring Security是一个安全性框架,能够与SpringBoot和Mybatis等各大框架集成使用.Spring Security提供了默认的十几种过滤器,每一种过滤器都能继承自定义实现,如“UsernamePasswordAuthenticationFilter”主要用来实现登录认证功能.权限系统关于安全方面的2个核心功能是认证和鉴权:认证是对用户进行身份验证,判断用户是否为系统合法用户;鉴权是当用户发起对资源的访问时,服务器判断用户是否具备访问请求该资源的权限[3].
对于单体架构系统,认证成功后,认证信息可以直接存储到本地服务器.对于分布式集群系统,由于负载均衡服务器将请求分发到集群中的任何一台服务器上,因此要保证每台服务器鉴权时能够拿到正确的认证信息,就不要采用Session认证技术,否则会导致服务器性能降低.为了解决这个问题,可以将用户身份信息和权限信息置于Token中,由客户端存储该Token,而服务器端无需存储用户认证信息或会话信息,以减轻服务端的压力.
分布式集群环境下通过一台鉴权服务器进行集中鉴权,势必会导致鉴权服务器访问压力大,且当访问不需要鉴权的服务器时,依然要经过鉴权服务器的路由转发,增加服务响应时间.
动态权限管理系统[4]采用JWT技术,将认证后的账号信息存放到Token中,通过在请求中携带Token访问服务器实现分散鉴权.但Token中并未存放权限信息,每次鉴权需要通过查询数据库获取用户的权限信息,造成资源浪费和性能下降.为了解决这个问题,可以采用“分散鉴权”模式:一方面,将鉴权过程分散到各个服务器,当认证成功后,将权限信息存储在Token中,鉴权时直接从Token中获得权限信息,从而避免频繁地访问数据库;另一方面,对于不需要鉴权的服务器,就直接去除鉴权模块.
JWT签发时,在它的载荷部分存放了Token的签发时间和过期时间,为了防止Token被盗造成严重的后果,Token的有效时间一般设置得很短.这样会造成Token频繁失效,用户需要不断重复登录,从而大大降低了用户体验.为了解决这个问题,可以通过引入基于Refresh Token的静默刷新机制进行Token刷新,在Token失效之前获取新的Token,解决了用户频繁掉线问题.
分布式权限管理系统架构如图1所示.系统通过Nginx进行反向代理,将用户的请求路由到不同的服务器,认证模块单独拿出,并将用户认证获取Token放到认证服务器,每一个Web应用服务器都有自定义的权限鉴权模块.
图1 分布式权限管理系统架构
用户进行身份认证的流程如图2所示.
图2 认证流程
认证具体步骤如下:
(ⅰ)用户发送登录请求到认证服务器.
(ⅱ)认证服务器拦截请求,进行用户身份验证,验证成功则转至(ⅲ),验证失败则将登录失败信息返回给前端.
(ⅲ)查询用户所有资源信息(角色、接口权限等),按照权限信息生成算法将拥有的所有应用程序编程(Application Programming Interface,API)接口权限生成为权限集合,同时用通用唯一识别码(Universally Unique Identifier,UUID)作为Key,将用户的权限集合储存在Redis,用于之后的权限刷新.
(ⅳ)通过权限集合和用户账号等信息生成Access Token,通过用户账号和权限集合对应的UUID等信息生成Refresh Token,2个Token的签发时间和过期时间一致.
(ⅴ)将认证信息返回给客户端,客户端将2个Token存储在本地.
服务器鉴权功能主要由自定义的过滤器实现,过滤器对客户端的请求进行拦截,流程如图3所示.
图3 鉴权流程
随着系统业务规模的扩大,用户权限信息也不断增加,如果不采用一定的方式进行处理,Token的长度就会不断增长,从而出现过多的网络带宽占用和超出服务器最大请求长度等问题.
对于Web应用来说,权限信息实质上是用户所能访问资源的统一资源定位符(Uniform Resource Locator,URL),由于URL满足层级结构,因此可以将它设计成树形结构.例如“/unit/addUnit”分为“unit”和“addUnit”,其中“unit”结点为“addUnit”的父结点.
URL之间往往存在包含关系,如“/user/student/query”包含“user/student”,在树形结构下,可以通过这样的包含关系去除重复的结点,减少权限信息长度.但是因为这样的关系,在遍历URL树时,难以判断遍历的结点是否为URL最终结点,所以必须给树的每个结点添加是否为最终结点的标志(图4).
图4 权限树
权限信息生成算法具体流程如下:
(ⅰ)按照“/”分隔,将URL分级处理成一棵多叉权限树,“/”为树的根节点.
(ⅱ)将URL权限树序列化成带固定格式的权限字符串集合,第2级添加大括号,第3级添加中括号,第4级添加小括号,同级的URL结点用符号“|”分开.每个URL后添加0或1,代表是否为最后一个结点,如{user=0}[register=1|verificationCode=1|applyRetrieveCode=1|retrievePassword=1|]{unit=0}[**=1].
(ⅲ)通过GZIP压缩算法进行权限字符串压缩.
认证成功后,在生成Access Token的基础上也生成Refresh Token,并返回给客户端.其中Refresh Token包含刷新Token时需要的用户账号信息和Redis中权限集合对应的Key.
基于Refresh Token的静默刷新机制如图5所示.
图5 刷新Token流程
刷新Token具体步骤如下:
(ⅰ)客户端携带Token向服务器请求资源.
(ⅲ)在超文本传输协议(Hyper Text Transfer Protocol,HTTP)响应头中添加Token状态刷新标志(refresh_status),同时服务器继续处理请求,返回数据给客户端.
(ⅳ)客户端检测到refresh_status刷新标志,携带Refresh Token访问认证服务器的Token刷新接口.
(ⅳ)认证服务器通过Refresh Token中的UUID去Redis数据库中查找对应用户的权限集合,最后生成新的Token返回给客户端.
为了测试新型分布式权限控制系统与传统权限系统的性能差异,在Centos7.9,Tomcat9.0,CPU 2.2 GHz,8.0 GB内存的环境下分别部署这2种权限系统,进行实验对比.
通过编写多线程程序模拟用户访问服务器(其中一个线程代表一个用户),并在每个线程中基于HTTP协议不断发送请求来模拟用户操作,每次请求计算从发送请求到获取响应的时间间隔,最终统计出平均响应时间.程序支持开启给定数量的线程,本次测试模拟从100递增到1 000的并发情况.表1示出了2个权限系统在不同用户数量情况下平均响应时间的变化情况.
表1 系统平均响应时间对比
由表1可知,分布式权限控制系统的平均响应时间始终比传统权限系统的短,说明新型权限控制系统在分布式环境下显著提升了系统性能.
对当前分布式环境下权限系统存在的问题进行了分析,并在分布式集群环境下,从认证信息存储、分布式鉴权和Token刷新机制等方面设计了可行性解决方案.系统采用“集中认证,分散鉴权”模式与传统认证模式相比,能更好地适应分布式集群环境,从而提升系统的整体性能和效率.