Model Syntax
모든 모델 CONF 파일에는 [request_definition], [policy_definition], [policy_effect], [matchers] 네 섹션이 반드시 정의되어야 합니다.
- RBAC의 경우
[role_definition]섹션을 추가합니다. - 제약 조건이 있는 RBAC(예: 직무 분리)의 경우
[constraint_definition]을 추가할 수 있습니다. #으로 시작하는 줄은 주석이며 해당 줄의 나머지는 무시됩니다.
Request Definition
[request_definition] 섹션은 e.Enforce(...)에 전달되는 매개변수를 정의합니다.
[request_definition]
r = sub, obj, act
여기서 sub, obj, act는 표준 3중 항목인 subject(주체), object(객체), action(동작)입니다. 형식을 변경할 수 있습니다. 예를 들 어 리소스가 없는 경우 sub, act를 사용하거나 두 개의 주체가 관련된 경우 sub, sub2, obj, act를 사용할 수 있습니다.
Policy Definition
[policy_definition] 섹션은 정책 규칙의 구조를 설명합니다. 예시:
[policy_definition]
p = sub, obj, act
p2 = sub, act
다음과 같은 정책 파일이 있을 때:
p, alice, data1, read
p2, bob, write-all-objects
정책 파일의 각 줄은 하나의 규칙입니다. 첫 번째 토큰은 정책 유형(p, p2 등)이며 정책 정의와 일치해야 합니다. 위 예시는 matcher에 다음 바인딩을 생성합니다:
(alice, data1, read) -> (p.sub, p.obj, p.act)
(bob, write-all-objects) -> (p2.sub, p2.act)
정책 규칙 요소는 항상 문자열로 처리됩니다. 논의 내용은 casbin/casbin#113을 참조하세요.
Policy Effect
[policy_effect] 섹션은 여러 정책이 일치할 때 결과를 어떻게 조합할지 정의합니다(예: 하나는 허용, 다른 하나는 거부).
[policy_effect]
e = some(where (p.eft == allow))
이는 allow-override를 제공합니다. 일치하는 정책 중 하나라도 effect가 allow이면 결과는 allow입니다. p.eft 필드는 정책 effect(allow 또는 deny)입니다. 이는 선택 사항이며 생략되면 기본값은 allow입니다.
대안적인 정책 effect 예시:
[policy_effect]
e = !some(where (p.eft == deny))
이는 deny-override를 구현합니다. 일치하는 정책 중 effect가 deny인 것이 없을 때만 결과가 allow입니다. 논리 연산자를 사용하여 표현식을 결합할 수 있습니다:
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
해당 표현식은 적어도 하나의 allow가 있고 deny가 없어야 함을 요구합니다. Casbin은 allow와 deny를 모두 지원하며 둘 다 일치하면 deny가 우선합니다.
정책 effect는 아래 내장된 effect 중 하나여야 합니다. 현재 구현에서는 사용자 지정 effect 표현식이 지원되지 않습니다.
사용 가능한 내장 정책 effect:
| Policy Effect | Meaning | Example |
|---|---|---|
| some(where (p.eft == allow)) | allow-override | ACL, RBAC, etc. |
| !some(where (p.eft == deny)) | deny-override | Deny-override |
| some(where (p.eft == allow)) && !some(where (p.eft == deny)) | allow-and-deny | Allow-and-deny |
| priority(p.eft) || deny | priority | Priority |
| subjectPriority(p.eft) | priority based on role | Subject-Priority |
Constraint Definition
선택적 [constraint_definition] 섹션은 RBAC에 대한 불변 조건(예: 직무 분리)을 정의합니다. 제약 조건은 역할 할당이 변경될 때 확인됩니다. 제약 조건을 사용하려면 [role_definition]이 있어야 합니다.
[constraint_definition]
c = sod("finance_requester", "finance_approver")
c2 = sodMax(["payroll_view", "payroll_edit", "payroll_approve"], 1)
c3 = roleMax("superadmin", 2)
c4 = rolePre("db_admin", "security_trained")
Constraint Types
Separation of Duties (sod) — 사용자가 두 역할을 모두 보유할 수 없습니다. Alice가 finance_requester를 가지고 있다면 finance_approver를 할당받을 수 없습니다.
c = sod("finance_requester", "finance_approver")
Separation of Duties Max (sodMax) — 사용자가 가질 수 있는 역할 세트의 개수를 제한합니다. 급여 관련 역할에 대해 sodMax(..., 1)을 설정하면 사용자는 view, edit 또는 approve 중 최대 하나만 가질 수 있습니다.
c2 = sodMax(["payroll_view", "payroll_edit", "payroll_approve"], 1)
Role Cardinality (roleMax) — 역할에 할당될 수 있는 사용자 수를 제한합니다. 예를 들어 superadmin은 최대 두 명의 사용자만 가질 수 있습니다.
c3 = roleMax("superadmin", 2)
Prerequisite Role (rolePre) — 다른 역할을 할당받기 위해 필요한 역할입니다. 예를 들어 사용자가 db_admin을 할당받으려면 먼저 security_trained를 가져야 합니다.
c4 = rolePre("db_admin", "security_trained")
How Constraints Work
제약 조건은 그룹화 정책이 변경될 때 적용됩니다(예: AddGroupingPolicy(), RemoveGroupingPolicy()). 변경으로 인해 제약 조건이 위반되면 작업이 오류와 함께 실패하고 정책이 업데이트되지 않습니다.
모델이 로드될 때 모든 제약 조건이 현재 정책에 대해 확인됩니다. 잘못된 제약 조건(잘못된 구문, RBAC 설정 누락 또는 이미 제약 조건을 위반하는 현재 데이터)으로 인해 로드가 명확한 오류와 함께 실패합니다.
Matchers
[matchers] 섹션은 요청이 정책 규칙과 어떻게 평가되는지를 정의합니다.
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
이 매처는 요청의 주체(subject), 객체(object), 동작(action)이 정책 필드와 정확히 일치해야 합니다.
매처는 산술 연산자(+, -, *, /) 및 논리 연산자(&&, ||, !)를 지원합니다.
매처 내 표현식 순서
매처 내 표현식의 순서는 성능에 상당한 영향을 미칠 수 있습니다. 예시:
const rbac_models = `
[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
`
func TestManyRoles(t *testing.T) {
m, _ := model.NewModelFromString(rbac_models)
e, _ := NewEnforcer(m, false)
roles := []string{"admin", "manager", "developer", "tester"}
// 2500 projects
for nbPrj := 1; nbPrj < 2500; nbPrj++ {
// 4 objects and 1 role per object (so 4 roles)
for _, role := range roles {
roleDB := fmt.Sprintf("%s_project:%d", role, nbPrj)
objectDB := fmt.Sprintf("/projects/%d", nbPrj)
e.AddPolicy(roleDB, objectDB, "GET")
}
jasmineRole := fmt.Sprintf("%s_project:%d", roles[1], nbPrj)
e.AddGroupingPolicy("jasmine", jasmineRole)
}
e.AddGroupingPolicy("abu", "manager_project:1")
e.AddGroupingPolicy("abu", "manager_project:2499")
// With same number of policies
// User 'abu' has only two roles
// User 'jasmine' has many roles (1 role per policy, here 2500 roles)
request := func(subject, object, action string) {
t0 := time.Now()
resp, _ := e.Enforce(subject, object, action)
tElapse := time.Since(t0)
t.Logf("RESPONSE %-10s %s\t %s : %5v IN: %+v", subject, object, action, resp, tElapse)
if tElapse > time.Millisecond*100 {
t.Errorf("More than 100 milliseconds for %s %s %s : %+v", subject, object, action, tElapse)
}
}
request("abu", "/projects/1", "GET") // really fast because only 2 roles in all policies and at the beginning of the casbin_rule table
request("abu", "/projects/2499", "GET") // fast because only 2 roles in all policies
request("jasmine", "/projects/1", "GET") // really fast at the beginning of the casbin_rule table
request("jasmine", "/projects/2499", "GET") // slow and fails the only 1st time <<<< pb here
request("jasmine", "/projects/2499", "GET") // fast maybe due to internal cache mechanism
// same issue with non-existing roles
// request("jasmine", "/projects/999999", "GET") // slow fails the only 1st time <<<< pb here
// request("jasmine", "/projects/999999", "GET") // fast maybe due to internal cache mechanism
}
강제 적용(Enforcement) 시간이 최대 6초까지 걸릴 수 있습니다.
go test -run ^TestManyRoles$ github.com/casbin/casbin/v3 -v
=== RUN TestManyRoles
rbac_api_test.go:598: RESPONSE abu /projects/1 GET : true IN: 438.379µs
rbac_api_test.go:598: RESPONSE abu /projects/2499 GET : true IN: 39.005173ms
rbac_api_test.go:598: RESPONSE jasmine /projects/1 GET : true IN: 1.774319ms
rbac_api_test.go:598: RESPONSE jasmine /projects/2499 GET : true IN: 6.164071648s
rbac_api_test.go:600: More than 100 milliseconds for jasmine /projects/2499 GET : 6.164071648s
rbac_api_test.go:598: RESPONSE jasmine /projects/2499 GET : true IN: 12.164122ms
--- FAIL: TestManyRoles (6.24s)
FAIL
FAIL github.com/casbin/casbin/v3 6.244s
FAIL
비용이 적게 드는 조건을 먼저 배치하고, 비용이 많이 드는 조건(예: 역할 조회)은 나중에 배치하십시오. 위 예시의 매처 순서를 다음과 같이 변경하면:
[matchers]
m = r.obj == p.obj && g(r.sub, p.sub) && r.act == p.act
go test -run ^TestManyRoles$ github.com/casbin/casbin/v3 -v
=== RUN TestManyRoles
rbac_api_test.go:599: RESPONSE abu /projects/1 GET : true IN: 786.635µs
rbac_api_test.go:599: RESPONSE abu /projects/2499 GET : true IN: 4.933064ms
rbac_api_test.go:599: RESPONSE jasmine /projects/1 GET : true IN: 2.908534ms
rbac_api_test.go:599: RESPONSE jasmine /projects/2499 GET : true IN: 7.292963ms
rbac_api_test.go:599: RESPONSE jasmine /projects/2499 GET : true IN: 6.168307ms
--- PASS: TestManyRoles (0.05s)
PASS
ok github.com/casbin/casbin/v3 0.053s
여러 섹션 유형
r2, p2, e2, m2와 같은 접미사를 사용하여 여러 request, policy, effect 또는 matcher 섹션을 정의할 수 있습니다. 이들은 접미사별로 페어링됩니다: r2는 m2를 통해 p2와 매칭되며 e2와 결합됩니다.
기본 설정이 아닌 세트를 사용하려면 enforce 함수의 첫 번째 인자로 EnforceContext를 전달하십시오. 구조:
EnforceContext{"r2","p2","e2","m2"}
type EnforceContext struct {
RType string
PType string
EType string
MType string
}
const enforceContext = new EnforceContext('r2', 'p2', 'e2', 'm2');
class EnforceContext {
constructor(rType, pType, eType, mType) {
this.pType = pType;
this.eType = eType;
this.mType = mType;
this.rType = rType;
}
}
EnforceContext enforceContext = new EnforceContext("2");
public class EnforceContext {
private String pType;
private String eType;
private String mType;
private String rType;
public EnforceContext(String suffix) {
this.pType = "p" + suffix;
this.eType = "e" + suffix;
this.mType = "m" + suffix;
this.rType = "r" + suffix;
}
}
사용 예시 (참고: model 및 policy 예시):
// Pass in a suffix as a parameter to NewEnforceContext, such as 2 or 3, and it will create r2, p2, etc.
enforceContext := NewEnforceContext("2")
// You can also specify a certain type individually
enforceContext.EType = "e"
// Don't pass in EnforceContext; the default is r, p, e, m
e.Enforce("alice", "data2", "read") // true
// Pass in EnforceContext
e.Enforce(enforceContext, struct{ Age int }{Age: 70}, "/data1", "read") //false
e.Enforce(enforceContext, struct{ Age int }{Age: 30}, "/data1", "read") //true
// Pass in a suffix as a parameter to NewEnforceContext, such as 2 or 3, and it will create r2, p2, etc.
const enforceContext = new NewEnforceContext('2');
// You can also specify a certain type individually
enforceContext.eType = "e"
// Don't pass in EnforceContext; the default is r, p, e, m
e.Enforce("alice", "data2", "read") // true
// Pass in EnforceContext
e.Enforce(enforceContext, {Age: 70}, "/data1", "read") //false
e.Enforce(enforceContext, {Age: 30}, "/data1", "read") //true
// Pass in a suffix as a parameter to NewEnforceContext, such as 2 or 3, and it will create r2, p2, etc.
EnforceContext enforceContext = new EnforceContext("2");
// You can also specify a certain type individually
enforceContext.seteType("e");
// Don't pass in EnforceContext; the default is r, p, e, m
e.enforce("alice", "data2", "read"); // true
// Pass in EnforceContext
// TestEvalRule is located in https://github.com/apache/casbin-jcasbin/blob/master/src/test/java/org/casbin/jcasbin/main/AbacAPIUnitTest.java#L56
e.enforce(enforceContext, new AbacAPIUnitTest.TestEvalRule("alice", 70), "/data1", "read"); // false
e.enforce(enforceContext, new AbacAPIUnitTest.TestEvalRule("alice", 30), "/data1", "read"); // true
특수 문법: in 연산자
in 연산자는 우측 배열에 좌측 값이 포함되어 있는지 확인합니다 (== 동등성 비교 사용, 타입 강제 변환 없음). 좌측에는 모든 값이 올 수 있으며, 우측에는 동일한 타입의 배열이 와야 합니다. Go에서는 배열이 반드시 []interface{}여야 합니다.
참고 예시: rbac_model_matcher_using_in_op, keyget2_model, 및 keyget_model.
예시:
[request_definition]
r = sub, obj
...
[matchers]
m = r.sub.Name in (r.obj.Admins)
e.Enforce(Sub{Name: "alice"}, Obj{Name: "a book", Admins: []interface{}{"alice", "bob"}})
표현식 평가기(Expression Evaluators)
매처는 언어별 표현식 엔진에 의해 평가됩니다. Casbin은 이를 통해 통합된 PERM 구문을 제공합니다. 일부 엔진은 문서화된 구문 이상의 추가 기능을 지원할 수 있으나, 해당 기능은 다른 언어에서 사용 불가능할 수 있습니다. 언어 간 호환성이 필요하다면 문서화된 구문만 사용하십시오.
구현별 표현식 평가기:
| 구현체 | 언어 | 표현식 평가기 |
|---|---|---|
| Casbin | Golang | https://github.com/apache/casbin-govaluate |
| jCasbin | Java | https://github.com/killme2008/aviatorscript |
| Node-Casbin | Node.js | https://github.com/donmccurdy/expression-eval |
| PHP-Casbin | PHP | https://github.com/symfony/expression-language |
| PyCasbin | Python | https://github.com/danthedeckie/simpleeval |
| Casbin.NET | C# | https://github.com/davideicardi/DynamicExpresso |
| Casbin4D | Delphi | https://github.com/casbin4d/Casbin4D/tree/master/SourceCode/Common/Third%20Party/TExpressionParser |
| casbin-rs | Rust | https://github.com/jonathandturner/rhai |
| casbin-cpp | C++ | https://github.com/ArashPartow/exprtk |
강제 적용(Enforcement) 속도가 느린 경우, 표현식 평가기가 병목 현상의 원인인 경우가 많습니다. Benchmarks 페이지를 참고하고 Casbin 또는 평가기 프로젝트에 이슈를 보고하는 것을 고려하십시오.
Comments are powered by giscus and GitHub Discussions. They load from giscus.app and GitHub only after you click the button below.