Casbin 权限框架

某团队需要在 Java 应用中实现一套权限控制系统。他们评估了几个方案:自研 RBAC、Spring Security、Shiro、Keycloak……每个方案都有自己的权衡。最后,团队选择了一个名字有趣的框架——Casbin。

Casbin 是一个轻量级的、Go 语言发源的多语言权限框架。它的核心理念是:用配置文件定义模型,用代码执行决策。

一、Casbin 定位

1.1 什么是 Casbin

Casbin 是一个开源的访问控制框架,最初由 Go 语言实现,现已支持 Java、Python、Node.js 等多种语言:

语言实现GitHub Stars
Gocasbin/casbin18k+
Javacasbin/jcasbin2k+
Pythoncasbin/pycasbin1k+
Node.jscasbin/node-casbin1k+

1.2 核心特性

特性说明
多模型支持ACL、RBAC、ABAC 等
轻量级核心库体积小,依赖少
可扩展适配器模式存储策略
高性能本地评估,无网络开销
简洁 API学习曲线低

二、支持的权限模型

2.1 ACL 模型

最基本的访问控制列表模型:

ACL
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

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

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

2.2 RBAC 模型

支持角色继承的 RBAC:

RBAC
[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

2.3 RBAC with Domains

带域(租户)隔离的 RBAC:

RBAC
[request_definition]
r = sub, dom, obj, act

[policy_definition]
p = sub, dom, obj, act

[role_definition]
g = _, _, _

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

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

2.4 ABAC 模型

支持属性条件判断:

ABAC
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

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

[matchers]
m = r.sub.Role == "admin" || r.obj.Owner == r.sub.Name

2.5 RESTful 模型

支持 RESTful 风格的路径匹配:

RESTful
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

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

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

三、核心概念

3.1 Model 模型

模型定义了如何做决策的逻辑:

flowchart LR
    subgraph "Model"
        M1[[Request Definition]]
        M2[[Policy Definition]]
        M3[[Matchers]]
        M4[[Policy Effect]]
    end
    
    subgraph "执行流程"
        REQ[请求] --> M1
        M1 --> MATCH[匹配器]
        MATCH --> M3
        MATCH --> M4
        M4 --> RES[结果]
    end

3.2 Policy 策略

策略定义了允许什么:

策略文件示例)
p, alice, data1, read
p, bob, data2, write
p, manager, data, read
p, manager, data, write

g, alice, manager
g, bob, manager

3.3 Enforcer 执行器

执行器是 Casbin 的核心,持有 Model 和 Adapter:

flowchart TB
    subgraph "Enforcer"
        E[Enforcer]
    end
    
    subgraph "依赖"
        E --> M[Model]
        E --> A[Adapter]
    end
    
    subgraph "输入"
        E --> REQ[请求]
    end
    
    subgraph "输出"
        E --> RES[决策]
    end

四、架构设计

4.1 整体架构

flowchart TB
    subgraph "应用层"
        APP[Application]
    end
    
    subgraph "Casbin"
        E[Enforcer]
        M[Model]
        EM[Enforcement Manager]
    end
    
    subgraph "存储层"
        A[Adapter]
        DB[(Database)]
    end
    
    APP -->|Enforce| E
    E --> M
    E --> A
    A --> DB

4.2 适配器模式

flowchart LR
    subgraph "Casbin"
        E[Enforcer]
    end
    
    subgraph "适配器"
        FA[File Adapter]
        MA[MySQL Adapter]
        RA[Redis Adapter]
        PA[PostgreSQL Adapter]
    end
    
    subgraph "存储"
        F[文件]
        M[(MySQL)]
        R[(Redis)]
        P[(PostgreSQL)]
    end
    
    E --> FA
    E --> MA
    E --> RA
    E --> PA
    
    FA --> F
    MA --> M
    RA --> R
    PA --> P

4.3 执行流程

sequenceDiagram
    participant App as Application
    participant E as Enforcer
    participant M as Model
    participant A as Adapter
    participant DB as Database
    
    App->>E: Enforce(request)
    E->>M: Match Policy
    M-->>E: Matched Policy
    E->>A: Load Policy (if needed)
    A-->>E: Policy Rules
    E->>E: Evaluate with Matcher
    E-->>App: Allow/Deny

五、Java 实现

5.1 Maven 依赖

pom.xml)
<dependency>
    <groupId>org.casbin</groupId>
    <artifactId>jcasbin</artifactId>
    <version>1.27.0</version>
</dependency>

5.2 基本使用

BasicExample.java)
import org.casbin.jcasbin.main.Enforcer;
import org.casbin.jcasbin.model.Model;
import org.casbin.jcasbin.adapter.FileAdapter;

