最近在做的项目需要实现一套权限控制方案
项目使用Spring + Vue.js构建前后端分离的系统,实现了基于RESTful API的权限控制。
权限控制系统包含两部分:接口访问控制以及数据实体访问控制。
后端可以分为三层:Controller Layer
, Service Layer
, 以及Data Persistence Layer
。
需求分析及方案设计
需求描述
甲方要求实现一套细粒度且可个性化配置的权限控制系统。
- 对前端展示的页面,不同用户有不同的操作界面。
- 不同用户对数据实体的操作权限不同。
- 对没有权限的操作,用户可以提出申请,审批通过之后自动执行(本次)操作。
- 不同用户能访问的数据实体(数据库表中的行)不同。
- 如果可能,需要实现对具体数据实体中的个别属性(数据库表中的列)的访问控制。
- 因为此系统是前后端分离、通过RESTful API来操作数据的,所以权限控制只能在后端进行。而在具体实践中,涉及增删改功能的前端的按钮与后端的API大多是一一对应的,只需要在后端对Controller层进行控制就好了。
- 对于数据实体的过滤,试图在数据从持久层取出后通过AOP做切面实现。
- 申请/审核思路大概是先往数据库中写入冗余的新数据并添加标示位,标记为无效数据。当审批链完成后,重新标记为有效数据(并删除旧数据)。
方案设计
流程概述
对API进行控制的时序图如图所示。Spring Security提供了AbstractSecurityInterceptor基类来拦截所有的请求。并在此拦截器中调用AccessDecisionManager接口实现权限访问,根据结果返回401 Unauthorized
或者403 Forbidden
.
对实体数据进行控制的时序图如图所示。通过切面增强,在切点上根据RBAC模式移除用户无权限访问的数据。
基于角色的权限控制
为了实现细粒度的权限控制,决定采用Role_Based Access Control 1.0模式的权限控制方案。
权限相关的实体分别为User
、Role
以及Resource
。
- Resouce为具体的资源,Role则为Resource的集合。
- 关联表Role_Resouce表示了一个Role可以访问哪些资源,为一对多映射。
- 关联表User_Role表示了一个User拥有哪些角色,从而有权访问对应的资源。
- 权限控制时,只验证这个用户是否具有对应的角色。
通过需求可知,需要控制的资源实际为两类,API以及数据。故将资源表拆分为Operation_Resouce
以及Data_Resource
, 分别在不同的流程中使用进行验证。
接口访问控制
用户鉴权功能通过拦截器和切面实现。Spring Security提供了AbstractSecurityInterceptor基类来拦截所有的请求。若一个请求携带了有效的JWT令牌,则会继续由此拦截器处理。可在此拦截器中判断用户是否有权限访问该接口。
将需要控制的接口注册为资源,并通过Ant Pattern唯一地标识一个接口。之后便可依托RBAC模式来实现接口访问控制,不再赘述。
数据访问控制
将需要控制的数据注册为资源,并依托RBAC模式进行控制;只需将数据实体的Classname和Identifier录入即可唯一标识一个实体。
基本的思路是:
- 在数据持久层设置切面,拦截返回值(从数据库装载的数据实体)。
- 对每个实体,解析出他的Identifier与Classname,并与资源表中已注册的数据资源比对。
- 若为注册的资源,进一步从关联表中获取可访问此资源的角色集合。
- 从上下文SecurityContext中获取当前访问API用户的Authority,判断用户是否拥有此角色。若无,则将其从返回结果中移除。
需要解决的问题是,在切面获取的返回结果是Object
,如何暴露出我们所需要的Identifier与Classname?可以考虑让被控制的实体类都实现同一个接口,例如一个提供getId()
方法的AuthorityExposed接口,在切面拦截后可以直接转型为AuthorityExposed的实例并调用getId()获取标识符。