对于RBAC与shiro的一些思考
一、什么是RBAC模型
RBAC模型是一个解决用户权限问题的设计思维。
在最简单的RBAC模型中,将用户表设计为如下几个表
1、用户
2、角色
3、权限
以及这三张表衍生出来的两张中间表
4、用户_角色表
5、权限_角色表
上面描述的5张表就构成了最基本也是最成熟的RBAC模型,可以看得出RBAC模型中的核心是角色,所有与用户相关的权限都是通过角色表进行关联的!在今后我们可能会用到的按钮元素表,样式表等各种各样的资源表最终都如权限表一般,与角色进行关联。RBAC的哲学就是,认角色不认用户,所有的查询都是通过角色来完成的。
1.1、RBAC模型的注意点
我们之所以要用到RBAC模型,本质上是为了实现两个功能:
1、资源对用户的可见性,例如:当前用户对应的角色是否能“看到“某个菜单项或是某个按钮,这一部分其实重在前端渲染,资源的可见性并不能保证资源安全,当有心人拿到资源的请求路径时依旧可以发出相应的请求。在我看来,对资源的可见性,更多的是对用户的体验上的优化。
2、对请求的鉴权,这部分才应该是我们关注的核心点。
需要注意的是,对于资源可见性的控制上,我们只要简单的将五张表进行关联查询,然后通过foreach渲染出用户可见的按钮即可,没有什么复杂的操作。无论是前后端分离的项目还是,传统项目或是各种模板引擎,都建议这样做,当然了还有别的做法shiro自带了jsp渲染规则,大家看看就好。
而对请求鉴权时,如果我们通过手写filter就显得极不优雅,不便于维护。所以就需要结合一些权限框架来使用,在项目中我优先推荐使用Apache的shiro。因为大伙都能快速入手,学习曲线平稳,框架灵活又安全。
在RBAC模型中,整个系统中需要进行鉴权的菜单,按钮,API,其实都是公共的一套,通过鉴权来实现不同角色展示不同的菜单。
二、查询权限列表的思路
当我们在鉴权的时候,最终都是要查询出一个collection集合,在使用mybatis的时候其实就是通过多表查询出一个collection集合。伪代码如下
user代表用户表
role代表角色表
permission代表权限表
user_role代表用户角色中间表
role_permission代表用户权限中间表
1 @Select(select 权限字段名称 from 2 user,user_role,role,role_permission,permission 3 where 4 user.主键 = user_role.用户主键 5 and role.主键 = user_role.角色主键 6 and role.主键 = role_permission.角色主键 7 and permission.主键 = role_permission.权限主键 8 and user.主键 = ?
这样就可以查询出当前用户包含的所有的权限名称列表,shiro就是通过权限名称来进行鉴权处理的,返回结果最好用Set<String>来接收,一个是为了放置重复,虽然说重复了也没啥,另一个是为了在构造函数中传入set集合。
不过这是角色构建,讲道理在权限这块,大家与查询role套路保持一致能清晰一点点。
三、RBAC怎样与shiro进行集成使用
我们想要将RBAC与shiro集成使用,需要优先理解,为什么要这样做。
上面我已经提到了,在对请求进行鉴权的时候,我们碰到了一个小的难题。而shiro自定义realm继承AuthorizingRealm抽象类,就可以优雅的解决请求鉴权。
这里有一个小彩蛋,我们可以只实现认证的realm,但是在项目中我们肯定要实现授权的realm即AuthorizingRealm,为什么授权的realm中既有认证方法又有授权方法呢?猜测是因为,在shiro中认证是鉴权的前置条件(其实好像在任何情况下都是这样,不进行认证我都不知道用户身份那么何谈鉴权---确定当前身份有什么样的权限嘞),所以shiro在实现鉴权realm时,并不需要继承认证的自定义realm。 具体要怎么做的? 其实套路很简单,就是以RABC模型中的角色为桥梁,查出当前用户对应的所有权限,然后将每个url需要的权限进行配置(可以使xml或是@configuration)。再将构造一个SimpleAuthorizationInfo,调用public void addObjectPermissions(Collection<Permission> permissions) 将权限列表设置进当前的角色中。这样就完成了权限的校验。
1 //这个是shiro框架的基础配置 2 @Configuration 3 public class ShiroConfiguration 4 { 5 //@Bean 6 //public CustomMatcher getCustomMatcher(){ 7 // return new CustomMatcher(); 8 //} 9 10 //配置自定义的Realm,这个realm是自定义的认证鉴权逻辑 11 @Bean 12 public CustomRealm getRealm() 13 { 14 CustomRealm customRealm = new CustomRealm(); 15 //customRealm.setCredentialsMatcher(customMatcher); 16 return customRealm; 17 //在realm中也可以自定义我们的密码校验逻辑,shiro文档搜索 CredentialsMatcher就知道怎么玩了 18 } 19 20 //配置安全管理器 21 @Bean 22 public SecurityManager securityManager(CustomRealm realm) { 23 //使用默认的安全管理器 24 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(realm); 25 //将自定义的realm交给安全管理器统一调度管理 26 securityManager.setRealm(realm); 27 return securityManager; 28 } 29 30 //Filter工厂,设置对应的过滤条件和跳转条件 31 @Bean 32 public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { 33 //1.创建shiro过滤器工厂 34 ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean(); 35 //2.设置安全管理器 36 filterFactory.setSecurityManager(securityManager); //3.通用配置(配置登录页面,登录成功页面,验证未成功页面) 37 filterFactory.setLoginUrl("/autherror?code=1"); 38 //设置登录页面 39 filterFactory.setUnauthorizedUrl("/autherror?code=2"); 40 //授权失败跳转页面 41 //4.配置过滤器集合 42 Map<String,String> filterMap = new LinkedHashMap<String,String>(); 43 // 配置不会被拦截的链接 顺序判断 44 filterMap.put("/user/home", "anon"); 45 filterMap.put("/user/**", "authc"); 46 47 //5.设置过滤器 48 filterFactory.setFilterChainDefinitionMap(filterMap); return filterFactory; 49 } 50 }
四、总结
上面说了这么多,其实需要非常关注的点有两个:
1、第一就是视图层面我们是通过查库以及渲染规则来完成客户体验层面的鉴权的,这一步无需shiro的介入(当然shiro中也有关于jsp权限渲染的内容,但是不做介绍了,自己查库渲染比使用shiro自带的jsp渲染要思路清晰的多,而且没有技术壁垒,可以用于各种技术中)。
2、第二点是,在url鉴权时,通过shiro来完成,主要是通过设置filterfactory自定义权限鉴定规则,以及自定义AuthorizingRealm时注入权限列表来完成请求访问层面的鉴权。