Casbin
공식 문서: 개요 | Casbin
TIP
이 문서는 Casbin 입문 기사로, 더 자세히 알고 싶다면 공식 웹사이트를 방문하여 학습하시기 바랍니다.
소개
시스템에서 백엔드 개발자는 API 권한 관리를 책임져야 하며, 이는 많은 작업이 필요합니다. 만약 모든 프로젝트마다 직접 작성해야 한다면 많은 시간이 낭비될 것입니다. 더 많은 인력과 물자를 가진 대기업은 자체 권한 프레임워크를 개발하는 경향이 있지만, 대부분의 중소 기업은 이러한 개발 비용을 감당할 수 없으므로 시중의 오픈소스 권한 프레임워크가 그들의 첫 번째 선택이 됩니다. Casbin 은 바로 그러한 고효율 오픈소스 접근 제어 라이브러리로, Go 언어로 개발되었으며 다른 주요 언어도 지원합니다.
Casbin 은 접근 제어 프레임워크일 뿐이며 접근 제어만 책임지고 접근 인증 로직은 Casbin 이 책임지지 않는다는 점에 유의해야 합니다. Casbin 은 사용자와 역할 간의 매핑 관계만 저장합니다. 다음 접근 제어 모델을 지원합니다:
- ACL (Access Control List, 접근 제어 목록)
- 슈퍼사용자가 있는 ACL
- 사용자가 없는 ACL: 인증이나 사용자 로그인이 없는 시스템에 특히 유용합니다.
- 리소스가 없는 ACL: 특정 리소스가 아닌 리소스 유형만 대상으로 하는 경우가 있습니다. 예를 들어
write-article,read-log등의 권한입니다. 특정 기사나 로그에 대한 접근을 제어하지 않습니다. - RBAC (역할 기반 접근 제어)
- 리소스 역할을 지원하는 RBAC: 사용자와 리소스는 모두 역할 (또는 그룹) 을 가질 수 있습니다.
- 도메인/테넌트를 지원하는 RBAC: 사용자는 다른 도메인/테넌트에 대해 다른 역할 세트를 가질 수 있습니다.
- ABAC (속성 기반 접근 제어):
resource.Owner와 같은 구문을 사용하여 요소의 속성을 가져오는 것을 지원합니다. - RESTful:
/res/*,/res/:id와 같은 경로 및GET,POST,PUT,DELETE와 같은 HTTP 메서드를 지원합니다. - 거부 우선: 허용 및 거부 승인을 지원하며, 거부가 허용보다 우선합니다.
- 우선순위: 정책 규칙은 우선순위를 결정하기 위해 순서대로 적용되며, 방화벽 규칙과 유사합니다.
작동 원리
Casbin 에서 접근 제어 모델은 PERM 기반 구성 파일로 추상화되며, PERM 은 Policy(정책), Effect(효과), Request(요청), Matcher(매칭) 를 의미합니다. 프로젝트에서 권한 부여 메커니즘을 수정할 때 구성 파일만 간단히 수정하면 됩니다. 일반적인 Model 구성 파일 내용은 다음과 같습니다:
[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이는 가장 간단한 ACL 접근 제어 모델입니다.
정책
구성 파일에서 정책 정의 부분은 다음과 같습니다.
[policy_definition]
p = sub, obj, actp는 policy 를 의미하며 다른 문자로 대체할 수 없습니다. sub 는 subject 로 정책 주체, obj 는 object 로 정책 대상, act 는 action 으로 행위를 의미합니다.
p = sub, obj, act네 번째 필드 eft 가 있을 수도 있으며, 생략 시 기본적으로 eft 는 allow 입니다.
p=sub, obj, act, eft이 줄은 policy 를 어떻게 작성하는지 설명할 뿐 실제 정책 정의는 아닙니다. 다음은 구체적인 policy 예제입니다.
p, jojo, cake, eatp 는 정책 규칙 정의임을 나타내며, jojo 는 정책 주체, cake 는 정책 대상, eat 은 행위입니다. 전체 의미는 주체 jojo 가 대상 cake 에 대해 행위 eat 을 할 수 있다는 것입니다. 구체적인 정책 규칙은 모델 파일에 나타나지 않으며, 전용 policy 파일이나 데이터베이스에 저장됩니다.
요청
구성 파일에서 요청 정의 부분은 다음과 같습니다.
[request_definition]
r = sub, obj, actr은 request 를 의미하며 다른 문자로 대체할 수 없습니다. sub 는 subject 로 요청 주체, obj 는 object 로 요청 대상, act 는 action 으로 요청 행위를 의미합니다. 일반적으로 요청 정의와 정책 정의의 필드 이름은 일치합니다. 요청 부분은 casbin 이 책임지지 않으며, 개발자가 무엇이 요청 주체이고 요청 대상인지를 결정하고, casbin 은 전달된 필드에 따라 접근 제어를 수행하기만 하면 됩니다.
매칭
구성 파일에서 매칭 정의 부분은 다음과 같습니다.
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.actm은 matcher 를 의미하며 다른 문자로 대체할 수 없으며, 그 뒤에는 해당 매칭 규칙이 옵니다. 위는 간단한 부울 표현식으로, 전달된 요청의 모든 필드가 정책 규칙의 필드와 모두 일치하면 매칭된다는 의미입니다. 물론 와일드카드나 표현력이 더 강한 정규 표현식일 수도 있습니다.
그 외에도 matcher 는 in 구문을 지원합니다. 예를 들어
[matchers]
m = r.sub in ("root","admin")또는
[matchers]
m = r.sub.Name in (r.obj.Admins)e.Enforce(Sub{Name: "alice"}, Obj{Name: "a book", Admins: []interface{}{"alice", "bob"}})매칭 시 Casbin 은 타입 검사를 수행하지 않고 interface 로 == 검사를 통해 동일한지 확인합니다.
효과
효과 정의 부분은 매칭 결과에 대해 다시 논리 조합 판단을 수행합니다. 구성 파일에서 효과 정의 부분은 다음과 같습니다.
[policy_effect]
e = some(where (p.eft == allow))e는 effect 를 의미하며 다른 문자로 대체할 수 없습니다. some 양화사는 매칭기를 만족하는 정책 규칙이 하나라도 있는지 판단합니다. any 양화사는 모든 정책 규칙이 매칭기를 만족하는지 판단합니다.
some(where (p.eft == allow))이 규칙은 매칭 결과에서 하나의 결과가 allow 이면 최종 결과가 allow 임을 의미합니다.
e = !some(where (p.eft == deny))이 규칙은 매칭 결과에서 deny 결과가 존재하지 않으면 최종 결과가 allow 임을 의미합니다.
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))이 규칙은 매칭 결과에서 하나가 allow 이고 deny 결과가 존재하지 않으면 최종 결과가 allow 임을 의미합니다.
Casbin 은 위와 같은 정책 효과 구문을 설계했지만 현재 실행은 하드코딩된 정책 효과를 사용할 뿐입니다. 그들은 이러한 유연성이 그다지 필요하지 않다고 생각합니다. 현재까지는 내장 정책 효과를 사용해야 하며 사용자 정의할 수 없습니다. 내장 지원 정책 효과는 다음과 같습니다.
| Policy effect 정의 | 의미 | 예시 |
|---|---|---|
| some(where (p.eft == allow)) | allow-override | ACL, RBAC 등 |
| !some(where (p.eft == deny)) | deny-override | 거부 재작성 |
| some(where (p.eft == allow)) && !some(where (p.eft == deny)) | allow-and-deny | 동의와 거부 |
| priority(p.eft) || deny | priority | 우선순위 |
| subjectPriority(p.eft) | 역할 기반 우선순위 | 주제 우선순위 |
TIP
위 네 가지 정의는 모두 여러 개 정의할 수 있으며, 구문은
type+number로, 예를 들어r2,p2,e2,m2입니다.모델 파일에는 주석이 있을 수 있으며,
#기호로 주석을 답니다.
예제
다음은 모델 파일의 작동 과정을 보여주는 예제입니다. 먼저 다음과 같은 간단한 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.actPolicy 파일은 다음과 같습니다.
p, alice, data1, read
p, bob, data2, write주체, 대상, 행위를 어떻게 추상화하는지는 비즈니스 로직에 의해 결정되며, 여기서는 중요하지 않으므로 생략합니다. 아래에 가장 간단한 방식으로 전달된 요청을 보여줍니다.
alice, data1, read
bob, data1, read
alice, data2, write
bob, data2, writepolicy 파일에서 alice 는 data1 에 대해 read 작업을 수행할 권한이 있고, bob 은 data2 에 대해 write 작업을 수행할 권한이 있다고 정의합니다. 그러면 전달된 요청에서
alice, data1, read는 alice 가 data1 에 대해 read 작업을 수행하려는 것을 나타내며,
bob, data1, read는 bob 이 data1 에 대해 read 작업을 수행하려는 것을 나타냅니다. 나머지도 마찬가지입니다. 그러면 최종 결과는 다음과 같습니다.
true
false
false
true이는 가장 간단한 ACL 예제이며, Casbin 공식 웹사이트에서는 온라인 편집 및 테스트 예제를 제공할 수 있습니다. Casbin editor 에서 테스트해 볼 수 있습니다.
RBAC
RBAC(Role-Based-Access-Controll) 은 역할 기반 접근 제어로, ACL 모델보다 [role definition] 이 하나 더 많습니다. 다음은 간단한 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여기서 역할 정의는 다음과 같습니다.
[role_definition]
g = _, _g 는 group 을 의미하며 다른 문자로 대체할 수 없으며, type+number 방식으로 여러 개 생성할 수 있고, _ 는 플레이스홀더로 몇 개의 입력 매개변수가 있는지를 나타냅니다. 일반적으로 Policy 에서 g 는 다음과 같은 형식입니다.
g, alice, data2_admin
g, mike, data1_admin
g, data1_admin data2_adminalice 는 주체를, data2_admin 은 역할을 의미하며, 엄밀히 말하면 casbin 은 모두 문자열로 간주하며, 어떻게 이해하고 사용하는지는 개발자에 따라 다릅니다.
g, alice, data2_admin는 alice 가 data2_admin 역할을 가짐을 나타냅니다.
g, mike, data1_admin는 mike 가 data1_admin 역할을 가짐을 나타냅니다.
g, data1_admin data2_admin는 역할 data1_admin 이 역할 data2_admin 역할을 가짐을 나타내며, 이는 역할 간의 상속 관계입니다.
리소스 역할 모델
리소스 역할 모델은 g2 를 새로 추가하여 리소스 역할 정의로 사용합니다. 모델 정의는 다음과 같습니다.
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
g2 = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.actPolicy 예제 정의는 다음과 같습니다.
p, alice, data1, read
p, bob, data2, write
p, data_group_admin, data_group, write
g, alice, data_group_admin
g2, data1, data_group
g2, data2, data_group여기서 g2 는 리소스 역할 그룹을 정의하며, 리소스를 다른 역할에 부여하고 동시에 사용자 역할과 리소스 역할 간의 사용자 관계를 규정합니다.
p, data_group_admin, data_group, write이 정책은 data_group_admin 역할을 가진 사용자가 data_group 역할을 가진 리소스에 대해 쓰기 작업을 수행할 수 있음을 정의합니다.
멀티 테넌트 도메인 모델
[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멀티 테넌트 도메인 모델은 전통적인 RBAC 모델보다 dom 필드가 더 많으며, 주체가 속한 도메인을 나타내는 데 사용됩니다. Policy 예제는 다음과 같습니다.
p, admin, domain1, data1, read
p, admin, domain1, data1, write
p, admin, domain2, data2, read
p, admin, domain2, data2, write
g, alice, admin, domain1
g, bob, admin, domain2예를 들어
p, admin, domain1, data1, read는 domain1 에 속한 주체 admin 이 data1 에 대해 read 작업을 수행할 권한이 있음을 정의합니다.
g, alice, admin, domain1는 alice 가 domain1 에 속하며 admin 역할을 가짐을 정의합니다.
ABAC
ABAC(Attribute-Based Access Control) 는 속성 기반 접근 제어로, 리소스 소유자나 기타 속성을 기반으로 접근 제어를 수행합니다. ABAC 모델은 policy_definition 에서 리소스 속성을 사용하며, matcher 에서 리소스 속성을 가져와 비교합니다. 다음은 간단한 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 == p.sub && r.obj.Owner == r.subPolicy 예제는 다음과 같습니다.
p, alice, /data1
p, bob, /data2ABAC 에서 리소스는 속성을 가질 수 있으며, 예를 들어 리소스 data1 의 소유자가 alice 라고 가정합니다. 그러면 matcher 는 r.obj.Owner == r.sub을 통해 리소스 소유자가 요청 주체와 일치하는지 확인합니다.
Go 코드에서 ABAC 를 사용하는 예제는 다음과 같습니다.
package main
import (
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
)
func main() {
text := `
[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.Owner == r.sub
`
m, _ := model.NewModelFromString(text)
e, _ := casbin.NewEnforcer(m)
e.AddPolicy("alice", "/data1")
e.AddPolicy("bob", "/data2")
// 리소스 속성 확인
sub := "alice"
obj := map[string]interface{}{"Owner": "alice"}
act := "read"
allowed, _ := e.Enforce(sub, obj, act)
println(allowed) // true
}사용법
Go 에서 사용
Casbin 을 Go 에서 사용하려면 먼저 의존성을 설치합니다.
go get github.com/casbin/casbin/v2그런 다음 다음과 같이 사용합니다.
package main
import (
"github.com/casbin/casbin/v2"
)
func main() {
// enforcer 생성, 첫 번째 매개변수는 모델 파일, 두 번째 매개변수는 policy 파일
e, _ := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")
// 권한 확인
sub := "alice" // 권한 주체
obj := "data1" // 권한 객체
act := "read" // 권한 행동
if ok, _ := e.Enforce(sub, obj, act); ok {
println("권한 있음")
} else {
println("권한 없음")
}
}Policy 저장소
Casbin 은 다양한 policy 저장소를 지원합니다.
- 파일 저장소: CSV 파일을 policy 저장소로 사용
- 데이터베이스 저장소: MySQL, PostgreSQL, SQL Server, Oracle 등
- NoSQL 저장소: MongoDB, Redis 등
- 클라우드 저장소: AWS S3, Azure Blob Storage 등
기본적으로 Casbin 은 CSV 파일을 policy 저장소로 사용하지만, 어댑터를 통해 다른 저장소를 사용할 수 있습니다.
어댑터
Casbin 은 다양한 어댑터를 제공하여 policy 를 다른 저장소에 저장할 수 있습니다.
import (
"github.com/casbin/casbin/v2"
gormadapter "github.com/casbin/gorm-adapter/v3"
)
func main() {
// GORM 어댑터 사용
a, _ := gormadapter.NewAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/")
e, _ := casbin.NewEnforcer("path/to/model.conf", a)
// 권한 확인
e.Enforce("alice", "data1", "read")
}Watcher
Casbin 은 watcher 를 통해 policy 변경을 감지하고 여러 인스턴스 간 policy 동기화를 지원합니다.
import (
"github.com/casbin/casbin/v2"
"github.com/casbin/redis-watcher/v2"
)
func main() {
e, _ := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")
// Redis watcher 생성
w, _ := rediswatcher.NewWatcher("127.0.0.1:6379")
// watcher 설정
e.SetWatcher(w)
// policy 변경 시 다른 인스턴스에 알림
e.SavePolicy()
}모델 예제
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.actRBAC 모델
[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.actRBAC + 도메인 모델
[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.actABAC 모델
[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.Owner == r.subRESTful 모델
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)결론
Casbin 은 Go 로 구축된 고도로 커스터마이징 가능한 접근 제어 라이브러리로, ACL, RBAC, ABAC 등 다양한 접근 제어 모델을 지원합니다. 구성 파일을 통해 접근 제어 모델을 쉽게 변경할 수 있으며, 다양한 저장소와 어댑터를 지원합니다.
자세한 내용은 공식 문서 를 참조하시기 바랍니다.
