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(可选)