如何设计一个通用的权限管理系统?说的太详细了!

JAVA公众号

共 10231字,需浏览 21分钟

 ·

2021-04-06 22:02

「今日推荐」

【开源项目】高仿抖音项目 就问你服不?
沉入海底 2 年的微软数据中心浮出水面:故障率只有陆地上的 1/8
腾讯,干掉 Redis 项目,正式开源、太牛逼啦!


一个系统,如果没有安全控制,是十分危险的,一般安全控制包括身份认证和权限管理。用户访问时,首先需要查看此用户是否是合法用户,然后检查此用户可以对那些资源进行何种操作,最终做到安全访问。身份认证的方式有很多种,最简单的就是直接用户名密码,还有业内比较通用的方式CAS方式登陆等;授权的框架也很多,比如OAuth2,Shiro等。本文首先会讲解一下CAS的概念,以及基于角色的权限管理模型(RBAC)的概念,接着进行数据表的设计,最后讲解如何利用Shiro进行权限管理。

一、CAS身份认证

集中式认证服务(英语:Central Authentication Service,缩写CAS)是一种针对万维网的单点登录协议。它的目的是允许一个用户访问多个应用程序,而只需提供一次凭证。

1.1、名词概念

CAS的核心就是其Ticket,及其在Ticket之上的一系列处理操作。CAS的主要票据有TGT、ST、PGT、PGTIOU、PT,其中TGT、ST是CAS1.0(基础模式)协议中就有的票据,PGT、PGTIOU、PT是CAS2.0(代理模式)协议中有的票据。这些票据谁生成得了?肯定是有相关服务的,主要服务有:KDC,AS,TGS。两者媒介肯定也是有的:TGC。

1.1.1、CAS的Ticket

1)TGT(Ticket Granting Tieckt)

TGT是CAS(具体为 KDC 的 AS 发放)为用户签发的登录票据,拥有了TGT,用户就可以证明自己在CAS成功登录过。TGT封装了Cookie值以及此Cookie值对应的用户信息。用户在CAS认证成功后,CAS生成cookie,写入浏览器,同时生成一个TGT对象,放入自己的缓存,TGT对象的ID就是cookie的值。当HTTP再次请求到来时,如果传过来的有CAS生成的cookie,则CAS以此cookie值为key查询缓存中有无TGT ,如果有的话,则说明用户之前登录过,如果没有,则用户需要重新登录。

简而言之,即获取这样一张票据后,以后申请各种其他服务票据 (ST) 便不必再向 KDC 提交身份认证信息 ( 准确术语是 Credentials) 。

2)ST(Service ticket)

ST是CAS(由 KDC 的 TGS 发放)为用户签发的访问某一service的票据。

用户访问service时,service发现用户没有ST,则要求用户去CAS获取ST。用户向CAS发出获取ST的请求,如果用户的请求中包含cookie,则CAS会以此cookie值为key查询缓存中有无TGT,如果存在TGT,则用此TGT签发一个ST,返回给用户。用户凭借ST去访问service,service拿ST去CAS验证,验证通过后,允许用户访问资源。

任何一台 Workstation 都需要拥有一张有效的 Service Ticket 才能访问域内部的应用 (Applications) 。如果能正确接收 Service Ticket ,说明在 CASClient-CASServer 之间的信任关系已经被正确建立起来。

3)PGT(Proxy Granting Ticket)

Proxy Service的代理凭据。用户通过CAS成功登录某一Proxy Service后,CAS生成一个PGT对象,缓存在CAS本地,同时将PGT的值(一个UUID字符串)回传给Proxy Service,并保存在Proxy Service里。Proxy Service拿到PGT后,就可以为Target Service(back-end service)做代理,为其申请PT。

4)PT(Proxy Ticket)

PT是用户访问Target Service(back-end service)的票据。如果用户访问的是一个Web应用,则Web应用会要求浏览器提供ST,浏览器就会用cookie去CAS获取一个ST,然后就可以访问这个Web应用了。如果用户访问的不是一个Web应用,而是一个C/S结构的应用,因为C/S结构的应用得不到cookie,所以用户不能自己去CAS获取ST,而是通过访问proxy service的接口,凭借proxy service的PGT去获取一个PT,然后才能访问到此应用。

TGT、ST、PGT、PT之间关系的总结

1:ST是TGT签发的。用户在CAS上认证成功后,CAS生成TGT,用TGT签发一个ST,ST的ticketGrantingTicket属性值是TGT对象,然后把ST的值redirect到客户应用。

2:PGT是ST签发的。用户凭借ST去访问Proxy service,Proxy service去CAS验证ST(同时传递PgtUrl参数给CAS),如果ST验证成功,则CAS用ST签发一个PGT,PGT对象里的ticketGrantingTicket是签发ST的TGT对象。

