美文网首页开发设计Java 杂谈Java技术升华
如何设计实现一个通用的权限管理模块

如何设计实现一个通用的权限管理模块

作者: PioneerYi | 来源:发表于2019-07-28 22:18 被阅读286次

    一个系统,如果没有安全控制,是十分危险的,一般安全控制包括身份认证和权限管理。用户访问时,首先需要查看此用户是否是合法用户,然后检查此用户可以对那些资源进行何种操作,最终做到安全访问。身份认证的方式有很多种,最简单的就是直接用户名密码,还有业内比较通用的方式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的简要解释

    祝工作顺利,天天开心!

    相关文章

      网友评论

        本文标题:如何设计实现一个通用的权限管理模块

        本文链接:https://www.haomeiwen.com/subject/otkmrctx.html