public class BasicExample {
    public static void main(String[] args) throws Exception {
        // 1. 创建执行器
        Enforcer e = new Enforcer(
            "path/to/basic_model.conf",  // 模型文件
            "path/to/basic_policy.csv"    // 策略文件
        );
        
        // 2. 权限检查
        boolean allowed = e.enforce("alice", "data1", "read");
        System.out.println("alice can read data1: " + allowed);
        
        // 3. 修改策略(文件模式)
        e.addPolicy("bob", "data2", "write");
        e.removePolicy("alice", "data1", "read");
    }
}

5.3 RBAC 实现

RBACExample.java)
public class RBACExample {
    public static void main(String[] args) throws Exception {
        Enforcer e = new Enforcer(
            "path/to/rbac_model.conf",
            "path/to/rbac_policy.csv"
        );
        
        // 权限检查(会自动检查角色继承)
        boolean allowed = e.enforce("alice", "data", "read");
        System.out.println("alice can read data: " + allowed);
        
        // 获取用户的所有角色
        List<String> roles = e.getRolesForUser("alice");
        System.out.println("alice's roles: " + roles);
        
        // 获取角色拥有的权限
        List<String> permissions = e.getPermissionsForUser("alice");
        System.out.println("alice's permissions: " + permissions);
        
        // 添加角色继承关系
        e.addRoleForUser("alice", "admin");
        
        // 检查角色成员
        List<String> members = e.getUsersForRole("admin");
        System.out.println("admin members: " + members);
    }
}

5.4 数据库适配器

DatabaseExample.java)
public class DatabaseExample {
    public static void main(String[] args) throws Exception {
        // 1. 使用 MySQL 适配器
        DbAdapter adapter = new DbAdapter(
            "mysql",
            "jdbc:mysql://localhost:3306/casbin",
            "root",
            "password"
        );
        
        // 2. 自动创建表
        adapter.createTable("casbin_rule");
        
        // 3. 创建执行器
        Enforcer e = new Enforcer(
            "path/to/rbac_model.conf",
            adapter
        );
        
        // 4. 从数据库加载策略
        // 策略会在首次加载时自动从数据库读取
        
        // 5. 修改策略(持久化到数据库)
        e.addPolicy("charlie", "data3", "read");
        e.removePolicy("alice", "data1", "read");
        
        // 6. 清除缓存并重新加载
        e.clearCache();
        e.loadPolicy();
    }
}

5.5 Spring Boot 集成

CasbinConfig.java)
@Configuration
public class CasbinConfig {
    
    @Value("${casbin.model.path}")
    private String modelPath;
    
    @Value("${casbin.policy.adapter}")
    private String adapterType;
    
    @Bean
    public Enforcer casbinEnforcer() throws Exception {
        Enforcer e;
        
        if ("database".equals(adapterType)) {
            // 使用数据库适配器
            DataSourceAdapter adapter = new DataSourceAdapter(dataSource);
            e = new Enforcer(modelPath, adapter);
        } else {
            // 使用文件适配器
            FileAdapter adapter = new FileAdapter(modelPath.replace(".conf", "_policy.csv"));
            e = new Enforcer(modelPath, adapter);
        }
        
        return e;
    }
}
CasbinService.java)
@Service
@Slf4j
public class CasbinService {
    
    private final Enforcer enforcer;
    
    public CasbinService(Enforcer enforcer) {
        this.enforcer = enforcer;
    }
    
    /**
     * 权限检查
     */
    public boolean checkPermission(String user, String resource, String action) {
        try {
            return enforcer.enforce(user, resource, action);
        } catch (Exception e) {
            log.error("Casbin check failed", e);
            return false;
        }
    }
    
    /**
     * 批量权限检查
     */
    public Map<String, Boolean> batchCheck(String user, 
            List<ResourceAction> resourceActions) {
        Map<String, Boolean> results = new HashMap<>();
        
        for (ResourceAction ra : resourceActions) {
            boolean allowed = checkPermission(user, ra.getResource(), ra.getAction());
            results.put(ra.getResource() + ":" + ra.getAction(), allowed);
        }
        
        return results;
    }
    
    /**
     * 授予角色
     */
    public boolean grantRole(String user, String role) {
        return enforcer.addRoleForUser(user, role);
    }
    
    /**
     * 撤销角色
     */
    public boolean revokeRole(String user, String role) {
        return enforcer.deleteRoleForUser(user, role);
    }
}

六、策略存储与持久化

6.1 策略存储表结构

MySQL
CREATE TABLE casbin_rule (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    ptype VARCHAR(128) NOT NULL,  -- 策略类型:p, g, p2, g2 等
    v0 VARCHAR(128),
    v1 VARCHAR(128),
    v2 VARCHAR(128),
    v3 VARCHAR(128),
    v4 VARCHAR(128),
    v5 VARCHAR(128),
    INDEX idx_ptype (ptype)
);