3:PT是PGT签发的。Proxy service代理back-end service去CAS获取PT的时候,CAS根据传来的pgt参数,获取到PGT对象,然后调用其grantServiceTicket方法,生成一个PT对象。

1.1.2、CAS的服务

CAS的主要服务有:

  • KDC(Key Distribution Center );
  • AS(Authentication Service)它索取 Crendential,发放 TGT;
  • TGS(Ticket  Granting Service),索取 TGT ,发放 ST。

1.1.3、CAS的媒介

TGC(Ticket Granting Cookie)

存放用户身份认证凭证的cookie,在浏览器和CAS Server间通讯时使用,并且只能基于安全通道传输(Https),是CAS Server用来明确用户身份的凭证。

1.2、CAS工作原理

CAS的单点登录的认证过程,所用应用服务器受到应用请求后,检查ST和TGT,如果没有或不对,转到CAS认证服务器登录页面,通过安全认证后得到ST和TGT,再重新定向到相关应用服务器,在回话生命周期之内如果再定向到别的应用,将出示ST和TGT进行认证,注意,取得TGT的过程是通过SSL安全协议的。

从网上找了一个比较专业又比较详细的CAS工作原理流程图:

CAS流程

专业版可能比较晦涩难懂,来个通俗版。通俗形象地说就是:相当于用户要去游乐场,首先要在门口检查用户的身份 ( 即 CHECK 用户的 ID 和 PASS), 如果用户通过验证,游乐场的门卫 (AS) 即提供给用户一张门卡 (TGT)。

这张卡片的用处就是告诉游乐场的各个场所,用户是通过正门进来,而不是后门偷爬进来的,并且也是获取进入场所一把钥匙。

现在用户有张卡,但是这对用户来不重要,因为用户来游乐场不是为了拿这张卡的而是为了游览游乐项目,这时用户摩天楼,并想游玩。

这时摩天轮的服务员 (client) 拦下用户,向用户要求摩天轮的 (ST) 票据,用户说用户只有一个门卡 (TGT), 那用户只要把 TGT 放在一旁的票据授权机 (TGS) 上刷一下。

票据授权机 (TGS) 就根据用户现在所在的摩天轮,给用户一张摩天轮的票据 (ST), 这样用户有了摩天轮的票据,现在用户可以畅通无阻的进入摩天轮里游玩了。

当然如果用户玩完摩天轮后,想去游乐园的咖啡厅休息下,那用户一样只要带着那张门卡 (TGT). 到相应的咖啡厅的票据授权机 (TGS) 刷一下,得到咖啡厅的票据 (ST) 就可以进入咖啡厅

当用户离开游乐场后,想用这张 TGT 去刷打的回家的费用,对不起,用户的 TGT 已经过期了,在用户离开游乐场那刻开始,用户的 TGT 就已经销毁了 。

二、基于角色的权限管理模型

在业界接受度较高的权限模型是RBAC(Role-Based Access Control),基本的概念是将“角色”这个概念赋予用户,在系统中用户通过分配角色从而获得相应的权限,一个用户可以有多个角色,一个角色可以有多个权限,从而实现权限的灵活配置。

2.1、基本的RBAC模型

最基本的RBAC模型,就是由“用户”,“角色”以及“权限”这三个主体组成,一个用户可以有多个角色,一个角色可以有多个权限,他们之间的关系可以是多对一关系,也可以是多对多关系。

用户角色权限关系

2.2、引入用户组的RBAC模型

如果用户数量比较庞大,新增一个角色时,需要为大量用户都重新分配一遍新的角色,工程量巨大,此时可以引入用户组的概念。如果部分用户的使用场景是相对一致和基础的,可以把这些用户打包成一个组,基于这个组的对象进行角色和权限的赋予。最终用户拥有的所有权限 = 用户个人拥有的权限+该用户所在用户组拥有的权限。

2.3、角色分级的RBAC模型

在一些业务场景中,上层角色需要继承下层角色的全部权限,此时则需要使用角色继承的RBAC模型。此时除了对角色进行定义,还需要管理角色间的关系,通过关系来体现角色的层级关系,从而达到继承权限的效果。角色的继承关系主有两种:树形图和有向无环图。

角色继承模式

继承关系常常来源于公司团队的组织架构,此时常常将角色与组织结构进行关联达到继承角色模型的效果。

2.4、角色限制的RBAC模型

在一些产品或系统中,部分角色可能是需要隔离的、不允许被同时赋予一个人的,比如不能既是运动员又是裁判员。因此,有些角色存在互拆关系。此外,限制还可能是数量上的,比如某个产品组中有且只有一个管理员,不允许删除或再分配其他管理员。

根据不同的业务需求,限制的形式很多,需要注意的是不能仅仅依赖后段限制,而是要在前端展示清晰的规则和恰当的限制,避免用户出错。

