如何设计实现一个通用的权限管理模块
一个系统,如果没有安全控制,是十分危险的,一般安全控制包括身份认证和权限管理。用户访问时,首先需要查看此用户是否是合法用户,然后检查此用户可以对那些资源进行何种操作,最终做到安全访问。身份认证的方式有很多种,最简单的就是直接用户名密码,还有业内比较通用的方式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)
字段名称 | 字段 | 类型 | 备注 |
---|---|---|---|
产品Id | pro_id | int(11) | 自增 |
产品名称(英) | name_en | varchar(50) | not null |
产品名层(中) | name_ch | varchar(50) | not null |
创建人 | creator | varchar(50) | not null |
所属人 | owner | varchar(50) | not null |
描述 | description | varchar(255) | |
创建时间 | create_time | timestamp | not null |
2、产品成员表(t_product_member)
字段名称 | 字段 | 类型 | 备注 |
---|---|---|---|
记录ID | id | int(11) | 自增 |
产品ID | pro_id | int(11) | fk:t_produck_info |
成员ID | member_id | int(11) | not null |
创建时间 | create_time | timestamp | not null |
3、用户信息表(t_user_info)
字段名称 | 字段 | 类型 | 备注 |
---|---|---|---|
用户ID | user_id | int(11) | not null |
英文名 | nike_name | varchar(10) | not null |
中文名 | real_name | varchar(10) | not null |
创建时间 | create_time | timestamp | not null |
更新时间 | update_time | timestamp | not null |
4、用户角色表(t_user_role)
字段名称 | 字段 | 类型 | 备注 |
---|---|---|---|
记录ID | id | int(11) | 自增 |
用户ID | user_id | int(11) | not null |
角色ID | role_id | varchar(50) | not null |
创建时间 | create_time | timestamp | not null |
5、角色表(t_role)
字段名称 | 字段 | 类型 | 备注 |
---|---|---|---|
记录ID | id | int(11) | 自增 |
角色ID | role_id | varchar(50) | not null,比如:A~USER |
角色名称 | role_name | varchar(50) | not null |
对象 | object | varchar(50) | not null |
对象ID | object_id | varchar(50) | not null |
角色备注 | comment | varchar(255) | |
创建时间 | create_time | timestamp | not null |
6、基础角色表(t_role_base)
字段名称 | 字段 | 类型 | 备注 |
---|---|---|---|
记录ID | id | int(11) | 自增 |
角色ID | role_id | varchar(50) | not null,比如:A~USER |
角色名称 | role_name | varchar(50) | not null |
角色备注 | comment | varchar(255) | |
权限 | permission | varchar(255) | not null |
创建时间 | create_time | timestamp | not null |
7、角色权限表(t_role_permission)
字段名称 | 字段 | 类型 | 备注 |
---|---|---|---|
记录ID | id | int(11) | 自增 |
角色ID | role_id | varchar(50) | fk:t_role->role_id |
权限 | permission | varchar(255) | not null |
基础角色ID | role_base_id | varchar(50) | fk:t_role_base->role_id |
创建时间 | create_time | timestamp | not null |
8、用户组表(t_user_group,可选)
字段名称 | 字段 | 类型 | 备注 |
---|---|---|---|
组ID | id | int(11) | 自增 |
组名称 | group_name | varchar(50) | not null |
组描述 | group_desc | varchar(255) | not null |
创建时间 | create_time | timestamp | not null |
9、组角色表(t_user_group_role,可选)
字段名称 | 字段 | 类型 | 备注 |
---|---|---|---|
记录ID | id | int(11) | 自增 |
组ID | role_id | varchar(50) | not null |
角色ID | role_id | varchar(255) | not null |
创建时间 | create_time | timestamp | not null |
10、用户权限表(t_user_permission,可选)
字段名称 | 字段 | 类型 | 备注 |
---|---|---|---|
记录ID | id | int(11) | 自增 |
用户ID | role_id | varchar(50) | not null |
权限 | permission | varchar(255) | not null |
创建时间 | create_time | timestamp | not 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。
六、参考资料
1、shiro官方文档
2、apache shiro
3、跟我学shiro
4、微服务架构下的统一身份认证和授权
5、单点登录SSO和集中式认证服务CAS和开放授权OAuth的简要解释
祝工作顺利,天天开心!