SpringBoot整合jasbin實現RBAC權限管理

前言

什麼是jasbin?

概述

Casbin是一個強大的、高效的開源訪問控制框架,其權限管理機制支持多種訪問控制模型redis

Casbin支持如下編程語言:

Casbin 能夠:

支持自定義請求的格式,默認的請求格式爲{subject, object, action}。
具備訪問控制模型model策略policy兩個核心概念。
支持RBAC中的多層角色繼承,不止主體能夠有角色,資源也能夠具備角色。
支持內置的超級用戶 例如:root或administrator。超級用戶能夠執行任何操做而無需顯式的權限聲明。
支持多種內置的操做符,如 keyMatch,方便對路徑式的資源進行管理,如 /foo/bar 能夠映射到 /foo*spring

Casbin 不能:

身份認證 authentication(即驗證用戶的用戶名、密碼),casbin只負責訪問控制。應該有其餘專門的組件負責身份認證,而後由casbin進行訪問控制,兩者是相互配合的關係。
管理用戶列表或角色列表。 Casbin 認爲由項目自身來管理用戶、角色列表更爲合適, 用戶一般有他們的密碼,可是 Casbin 的設計思想並非把它做爲一個存儲密碼的容器。 而是存儲RBAC方案中用戶和角色之間的映射關係。編程

什麼是RBAC?

基於角色的訪問控制(RBAC)是實施面向企業安全策略的一種有效的訪問控制方式。
其基本思想是,對系統操做的各類權限不是直接授予具體的用戶,而是在用戶集合與權限集合之間創建一個角色集合。每一種角色對應一組相應的權限。一旦用戶被分配了適當的角色後,該用戶就擁有此角色的全部操做權限。這樣作的好處是,沒必要在每次建立用戶時都進行分配權限的操做,只要分配用戶相應的角色便可,並且角色的權限變動比用戶的權限變動要少得多,這樣將簡化用戶的權限管理,減小系統的開銷。api

如何使用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的時候都會執行該方法。springboot

@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.定義攔截器,對登陸用戶的權限進行攔截,並做出校驗是否有此權限。cookie

@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;
   }

}
相關文章
相關標籤/搜索