序言

由于最近一直卡在权限控制这个坎上,原来设计的比较简单的权限控制思路已经无法满足比较复杂一些的场景,因此一直在探索一种在大部分场景下比较通用的权限模型。

首先,这里说明一下两种RBAC权限模型分别是“基于角色的权限控制(Role-Based-Access-Control)”和“基于资源的权限控制(Resource-Based-Access-Control)”两种模型,这两种模型是Java最常见的权限控制的模型。它们之间的数据库结构区别并没有太大,甚至也可以一样。都是以最基础的五张表(用户表、角色表、用户-角色关系表、权限表、角色-权限关系表)组成,往后再复杂的业务再在这个基础上进行拓展,例如加入用户组、组织、模块等等概念,可以参考一下一这篇文章:

https://blog.csdn.net/qiaqia609/article/details/38102091

下面就以Spring Boot + Shiro为载体来具体记录一下。

1.准备数据库

首先还是最基础的五张表,后续的所有需求都是在这上面来拓展的,不管是基于角色还是基于资源都是差不多的数据结构。

这里还是贴一下Shiro的配置文件:ShiroConfig.java


   
  1. /**
  2. * @author phw
  3. * @date Created in 04-08-2018
  4. * @description 基于RESTFul风格的Shiro配置
  5. */
  6. @Slf4j
  7. @Configuration
  8. public class ShiroConfig {
  9. @Bean
  10. public DefaultWebSecurityManager securityManager(MyShiroRealm myShiroRealm) {
  11. DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  12. securityManager.setRealm(myShiroRealm);
  13. /**
  14. * 关闭shiro自带的session管理
  15. * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
  16. */
  17. DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
  18. DefaultSessionStorageEvaluator evaluator = new DefaultSessionStorageEvaluator();
  19. evaluator.setSessionStorageEnabled( false);
  20. subjectDAO.setSessionStorageEvaluator(evaluator);
  21. securityManager.setSubjectDAO(subjectDAO);
  22. return securityManager;
  23. }
  24. @Bean
  25. public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
  26. ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
  27. //filterFactoryBean.setLoginUrl("/sign-in");
  28. //添加自己的过滤器并取名jwt
  29. Map<String, Filter> filterMap = new HashMap<>();
  30. filterMap.put( "jwt", new JWTFilter());
  31. filterFactoryBean.setFilters(filterMap);
  32. filterFactoryBean.setSecurityManager(securityManager);
  33. filterFactoryBean.setUnauthorizedUrl( "/401");
  34. //自定义url规则
  35. Map<String, String> filterRuleMap = new LinkedHashMap<>();
  36. // 访问401和404页面不通过我们的Filter
  37. /*filterRuleMap.put("/401", "anon");
  38. filterRuleMap.put("/403", "anon");
  39. filterRuleMap.put("/404", "anon");
  40. filterRuleMap.put("/sign-in", "anon");
  41. filterRuleMap.put("/sign-up", "anon");
  42. filterRuleMap.put("/sign-out", "logout");
  43. // 所有请求通过自己的JWT Filter
  44. filterRuleMap.put("/**", "jwt");*/
  45. /*List<Permission> permissions = permissionMapper.selectAll();
  46. for (Permission permission: permissions) {
  47. filterRuleMap.put(permission.getUrl(), permission.getInit());
  48. }
  49. filterFactoryBean.setFilterChainDefinitionMap(filterRuleMap);
  50. log.info("Shiro Filter Factory Bean inject successful...");*/
  51. return filterFactoryBean;
  52. }
  53. @Bean
  54. @DependsOn( "lifecycleBeanPostProcessor")
  55. public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
  56. DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
  57. // 强制使用cglib,防止重复代理和可能引起代理出错的问题
  58. // https://zhuanlan.zhihu.com/p/29161098
  59. advisorAutoProxyCreator.setProxyTargetClass( true);
  60. return advisorAutoProxyCreator;
  61. }
  62. @Bean
  63. public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
  64. return new LifecycleBeanPostProcessor();
  65. }
  66. @Bean
  67. public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
  68. AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
  69. advisor.setSecurityManager(securityManager);
  70. return advisor;
  71. }
  72. }

这里配置的是无状态(stateless)的shiro配置,因为要用到RESTful协议,所以需要使用jwt来保证api的安全。所以这里需要关闭掉Shiro自带的Session管理器,然后启用Shiro注解,自定义URL规则会在稍后说到。

2.Role Based Access Control

首先,来说一下比较常见的基于角色的权限控制。

我们所说的角色,其实就是一系列权限的集合,里面指定了哪种角色可以做什么事情,而用户也可以看作是一组角色的集合,所以我们在使用第一种rbac来进行权限控制的时候,一般是判断一个用户是否有某一个角色。我们用Shiro来讲就是像下面这样:


   
  1. @RequiresRoles( "admin")
  2. //或者
  3. if(SecurityUtils.getSubject().hasRole( "admin")) {
  4.      //do some thing...
  5.     System.out.println( "I'm admin.")
  6. }

上面的代码是很多文章集成shiro后成功的样子,我当时也是这么做的,但是随着后来权限管理的业务越来越复杂,这种简单的或者说静态的权限管理模型已经不适用了,举个栗子:如果现在我要增加一个角色,名字叫teacher,让它也能打印一点东西。那么要做的就是首先在数据库添加角色名,然后修改权限代码,改成:

@RequiresRoles(value = {"admin", "teacher"}, logical = Logical.OR)
  

或者类似。也就是说,每一次变更需求,都需要变更代码,然后重新部署项目~~这显然是不怎么符合我们预期要求的。

因此这种模型只适合对权限需求变动不大的场景了。

当然,优点就是非常简单,开发起来使用起来非常方面,特别是配合上Shiro,一个注解就能搞定。

3.Resource Based Access Control

第二种基于资源的权限控制,是第一种的优化方案,我们还是来看看栗子:


   
  1. @RequiresPermissions( "user:add")
  2. //或者
  3. if(SecurityUtils.getSubject().hasPermission( "user:add")) {
  4. //do some thing...
  5. System.out.println( "user:add");
  6. }

这种方式在一定程度上解决了上述第一种模型的问题,网上也有很多关于Resource Based Access Control的解释,但是这里我用我自己的话来解释一下,还是用上面的栗子,客户要求“admin”和“teacher”都有权限去打印一些东西。于是,第二种就不需要再修改代码了,我直接在前台给teacher授权“user:add”这个权限就可以了啊,因为这样已经很明确的指定了执行这个方法需要什么权限,就是“user:add”权限,我现在不管你是什么角色,什么组织,什么模块也好,只要你拥有这个“user:add”这个权限,那你就能执行这个方法。

但是,问题又来了。

如果现在需求变更,执行这个方法需要另外一个权限“user:edit”怎么办?是不是又得像上面那样,修改代码,重新部署项目。

所以,我们还需要一种能够完全动态控制权限的模型,不把任何权限或者角色写死,直接在数据库(前台)配置,每增加一个功能就注册一个功能然后指定它的需要的权限,就算需求变更,也不需要重新部署项目,只需要在前台稍微修改配置一下就能达到目的。

太晚了,今天就写到这里吧,预知后事如何,请听下回.......分解........

原文地址:https://blog.csdn.net/qq_33698579/article/details/80159823

发布评论
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

权限系统的设计模式 ACL RBAC ABAC知识解答
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。