2.5、权限管理的基本元素

权限管理的基本元素为:用户,角色,资源,操作,权限。

1、用户 应用系统的具体操作者,用户可以自己拥有权限信息,可以归属于0~n个角色,可属于0~n个组。他的权限集是自身具有的权限、所属的各角色具有的权限、所属的各组具有的权限的合集。它与权限、角色、组之间的关系都是n对n的关系。

2、用户组(可选) 为了更好地管理用户,对用户进行分组归类,简称为用户分组。组也具有上下级关系,可以形成树状视图。在实际情况中,我们知道,组也可以具有自己的角色信息、权限信息。

3、角色 为了对许多拥有相似权限的用户进行分类管理,定义了角色的概念,例如系统管理员、管理员、用户、访客等角色。角色具有上下级关系,可以形成树状视图,父级角色的权限是自身及它的所有子角色的权限的综合。父级角色的用户、父级角色的组同理可推。

4、资源 权限管理的一个单元实体对象,我们广义的称之为资源,可以是一个人,也可以是一个产品,一个文件,一个页面 等等。5、操作 对资源进行的实际操作,比如读、写、编辑等等。

6、权限 资源+操作,构成一个权限控制点。

对象间的关系包括:

  • 是否关系
  • 继承关系
  • 限制关系(互斥、范围限制、边界限制、字段限制)

三、数据表设计

按照RBAC模型,数据库可以这样设计:

1、产品表(t_product_info)

字段名称字段类型备注
产品Idpro_idint(11)自增
产品名称(英)name_envarchar(50)not null
产品名层(中)name_chvarchar(50)not null
创建人creatorvarchar(50)not null
所属人ownervarchar(50)not null
描述descriptionvarchar(255)
创建时间create_timetimestampnot null

2、产品成员表(t_product_member)

字段名称字段类型备注
记录IDidint(11)自增
产品IDpro_idint(11)fk:t_produck_info
成员IDmember_idint(11)not null
创建时间create_timetimestampnot null

3、用户信息表(t_user_info)

字段名称字段类型备注
用户IDuser_idint(11)not null
英文名nike_namevarchar(10)not null
中文名real_namevarchar(10)not null
创建时间create_timetimestampnot null
更新时间update_timetimestampnot null

4、用户角色表(t_user_role)

字段名称字段类型备注
记录IDidint(11)自增
用户IDuser_idint(11)not null
角色IDrole_idvarchar(50)not null
创建时间create_timetimestampnot null

5、角色表(t_role)

字段名称字段类型备注
记录IDidint(11)自增
角色IDrole_idvarchar(50)not null,比如:A~USER
角色名称role_namevarchar(50)not null
对象objectvarchar(50)not null
对象IDobject_idvarchar(50)not null
角色备注commentvarchar(255)
创建时间create_timetimestampnot null

6、基础角色表(t_role_base)

字段名称字段类型备注
记录IDidint(11)自增
角色IDrole_idvarchar(50)not null,比如:A~USER
角色名称role_namevarchar(50)not null
角色备注commentvarchar(255)
权限permissionvarchar(255)not null
创建时间create_timetimestampnot null

7、角色权限表(t_role_permission)

字段名称字段类型备注
记录IDidint(11)自增
角色IDrole_idvarchar(50)fk:t_role->role_id
权限permissionvarchar(255)not null
基础角色IDrole_base_idvarchar(50)fk:t_role_base->role_id
创建时间create_timetimestampnot null

8、用户组表(t_user_group,可选)

字段名称字段类型备注
组IDidint(11)自增
组名称group_namevarchar(50)not null
组描述group_descvarchar(255)not null
创建时间create_timetimestampnot null

9、组角色表(t_user_group_role,可选)

字段名称字段类型备注
记录IDidint(11)自增
组IDrole_idvarchar(50)not null
角色IDrole_idvarchar(255)not null
创建时间create_timetimestampnot null

10、用户权限表(t_user_permission,可选)

字段名称字段类型备注
记录IDidint(11)自增
用户IDrole_idvarchar(50)not null
权限permissionvarchar(255)not null
创建时间create_timetimestampnot null

四、角色及权限点设计

权限控制的整个过程可以描述为:“谁”对“什么”进行什么”操作",从而,引出我们需要做的工作有:角色设计,资源定义,以及对资源的操作定义。再详细描述下,鉴权就是根据用户身份(角色)获得其对那些资源,可以进行什么操作,其中对资源的操作做为一个独立的权限体。

4.1、定义系统中的用户角色

一般是采用“通用角色+实例角色”的模式,实例角色可继承通用角色,从而拥有通用角色的权限。

