Skip to content

Casbin

공식 저장소: casbin/casbin: An authorization library that supports access control models like ACL, RBAC, ABAC in Golang (github.com)

공식 문서: 개요 | Casbin

TIP

이 문서는 Casbin 입문 기사로, 더 자세히 알고 싶다면 공식 웹사이트를 방문하여 학습하시기 바랍니다.

소개

시스템에서 백엔드 개발자는 API 권한 관리를 책임져야 하며, 이는 많은 작업이 필요합니다. 만약 모든 프로젝트마다 직접 작성해야 한다면 많은 시간이 낭비될 것입니다. 더 많은 인력과 물자를 가진 대기업은 자체 권한 프레임워크를 개발하는 경향이 있지만, 대부분의 중소 기업은 이러한 개발 비용을 감당할 수 없으므로 시중의 오픈소스 권한 프레임워크가 그들의 첫 번째 선택이 됩니다. Casbin 은 바로 그러한 고효율 오픈소스 접근 제어 라이브러리로, Go 언어로 개발되었으며 다른 주요 언어도 지원합니다.

Casbin 은 접근 제어 프레임워크일 뿐이며 접근 제어만 책임지고 접근 인증 로직은 Casbin 이 책임지지 않는다는 점에 유의해야 합니다. Casbin 은 사용자와 역할 간의 매핑 관계만 저장합니다. 다음 접근 제어 모델을 지원합니다:

  1. ACL (Access Control List, 접근 제어 목록)
  2. 슈퍼사용자가 있는 ACL
  3. 사용자가 없는 ACL: 인증이나 사용자 로그인이 없는 시스템에 특히 유용합니다.
  4. 리소스가 없는 ACL: 특정 리소스가 아닌 리소스 유형만 대상으로 하는 경우가 있습니다. 예를 들어 write-article, read-log 등의 권한입니다. 특정 기사나 로그에 대한 접근을 제어하지 않습니다.
  5. RBAC (역할 기반 접근 제어)
  6. 리소스 역할을 지원하는 RBAC: 사용자와 리소스는 모두 역할 (또는 그룹) 을 가질 수 있습니다.
  7. 도메인/테넌트를 지원하는 RBAC: 사용자는 다른 도메인/테넌트에 대해 다른 역할 세트를 가질 수 있습니다.
  8. ABAC (속성 기반 접근 제어): resource.Owner 와 같은 구문을 사용하여 요소의 속성을 가져오는 것을 지원합니다.
  9. RESTful: /res/*, /res/:id 와 같은 경로 및 GET, POST, PUT, DELETE 와 같은 HTTP 메서드를 지원합니다.
  10. 거부 우선: 허용 및 거부 승인을 지원하며, 거부가 허용보다 우선합니다.
  11. 우선순위: 정책 규칙은 우선순위를 결정하기 위해 순서대로 적용되며, 방화벽 규칙과 유사합니다.

작동 원리

Casbin 에서 접근 제어 모델은 PERM 기반 구성 파일로 추상화되며, PERM 은 Policy(정책), Effect(효과), Request(요청), Matcher(매칭) 를 의미합니다. 프로젝트에서 권한 부여 메커니즘을 수정할 때 구성 파일만 간단히 수정하면 됩니다. 일반적인 Model 구성 파일 내용은 다음과 같습니다:

bash
[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, act

p는 policy 를 의미하며 다른 문자로 대체할 수 없습니다. sub 는 subject 로 정책 주체, obj 는 object 로 정책 대상, act 는 action 으로 행위를 의미합니다.

p = sub, obj, act

네 번째 필드 eft 가 있을 수도 있으며, 생략 시 기본적으로 eft 는 allow 입니다.

p=sub, obj, act, eft

이 줄은 policy 를 어떻게 작성하는지 설명할 뿐 실제 정책 정의는 아닙니다. 다음은 구체적인 policy 예제입니다.

p, jojo, cake, eat

p 는 정책 규칙 정의임을 나타내며, jojo 는 정책 주체, cake 는 정책 대상, eat 은 행위입니다. 전체 의미는 주체 jojo 가 대상 cake 에 대해 행위 eat 을 할 수 있다는 것입니다. 구체적인 정책 규칙은 모델 파일에 나타나지 않으며, 전용 policy 파일이나 데이터베이스에 저장됩니다.

요청

구성 파일에서 요청 정의 부분은 다음과 같습니다.

[request_definition]
r = sub, obj, act

r은 request 를 의미하며 다른 문자로 대체할 수 없습니다. sub 는 subject 로 요청 주체, obj 는 object 로 요청 대상, act 는 action 으로 요청 행위를 의미합니다. 일반적으로 요청 정의와 정책 정의의 필드 이름은 일치합니다. 요청 부분은 casbin 이 책임지지 않으며, 개발자가 무엇이 요청 주체이고 요청 대상인지를 결정하고, casbin 은 전달된 필드에 따라 접근 제어를 수행하기만 하면 됩니다.

매칭

구성 파일에서 매칭 정의 부분은 다음과 같습니다.

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

m은 matcher 를 의미하며 다른 문자로 대체할 수 없으며, 그 뒤에는 해당 매칭 규칙이 옵니다. 위는 간단한 부울 표현식으로, 전달된 요청의 모든 필드가 정책 규칙의 필드와 모두 일치하면 매칭된다는 의미입니다. 물론 와일드카드나 표현력이 더 강한 정규 표현식일 수도 있습니다.

그 외에도 matcher 는 in 구문을 지원합니다. 예를 들어

[matchers]
m = r.sub in ("root","admin")

또는

[matchers]
m = r.sub.Name in (r.obj.Admins)
go
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-overrideACL, RBAC 등
!some(where (p.eft == deny))deny-override거부 재작성
some(where (p.eft == allow)) && !some(where (p.eft == deny))allow-and-deny동의와 거부
priority(p.eft) || denypriority우선순위
subjectPriority(p.eft)역할 기반 우선순위주제 우선순위

TIP

  1. 위 네 가지 정의는 모두 여러 개 정의할 수 있으며, 구문은 type+number로, 예를 들어 r2,p2,e2,m2입니다.

  2. 모델 파일에는 주석이 있을 수 있으며, # 기호로 주석을 답니다.

예제

다음은 모델 파일의 작동 과정을 보여주는 예제입니다. 먼저 다음과 같은 간단한 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

Policy 파일은 다음과 같습니다.

p, alice, data1, read
p, bob, data2, write

주체, 대상, 행위를 어떻게 추상화하는지는 비즈니스 로직에 의해 결정되며, 여기서는 중요하지 않으므로 생략합니다. 아래에 가장 간단한 방식으로 전달된 요청을 보여줍니다.

alice, data1, read
bob, data1, read
alice, data2, write
bob, data2, write

policy 파일에서 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_admin

alice 는 주체를, 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.act

Policy 예제 정의는 다음과 같습니다.

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.sub

Policy 예제는 다음과 같습니다.

p, alice, /data1
p, bob, /data2

ABAC 에서 리소스는 속성을 가질 수 있으며, 예를 들어 리소스 data1 의 소유자가 alice 라고 가정합니다. 그러면 matcher 는 r.obj.Owner == r.sub을 통해 리소스 소유자가 요청 주체와 일치하는지 확인합니다.

Go 코드에서 ABAC 를 사용하는 예제는 다음과 같습니다.

go
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 에서 사용하려면 먼저 의존성을 설치합니다.

sh
go get github.com/casbin/casbin/v2

그런 다음 다음과 같이 사용합니다.

go
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 저장소를 지원합니다.

  1. 파일 저장소: CSV 파일을 policy 저장소로 사용
  2. 데이터베이스 저장소: MySQL, PostgreSQL, SQL Server, Oracle 등
  3. NoSQL 저장소: MongoDB, Redis 등
  4. 클라우드 저장소: AWS S3, Azure Blob Storage 등

기본적으로 Casbin 은 CSV 파일을 policy 저장소로 사용하지만, 어댑터를 통해 다른 저장소를 사용할 수 있습니다.

어댑터

Casbin 은 다양한 어댑터를 제공하여 policy 를 다른 저장소에 저장할 수 있습니다.

go
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 동기화를 지원합니다.

go
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.act

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

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

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.sub

RESTful 모델

[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 등 다양한 접근 제어 모델을 지원합니다. 구성 파일을 통해 접근 제어 모델을 쉽게 변경할 수 있으며, 다양한 저장소와 어댑터를 지원합니다.

자세한 내용은 공식 문서 를 참조하시기 바랍니다.

Golang by www.golangdev.cn edit