6.2 自定义适配器

RedisAdapter.java)
public class RedisAdapter extends Adapter {
    
    private JedisPool jedisPool;
    private String keyPrefix = "casbin:policy:";
    
    public RedisAdapter(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }
    
    @Override
    public void loadPolicy(Model model) {
        try (Jedis jedis = jedisPool.getResource()) {
            Set<String> keys = jedis.keys(keyPrefix + "*");
            
            for (String key : keys) {
                String value = jedis.get(key);
                String[] parts = value.split(",");
                
                // 解析并加载策略
                if (parts.length >= 2) {
                    model.addPolicy(parts[0], "p", Arrays.copyOfRange(parts, 1, parts.length));
                }
            }
        }
    }
    
    @Override
    public void savePolicy(String ptype, String... rules) {
        // 实现保存逻辑
    }
}

七、Casbin vs OPA

7.1 核心对比

维度CasbinOPA
策略语言模型配置 + 匹配器Rego
表达能力中等强大
学习曲线低中等
性能本地评估,高性能可配置
生态系统较小丰富
K8s 集成无Gatekeeper
云原生支持一般优秀
策略测试基础完善

7.2 选型建议

场景推荐
简单 RBAC 需求Casbin
复杂策略逻辑OPA
微服务权限Casbin
K8s 资源控制OPA Gatekeeper
快速上线Casbin
需要云原生生态OPA

7.3 混合使用

混合使用示例)
@Service
public class HybridAuthorizationService {
    
    private final Enforcer casbin;
    private final OPAClient opaClient;
    
    /**
     * Casbin 处理常规 RBAC
     * OPA 处理特殊 ABAC 规则
     */
    public AuthorizationResult authorize(AccessRequest request) {
        // 1. Casbin 快速判断(简单 RBAC)
        if (casbin.enforce(request.getUserId(), 
                request.getResourceType(), 
                request.getAction())) {
            return AuthorizationResult.permit("casbin");
        }
        
        // 2. OPA 判断(复杂规则)
        if (opaClient.evaluate(buildOPAInput(request)).isAllowed()) {
            return AuthorizationResult.permit("opa");
        }
        
        return AuthorizationResult.deny();
    }
}
核心洞察

Casbin 的设计哲学是「简单场景简单做」。如果你只需要一个轻量级的 RBAC/ACL 引擎,Casbin 是很好的选择;如果你的策略逻辑复杂且需要云原生集成,OPA 更合适。

思考题

问题 1:Casbin 的模型配置和 OPA 的 Rego 语言,各自在表达能力和易用性上有什么取舍?

参考答案

表达能力对比:

场景CasbinOPA
简单的角色权限✅ 简洁✅ 可以
基于时间的权限⚠️ 需要辅助字段✅ 原生支持
基于 IP 的权限⚠️ 需要辅助字段✅ 原生支持
数值比较❌ 不支持✅ 支持
正则表达式⚠️ 部分支持✅ 完整支持
跨表关联查询❌ 不支持✅ 支持

易用性对比:

维度CasbinOPA
入门难度低中等
配置文件可读性高(INI 风格)中(函数式)
调试难度低中等
测试工具基础完善

结论:简单场景用 Casbin,复杂场景用 OPA。

问题 2:设计一个将 Casbin 与 Spring Security 集成的方案,使两者协同工作。

参考答案

集成方案设计:

1. 架构分层

┌─────────────────────────────────────┐
│         Spring Security Filter      │
├─────────────────────────────────────┤
│  认证 (Authentication)               │ ← Spring Security
├─────────────────────────────────────┤
│  授权 (Authorization)               │ ← Casbin + Spring Security
└─────────────────────────────────────┘

2. 实现代码

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .addFilterBefore(casbinAuthorizationFilter(), 
                AuthorizationFilter.class);
        
        return http.build();
    }
    
    @Bean
    public CasbinAuthorizationFilter casbinAuthorizationFilter() {
        return new CasbinAuthorizationFilter(casbinService);
    }
}

3. Casbin 授权过滤器

public class CasbinAuthorizationFilter extends OncePerRequestFilter {
    
    private final CasbinService casbinService;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) 
            throws ServletException, IOException {
        
        String user = getCurrentUser(request);
        String resource = extractResource(request);
        String action = mapMethodToAction(request.getMethod());
        
        if (!casbinService.checkPermission(user, resource, action)) {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            return;
        }
        
        chain.doFilter(request, response);
    }
}

4. 职责划分建议

层级职责实现
全局角色@PreAuthorize("hasRole('ADMIN')")Spring Security
资源权限文档、API 的读/写/删Casbin
动态规则时间、IP、设备等OPA(可选)