常见的通用角色定义:ADMIN、MANAGER、MEMBER、GUEST 常见角色权限分配:1)SUPER_ADMIN,具有系统一切权限 1)产品ADMIN,具有当前产品所有权限;2)产品MANAGER,不具备删除权限,可修改,添加成员等 3)产品MEMEBER,可查看,修改信息,不可添加成员;4)产品GUEST,只可查看

实例角色:实例角色一般可以这样定义:“资源点+通用角色+资源ID”

注:其中资源可能是产品,可能是页面,也可能是菜单等

4.2、定义系统中的资源以及操作

一般系统中的最常见资源就是:产品(P) 一般对资源的主要操作包括:增加(CREATE)、删除(DELETE)、修改(EDIT)、查看(VIEW)

当然,系统中的资源肯定不止产品,同时产品这个粒度有些太粗,还可以更细化控制,当然一切都根据实际业务需求情况定义相应的资源点和操作。

4.3、权限体策略

权限控制策略采用五元组,如下:

资源:操作:实例:BU:密级

其中,资源可以是人,也可以是一个需求,一个文件等等;操作为对资源的操作;实例极为产品ID或者用户ID;BU,密级用于控制不同BU,不同保密模块的可见性

五、身份认证加权限管理实施

JAVA可以采用SHIRO框架,一个最简单的一个Shiro应用:1)应用代码通过subject授权,而subject又委托给SecurityManager;2)我们需要给Shiro的security注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断;

Shiro的最主要要做的工作其实就是两个:身份认证和权限校验,下面分别进行介绍。

5.1、身份认证

通过前面的文章分析,我们知道自定义身份校验校验逻辑,只需要继承AuthenticatingRealm即可,Override如下接口进行身份认证:

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token){}
复制代码

上面这个接口,参数AuthenticationToken,它可以自定义子类实现,框架提供的有:UsernamePasswordToken,CasToken。

如果是采用用户名密码方式登陆,那么就构造一个UsernamePasswordToken,然后取数据查询用户名密码是否有效;如果是采用的CAS方式登陆,那么就通过ticket构造一个CasToken,然后与CAS服务交互验证ticket是否有效。如果验证通过会生成一个AuthenticationInfo。此时身份认证完成。

最简单的用户密码登陆身份校验代码 CAS方式验证首先得有CAS系统,这里就不说明CAS方式怎么验证了,说一下怎么用用户密码登陆进行身份校验,认证流程都一样

自定义一个AuthenticatingRealm:

public class MyRealm1 implements AuthenticatingRealm {  
    @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {  
        String username = (String)token.getPrincipal();  //得到用户名 
        String password = new String((char[])token.getCredentials()); //得到密码  
        //取数据库中看用户名是否有效
        //checkUserInfo();
        
        //如果身份认证验证成功,返回一个AuthenticationInfo实现;  
        return new SimpleAuthenticationInfo(username, password, getName());  
    }  
}  
复制代码

5.2、权限校验

权限校验主要要做的事情就是完成"从数据库中查出用户所拥有的所有权限是否包含当前待校验的权限"这么一个判断过程,因此主要要做的就是:1)从数据库中查出用户所拥有的所有权限;2)解析权限,看看是否包含待校验的权限。

1、第一步:获取用户权限信息 获取用户权限信息这个过程是在Realm中完成的,继承AuthorizingRealm,然后Override如下两个接口获取用户的权限信息:

//获取用户身份信息,Authorization前需要先获取用户身份信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token){}

//获取用户权限信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection){
}
复制代码

权限信息查询过程一般为:1)从数据库中读区用户自身所配权限;2)从数据库中读取用户角色所用拥有的权限(角色包含实例角色和BASE角色) 3)用户最终的权限:用户自身权限+用户角色权限

2、第二步:权限校验

1)如果通过角色校验,则调用hasRole,与传入的角色比较即可;

2)如果通过权限体校验,则调用isPermitted(...),与传入的权限进行比较即可。

shiro内部逻辑如下:首先通过PermissionResolver将权限字符串转换成相应的Permission实例,默认使用WildcardPermissionResolver,即转换为通配符的WildcardPermission;接着调用Permission.implies(Permission p)逐个与传入的权限比较,如果有匹配的则返回true,否则false。

六、参考资料

https://shiro.apache.org/

https://github.com/apache/shiro

https://jinnianshilongnian.iteye.com/blog/2018398

https://my.oschina.net/bochs/blog/2248956

https://blog.itning.top/posts/Essays/20190408-A-brief-explanation-of-single-sign-on-SSO-and-centralized-authentication-service-CAS-and-open-authorized-OAuth.html

来源:https://juejin.im/post/6850037267554287629


END
最近熬夜给大家准备了515套Java代码,有一些是业务类的小项目,比如Java博客项目,也有脚手架、也有平时用一些的工具类、21套小程序代码,也有一些游戏类的项目。

扫以下二维码并回复“828”即可获取


如下图所示

浏览 15
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报