1

前言

什么是jasbin?

概述

Casbin是一个强大的、高效的开源访问控制框架,其权限管理机制支持多种访问控制模型

Casbin支持以下编程语言:

Casbin 可以:

支持自定义请求的格式,默认的请求格式为{subject, object, action}。
具有访问控制模型model策略policy两个核心概念。
支持RBAC中的多层角色继承,不止主体可以有角色,资源也可以具有角色。
支持内置的超级用户 例如:root或administrator。超级用户可以执行任何操作而无需显式的权限声明。
支持多种内置的操作符,如 keyMatch,方便对路径式的资源进行管理,如 /foo/bar 可以映射到 /foo*

Casbin 不能:

身份认证 authentication(即验证用户的用户名、密码),casbin只负责访问控制。应该有其他专门的组件负责身份认证,然后由casbin进行访问控制,二者是相互配合的关系。
管理用户列表或角色列表。 Casbin 认为由项目自身来管理用户、角色列表更为合适, 用户通常有他们的密码,但是 Casbin 的设计思想并不是把它作为一个存储密码的容器。 而是存储RBAC方案中用户和角色之间的映射关系。

什么是RBAC?

基于角色的访问控制(RBAC)是实施面向企业安全策略的一种有效的访问控制方式。
其基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。每一种角色对应一组相应的权限。一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限。这样做的好处是,不必在每次创建用户时都进行分配权限的操作,只要分配用户相应的角色即可,而且角色的权限变更比用户的权限变更要少得多,这样将简化用户的权限管理,减少系统的开销。

如何使用Casbin?

想要明白如何使用casbin,必须要清楚model和policy两个概念,即模型和资源,可以参考官方文档
https://casbin.org/zh-CN/ 学习。

springboot集成casbin

1.pom.xml文件添加依赖
        <!--jcasbin-->
        <dependency>
            <groupId>org.casbin</groupId>
            <artifactId>jcasbin</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!--casbin适配器(下面会概述)-->
        <dependency>
            <groupId>org.casbin</groupId>
            <artifactId>jdbc-adapter</artifactId>
            <version>2.0.0</version>
        </dependency>
2.定义model模型,这里是使用RBAC+RESTFUL进行模型的定义。
[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) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)

3.创建casbin适配器类,将datasource数据源写入casbin的适配器

@Slf4j
@Configuration
public class CasbinAdapterConfig {

    private final DataSource dataSource;

    @Autowired
    public CasbinAdapterConfig(DataSource dataSource) {
        this.dataSource = dataSource;

    }

    @Bean
    public JDBCAdapter adapterConfig() throws Exception {
        return new JDBCAdapter(dataSource);
    }
}

4.创建一个初始化的配置类,并且继承InitializingBean接口

InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。

@Component
public class EnforcerFactory implements InitializingBean {


    @Override
    public void afterPropertiesSet() throws Exception {
        
    }
    
}

5.EnforcerFactory配置类中加载模型和适配器

@Component
public class EnforcerFactory implements InitializingBean {

    private static Enforcer enforcer;
    
    private static final String modelpath = "model.conf";

    @Override
    public void afterPropertiesSet() throws Exception {
        ClassPathResource classPathResource = new ClassPathResource(modelpath);
        enforcer = new Enforcer(classPathResource.getAbsolutePath(), jdbcAdapter);
        enforcer.savePolicy();
    }


    /**
     * 是否存在命名授权规则
     *
     * @param sub
     * @param obj
     * @param act
     * @return
     */
    public boolean hasNamedPolicy(String sub, String obj, String act) {
        return enforcer.hasNamedPolicy("p", sub, obj, act);
    }


    /**
     * 是否匹配
     *
     * @param match request
     * @return boolean
     */
    public boolean policyMatch(Object... match) {
        return enforcer.enforce(match);
    }

    /**
     * 向当前策略添加角色继承规则
     *
     * @param request
     * @return
     */
    public boolean addGroupingPolicy(String... request) {
        boolean b = enforcer.addGroupingPolicy(request);
        savePolicy();
        return b;

    }


    /**
     * 添加权限
     *
     * @param policy request
     * @return boolean
     */
    public boolean addPolicy(Policy policy) {
        boolean addPolicy = enforcer.addPolicy(policy.getSub(), policy.getObj(), policy.getAct());
        savePolicy();
        return addPolicy;
    }

    /**
     * 删除权限
     *
     * @param policy request
     * @return boolean
     */
    public boolean removePolicy(Policy policy) {
        boolean removePolicy = enforcer.removePolicy(policy.getSub(), policy.getObj(), policy.getAct());
        savePolicy();
        return removePolicy;
    }

    /**
     * 是否有此资源
     *
     * @param sub
     * @param obj
     * @param act
     * @return
     */
    public boolean hasPolicy(String sub, String obj, String act) {
        return enforcer.hasPolicy(sub, obj, act);
    }

更多api请参考官网:api文档

6.定义拦截器,对登录用户的权限进行拦截,并作出校验是否有此权限。

@Component
@Slf4j
public class MvcInterceptor implements HandlerInterceptor {


   private final EnforcerFactory EnforcerFactory;
   private final RedisUtil redisUtil;
   private final RoleService roleService;

   @Autowired
   public MvcInterceptor(EnforcerFactory enforcerFactory, RedisUtil redisUtil, RoleService roleService) {
       EnforcerFactory = enforcerFactory;
       this.redisUtil = redisUtil;
       this.roleService = roleService;
   }

   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       String path = request.getServletPath();
       String method = request.getMethod();
       log.info("用户ip:{},访问路径:{},请求类型:{}", request.getRemoteAddr(), path, method);

       Cookie[] cookies = request.getCookies();
       ParamCheck.getArrayCheck(cookies, "当前用户未登陆,请重新登陆。");
       // 获取登录的cookie对象
       Optional<Cookie> first = Arrays.stream(cookies).filter(w -> w.getName().equals(UserConstants.USER_LOGIN_COOKIE)).findFirst();
       // 校验是否登录
       if (!first.isPresent()) {
           throw new BaseException("用户未登录,请重新登录");
       }
       // 获取到cookie
       Cookie cookie = first.get();
       String key = String.format(UserConstants.USER_LOGIN_REDIS, cookie.getValue());
       if (!redisUtil.hasKey(key)) {
           response.addCookie(CookieUtil.cleanCookie(cookie));
           throw new BaseException("当前用户未登陆,请重新登陆");
       }
       redisUtil.expire(key, 600);
       UserThreadLocal.setThreadLocal(JSON.parseObject((String) redisUtil.get(key), UserDO.class));
       if (!EnforcerFactory.policyMatch(UserLocal.getRoleCode(), path, method)) {
           throw new BaseException("用户:" + UserLocal.getUserName() + ",无此权限");
       }
       return true;
   }

}

Zeran
32 声望4 粉丝

学而不思则罔,思而不学则殆。