美文网首页
权限框架之jcasbin讲解

权限框架之jcasbin讲解

作者: 上善若泪 | 来源:发表于2023-09-03 11:06 被阅读0次

    1 jcasbin

    1.1 前言

    作为一名后台开发人员,权限这个名词应该算是特别熟悉的了。就算是java里的类也有 publicprivate权限之分。之前项目里一直使用shiro作为权限管理的框架。说实话,shiro的确挺强大的,但是它也有很多不好的地方。shiro默认的登录地址还是login.jsp,前后端分离模式使用shiro还要重写好多类;手机端存储用户信息、保持登录状态等等,对shiro来说也是一个难题。
    在分布式项目里,比如电商项目,其实不太需要明确的权限划分,说白了,认为没必要做太麻烦的权限管理,一切从简。何况shiro对于springCloud等各种分布式框架来说,简直就是灾难。每个子系统里都要写点shiro的东西,慢慢的,越来越恶心。zuul网关就在这里大显身手了,控制用户的登录,鉴定用户的权限等等。zuul网关控制用户登录,鉴权以后再详说。
    然后最近发现了另一个权限框架jcasbin
    github地址:https://github.com/casbin/jcasbin

    点击此处了解spring security权限控制使用操作

    1.2 工作原理

    1.2.1 PERM模型

    Casbin 中, 访问控制模型被抽象为基于 PERM (Policy, Effect, Request, Matcher) 的一个文件,描述了资源与用户之间的关系。
    PERM :

    • Policy:策略,定义权限的规则, p = {sub, obj, act, eft},策略一般存储到数据库,因为会有很多。
    • Effect:影响,它决定我们是否可以放行。
    • Request:访问请求, r = {sub, obj, act}。它实际上定义了我们应该提供访问控制匹配功能的参数名称和顺序。
      sub, obj, act, eft含义如下:
      • subsubject,访问实体;
      • objobject,访问的资源;
      • actaction,访问方法;
      • efteffect,策略结果,一般为空,默认指定allow,还可以定义为deny
    • Matcher:匹配规则,判断 Request 是否满足 Policy。

    1.2.2 Model语法

    ModelCasbin的具体访问模型,其主要以文件的形式出现,该文件常常以.conf最为后缀。

    Model CONF 至少应包含四个部分: [request_definition], [policy_definition], [policy_effect], [matchers]
    如果 model 使用 RBAC, 还需要添加[role_definition]部分。
    Model CONF 文件可以包含注释。注释以 #开头, # 会注释该行剩余部分。

    1.2.2.1 Request定义

    [request_definition]
    r = sub, obj, act
    

    [request_definition] 部分用于request的定义,它明确了 e.Enforce(…) 函数中参数的含义。sub, obj, act 表示经典三元组: 访问实体 (Subject),访问资源 (Object) 和访问方法 (Action)

    1.2.2.2 Policy定义

    [policy_definition]
    p = sub, obj, act
    

    [policy_definition] 部分是对policy的定义,policy部分的每一行称之为一个策略规则, 每条策略规则通常以形如p, p2的policy type开头。

    1.2.2.3 Policy effect定义

    [policy_effect]
    e = some(where (p.eft == allow))
    

    [policy_definition] 部分是对policy的定义。上面的策略效果表示如果有任何匹配的策略规则 允许,最终效果是允许 (aka allow-override)
    p.eft 是策略的效果,它可以 允许 或 否定。 它是可选的,默认值是 允许。 因为我们没有在上面指定它,所以它使用默认值。
    在这个表达式中,some 是一个关键字,表示策略中是否存在符合后续条件的策略项。where 是一个关键字,用于指定条件。eft 属性表示策略的效果,通常用于表示允许访问。

    1.2.2.4 角色定义

    [role_definition]
    g = _, _
    

    这个部分定义了角色的结构
    g = _, _ 表示角色使用通配符_ 表示,表示不关心角色的具体值

    1.2.2.5 匹配器

    [matchers] 是策略匹配程序的定义。匹配程序是表达式。它定义了如何根据请求评估策略规则。

    [matchers]
    m = g(r.sub,p.sub) && r.obj == p.obj && r.act == p.act
    

    上述匹配器是最简单的,这意味着请求中的主题、对象和行动应该与政策规则中的匹配。

    • g(r.sub, p.sub):表示通过匹配器函数 g 来匹配主体属性 r.sub 和策略项的主体属性 p.sub。这里使用的是通配符 "_",表示不关心具体的角色值。
    • r.obj == p.obj:表示匹配对象属性,要求请求的对象属性 r.obj 和策略项的对象属性 p.obj 相等。
    • r.act == p.act:表示匹配动作属性,要求请求的动作属性 r.act 和策略项的动作属性 p.act 相等。

    注意:g()函数和==运算符在匹配器中有不同的作用。

    • g(r.sub, p.sub)g()函数是一个特殊的函数,用于在匹配器中进行角色匹配。在这里,g()函数将匹配请求中的主体属性(r.sub)和策略项中的主体属性(p.sub)。这里的g()函数可以根据需求进行定制,例如使用角色继承关系等。通常情况下,使用通配符_来表示不关心角色的具体值。
    • r.obj == p.obj和r.act == p.act==是相等比较运算符,用于比较请求中的对象属性(r.obj)和策略项中的对象属性(p.obj),以及请求中的动作属性(r.act)和策略项中的动作属性(p.act)。它们要求两边的操作数在值上完全相等才会返回true。
    • g() 函数用于角色匹配,而==运算符用于属性值的比较。这两者在匹配器中的作用是不同的

    1.2.2.6 完整model.conf

    # Request定义
    [request_definition]
    r = sub, obj, act
    
    # 策略定义
    [policy_definition]
    p = sub, obj, act
    
    # 角色定义
    [role_definition]
    g = _, _
    
    [policy_effect]
    e = some(where (p.eft == allow))
    
    # 匹配器定义
    [matchers]
    m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
    

    1.2.3 policy.csv

    policy.csv文件就相当于一张权限表。

    p, zhangsan, data1, read
    

    1.3 准备

    1.3.1 mavan依赖

    <dependency>
        <groupId>org.casbin</groupId>
        <artifactId>jcasbin</artifactId>
        <version>1.4.0</version>
    </dependency>
    <dependency>
        <groupId>org.casbin</groupId>
        <artifactId>jdbc-adapter</artifactId>
        <version>2.0.0</version>
    </dependency>
    

    1.3.2 配置文件

    jcasbin把用户的角色、权限信息(访问路径)放置在配置文件里,然后通过输入流读取配置文件。主要有两个配置文件:model.conf 和 policy.csv。

    image.png

    其实也可以读取数据库的角色权限配置。所以我们可以把关于数据库的信息提取出来,可以进行动态设置。

    @Configuration
    @ConfigurationProperties(prefix = "org.jcasbin")
    public class EnforcerConfigProperties {
     
      private String url;
      
      private String driverClassName;
      
      private String username;
      
      private String password;
      
      private String modelPath;
     
    }
    

    这样我们就可以在application.properties里进行相关配置了。model.conf是固定的文件。policy.csv的内容是可以从数据库读取的。

    org.jcasbin.url=jdbc:mysql://localhost:3306/casbin?useSSL=false
    org.jcasbin.driver-class-name=com.mysql.cj.jdbc.Driver
    org.jcasbin.username=root
    org.jcasbin.password=root
    # 绝对路径
    org.jcasbin.model-path=D:/Work/Project/IDEAWorkSpace/idea2023/swagger-api-demo/conf/authz_model.conf
    # 绝对路径
    org.jcasbin.policy-path=D:/Work/Project/IDEAWorkSpace/idea2023/swagger-api-demo/conf/policy.csv
    

    1.2.3 读取权限信息进行初始化

    我们要对 Enforcer 这个类初始化,加载配置文件里的信息。所以我们写一个类实现InitializingBean,在容器加载的时候就初始化这个类,方便后续的使用。

    @Component
    public class EnforcerFactory implements InitializingBean {
     
      private static Enforcer enforcer;
     
      @Autowired
      private EnforcerConfigProperties enforcerConfigProperties;
      private static EnforcerConfigProperties config;
      
      @Override
      public void afterPropertiesSet() throws Exception {
        config = enforcerConfigProperties;
        //从数据库读取策略
        //JDBCAdapter jdbcAdapter = new JDBCAdapter(config.getDriverClassName(),config.getUrl(),config.getUsername(),config.getPassword());
        //enforcer = new Enforcer(config.getModelPath(), jdbcAdapter);
        //从配置文件读取策略
        enforcer = new Enforcer(config.getModelPath(), config.getPolicyPath());
        enforcer.loadPolicy();//Load the policy from DB.
      }
      
      /**
       * 添加权限
       * @param policy
       * @return
       */
      public static boolean addPolicy(Policy policy){
        boolean addPolicy = enforcer.addPolicy(policy.getSub(),policy.getObj(),policy.getAct());
        enforcer.savePolicy();
        
        return addPolicy;
      }
      
      /**
       * 删除权限
       * @param policy
       * @return
       */
      public static boolean removePolicy(Policy policy){
        boolean removePolicy = enforcer.removePolicy(policy.getSub(),policy.getObj(),policy.getAct());
        enforcer.savePolicy();
        
        return removePolicy;
      }
      
      public static Enforcer getEnforcer(){
        return enforcer;
      }
      
    }
    

    在这个类里,我们注入写好的配置类,然后转为静态的,在afterPropertiesSet方法里实例化Enforcer并加载policy(策略,角色权限/url对应关系)。

    同时又写了两个方法,用来添加和删除policy,Policy是自定的一个类,对官方使用的集合/数组进行了封装。

    public class Policy {
      /**想要访问资源的用户 或者角色*/
      private String sub;
      
      /**将要访问的资源,可以使用 * 作为通配符,例如/user/* */
      private String obj;
      
      /**用户对资源执行的操作。HTTP方法,GET、POST、PUT、DELETE等,可以使用 * 作为通配符*/
      private String act;
     
    }
    

    1.4 使用

    1.4.1 权限控制

    jcasbin的权限控制非常简单,自定义一个过滤器,if判断就可以搞定,没错,就这么简单。

    @WebFilter(urlPatterns = "/*" , filterName = "JCasbinAuthzFilter")
    @Order(Ordered.HIGHEST_PRECEDENCE)//执行顺序,最高级别最先执行,int从小到大
    public class JCasbinAuthzFilter implements Filter {
      
      private static final Logger log = LoggerFactory.getLogger(JCasbinAuthzFilter.class);
     
      private static Enforcer enforcer;
     
      @Override
      public void init(FilterConfig filterConfig) throws ServletException {
      }
     
      @Override
      public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
          throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            
            String user = request.getParameter("username");
            String path = request.getRequestURI();
            String method = request.getMethod();
     
            enforcer = EnforcerFactory.getEnforcer();
            if (path.contains("anon")) {
              chain.doFilter(request, response);
        }else if (enforcer.enforce(user, path, method)) {
          chain.doFilter(request, response);
        } else {
          log.info("无权访问");
          Map<String, Object> result = new HashMap<String, Object>();
                result.put("code", "1001");
                result.put("msg", "用户权限不足");
                result.put("data",null);
                response.setCharacterEncoding("UTF-8");
                response.setContentType(application/json);
                response.getWriter().write(JSONObject.toJSONString(result,SerializerFeature.WriteMapNullValue));
          }
        
      }
     
      @Override
      public void destroy() {
        
      }
     
    }
    

    主要是用 enforcer.enforce(user, path, method) 这个方法对用户、访问资源、方式进行匹配。这里的逻辑可以根据自己的业务来实现。在这个过滤器之前还可以添加一个判断用户是否登录的过滤器。

    1.4.2 添加删除权限

    对于权限的操作,直接调用上面写好的 EnforcerFactory 里对应的方法即可。并且,可以达到同步的效果。就是不用重启服务器或者其他任何操作,添加或删除用户权限后,用户对应的访问就会收到影响。

    
    @RestController
    public class PerController {
    
        @PutMapping("/anon/role/per")
        public String addPer(){
            EnforcerFactory.addPolicy(new Policy("alice", "/user/list", "*"));
            return "OK";
        }
    
        @DeleteMapping("/anon/role/per")
        public String deletePer(){
            EnforcerFactory.removePolicy(new Policy("alice", "/user/list", "*"));
            return "OK";
        }
    }
    
    

    相关文章

      网友评论

          本文标题:权限框架之jcasbin讲解

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