애플리케이션에서 권한 관리는 보안의 핵심 요소 중 하나다.
예를 들어, 새로운 리소스를 생성할 때 누가 이 리소스를 소유하는지, 누가 읽거나 수정할 수 있는지를 명확하게 정의하고 검증해야 한다.
하지만 권한 요구 사항이 복잡해질수록, 기존 RDBMS 기반의 접근 방식으로 이를 처리하는 것은 점점 더 어려워진다.
일반적으로 RDB에서는 계층 구조를 표현하기 위해 부모 ID를 사용하지만, 시간이 지남에 따라 다음과 같은 문제가 발생할 수 있다.
- 풀 스캔(Full Scan) 발생
- 복잡한 조인으로 인한 성능 저하
- 비즈니스 로직과 데이터 구조의 괴리로 인해 유지보수 난이도 증가
이러한 문제를 해결하기 위해 등장한 것이 바로 SpiceDB다.
SpiceDB는 권한 관리를 위해 설계된 특화된 데이터베이스로, 복잡한 권한 요구 사항을 효과적으로 처리할 수 있는 강력한 기능을 제공한다.
특히, 관계(Relationship) 모델을활용하여 권한 구조를 직관적이고 효율적으로 표현할 수 있다.
이제 간단한 예제를 통해 SpiceDB에서 스키마를 정의하고 질의하는 방법, 그리고 코틀린 환경에서 애플리케이션에 적용하는 방법을 소개하겠다.
실습 시나리오: 그룹과 티켓의 권한 모델링
SpiceDB의 개념을 효과적으로 이해하기 위해 약간 복잡한 권한 요구 사항을 정의했다.
이제 이 요구 사항을 기반으로 SpiceDB에서 모델링을 진행할 것이므로, 내용을 잘 숙지하면 실습이 더욱 원활할 것이다.
그룹(Group)과 사용자(Member)
- 여러 개의 그룹이 존재하며, 각 그룹에는 사용자가 포함될 수 있다.
티켓(Ticket) 권한 규칙
- 사용자는 티켓을 작성할 수 있다.
- 티켓 작성자는 자신이 작성한 티켓에 대한 읽기(READ) 및 쓰기(WRITE) 권한을 가진다.
그룹 내 사용자 권한
- 같은 그룹에 속한 사용자는 해당 그룹 내 티켓에 대해 읽기(READ) 권한을 가진다.
- 그룹의 호스트(Host)는 해당 그룹 내 티켓에 대해 읽기(READ) 및 쓰기(WRITE) 권한을 가진다.
그룹 간 권한 제한
- 다른 그룹에 속한 사용자는 해당 그룹의 티켓에 대해 읽기(READ) 또는 쓰기(WRITE) 권한이 없다.
상위 그룹 개념
- 그룹은 계층 구조를 가질 수 있다.
- 상위 그룹에 속한 사용자는 하위 그룹의 티켓을 읽거나 쓸 수 있는 권한을 가진다.
이제 이 요구 사항을 기반으로 SpiceDB에서 권한 모델을 정의하고 실습을 진행해보자.
그러면 SpiceDB의 핵심 개념과 사용법을 익힐 수 있을 것이다.
SpiceDB 스키마 생성
SpiceDB에서는 zed CLI를 제공하여 쉽게 스키마를 정의하고 적용할 수 있다.
우선, zed CLI를 설치하고, 로컬에서 SpiceDB를 실행하는 방법을 살펴보자.
1. zed CLI 설치
zed CLI는 공식 문서에서 안내하는 방법으로 설치할 수 있다.
https://authzed.com/docs/spicedb/getting-started/installing-zed
Installing Zed
Welcome to the SpiceDB and AuthZed docs site.
authzed.com
Mac 사용자는 brew를 이용해 간편하게 설치할 수 있다.
brew install authzed/tap/spicedb authzed/tap/zed
설치 후, SpiceDB와 통신할 수 있도록 context를 설정해야 한다.
환경에 맞게 아래 명령어를 실행하면 된다.
zed context set $name localhost:50051 $key --insecure
2. SpiceDB 로컬 실행 (Docker Compose 사용)
SpiceDB를 로컬에서 실행하는 방법은 여러 가지가 있지만, 여기서는 Docker Compose를 이용하겠다.
아래 docker-compose.yml을 사용하여 PostgreSQL과 함께 실행할 수 있다.
services:
database:
image: postgres:15.2
ports:
- '5432'
environment:
- 'POSTGRES_USER=wolfdesk'
- 'POSTGRES_DB=wolfdesk'
- 'POSTGRES_PASSWORD=wolfdesk'
spicedb:
image: authzed/spicedb:v1.39.1
depends_on:
migrate:
condition: service_completed_successfully
ports:
- "50051:50051"
command: serve
environment:
TZ: "Asia/Seoul"
SPICEDB_GRPC_ENABLED: "true"
SPICEDB_GRPC_ADDR: ":50051"
SPICEDB_GRPC_PRESHARED_KEY: "wolfdesk"
SPICEDB_DATASTORE_ENGINE: "postgres"
SPICEDB_DATASTORE_CONN_URI: "postgres://wolfdesk:wolfdesk@database:5432/wolfdesk?sslmode=disable"
migrate:
image: authzed/spicedb:v1.39.1
restart: on-failure
depends_on:
- database
command: migrate head
environment:
TZ: "Asia/Seoul"
SPICEDB_DATASTORE_ENGINE: "postgres"
SPICEDB_DATASTORE_CONN_URI: "postgres://wolfdesk:wolfdesk@database:5432/wolfdesk?sslmode=disable"
SpiceDB의 지원 스토리지 엔진
SpiceDB는 다양한 데이터베이스 엔진을 지원한다.
PostgreSQL이 권장되므로, 여기서도 PostgreSQL을 사용한다.
- CockroachDB → 다중 리전 배포에 적합
- Cloud Spanner → Google Cloud 배포에 적합
- PostgreSQL → 권장 (단일 리전 배포에 적합)
- MySQL → 비추천 (PostgreSQL을 사용할 수 없는 경우에만)
- memdb → 로컬 개발 및 테스트 용도
3. SpiceDB 스키마 정의
이제 앞서 정리한 시나리오를 기반으로 SpiceDB 스키마를 작성해보자.
3-1. 리소스 정의
definition member {}
definition group {
relation parent: group
relation host: member
relation member: member
permission write = host + parent + parent->member + parent->write
permission read = write + member
}
definition ticket {
relation owner: member
relation group: group
permission write = owner + group->write
permission read = write + group->member
}
익숙한 SQL과는 다소 다른 문법이지만, 구조를 살펴보면 이해하기 쉽다.
3-2. 리소스 설명
✅ member (사용자)
- 서비스를 사용하는 사용자를 나타낸다.
- 별다른 추가 요구 사항이 없으므로 간단하게 정의한다.
✅ group (그룹)
- 여러 사용자를 포함하는 그룹을 나타낸다.
- 상위 그룹을 표현하기 위해 parent 관계를 추가했다.
- 그룹의 관리자를 나타내기 위해 host 관계를 선언했다.
- 그룹에 속한 멤버를 표현하기 위해 member 관계를 정의했다.
✅ group 권한 (permission)
SpiceDB에서는 권한을 별도로 부여하는 것이 아니라, 관계를 통해 권한이 자동으로 적용된다.
- 쓰기(WRITE) 권한
✅ 그룹의 host (관리자)
✅ 부모 그룹의 member
✅ 부모 그룹의 WRITE 권한이 있는 사람 - 읽기(READ) 권한
✅ WRITE 권한이 있는 사람
✅ 이 그룹의 일반 member
'→' parent->write 와 같은 화살표 문법을 통해 관계를 손쉽게 정의할 수 있다.
✅ ticket (티켓)
- 사용자가 작성하는 티켓을 나타낸다.
- 티켓의 소유자를 정의하기 위해 owner 관계를 선언했다.
- 티켓이 어떤 그룹에 속해 있는지를 정의하기 위해 group 관계를 선언했다.
✅ ticket 권한 (permission)
- 쓰기(WRITE) 권한
✅ 티켓의 owner (작성자)
✅ 티켓이 속한 그룹의 WRITE 권한이 있는 사용자 - 읽기(READ) 권한
✅ WRITE 권한이 있는 사용자
✅ 티켓이 속한 그룹의 일반 member
3-3. SpiceDB 스키마 적용
# 스키마 파일 작성
vi schema.zed
# 스키마 작성
definition member {}
definition group {
relation parent: group
relation host: member
relation member: member
permission write = host + parent + parent->member + parent->write
permission read = write + member
}
definition ticket {
relation owner: member
relation group: group
permission write = owner + group->write
permission read = write + group->member
}
# Esc → :wq 저장
# 스키마 적용
zed schema write schema.zed
이제 스키마가 적용되었으며, 이후에는 이를 기반으로 데이터와 권한을 관리할 수 있다.
관계(Relationship) 정의 및 적용
이제 SpiceDB에 데이터를 추가하고, 리소스 간 관계를 정의하면서 실습을 진행해보자.
SpiceDB에서는 zed CLI를 사용하여 관계를 생성할 수 있다.
관계 정의 (Relationship Definition)
SpiceDB에서 관계를 정의하는 규칙
zed relationship create <리소스이름>:<리소스ID> <관계> <리소스이름>:<리소스ID>
- 관계는 스키마에서 정의된 리소스만 사용 가능하다.
- 관계를 통해 사용자(member), 그룹(group), 티켓(ticket) 등의 리소스를 연결할 수 있다.
관계 생성 (Create Relationships)
기본 그룹 관계 설정
아래 명령어를 실행하여 그룹과 사용자 간 관계를 설정한다.
zed relationship create group:team member member:alice # (1)
zed relationship create group:team host member:alice # (2)
zed relationship create group:team member member:bob # (3)
zed relationship create group:team member member:charlie # (4)
📌 설명
- (1) 그룹 team에 속한 멤버: alice
- (2) 그룹 team의 관리자(host): alice
- (3) 그룹 team에 속한 멤버: bob
- (4) 그룹 team에 속한 멤버: charlie
💡 그룹 team의 관리자(host)는 alice이며, alice, bob, charlie가 팀의 멤버(member)이다.
그룹 계층 구조 설정
SpiceDB에서는 그룹 간 계층을 정의할 수 있다.
아래 명령어를 실행하여 상위 그룹을 설정하자.
zed relationship create group:team parent group:super_team # (5)
zed relationship create group:super_team parent group:admin_team # (6)
zed relationship create group:super_team host member:dog # (7)
zed relationship create group:super_team member member:dog
zed relationship create group:admin_team host member:admin # (8)
zed relationship create group:admin_team member member:admin
📌 설명
- (5) team 그룹의 상위 그룹은 super_team
- (6) super_team 그룹의 상위 그룹은 admin_team
- (7) 그룹 super_team의 관리자는 dog
- (8) 그룹 admin_team의 관리자는 admin
💡 즉, 그룹 team ⬆ super_team ⬆ admin_team 순으로 계층이 만들어지고,
super_team을 관리하는 사용자는 dog, admin_team을 관리하는 사용자는 admin이다.
티켓(Ticket) 관계 정의
티켓과 사용자, 그룹 간 관계를 생성해보자.
zed relationship create ticket:1000 owner member:alice # (9)
zed relationship create ticket:1000 group group:team # (10)
📌 설명
- (9) ticket:1000의 소유자는 alice
- (10) ticket:1000이 속한 그룹은 team
💡 티켓 1000은 alice가 작성했으며, team 그룹에 속해 있다.
이제 생성한 관계를 도식화하면 다음과 같다.

- admin_team은 최상위 그룹이며, 관리자는 admin이다.
- super_team은 중간 그룹이며, 관리자는 dog이다.
- team은 하위 그룹이며, 관리자는 alice, 멤버는 alice, bob, charlie이다.
- 티켓 1000은 alice가 작성했으며, team 그룹에 속한다.
권한 검증 (Permission Check)
이제 앞서 정의한 관계를 기반으로 권한이 정상적으로 동작하는지 검증해보자.
SpiceDB에서는 zed permission check 명령어를 사용하여 특정 사용자가 특정 리소스에 대한 권한을 가지고 있는지 확인할 수 있다.
티켓(Ticket) 접근 권한 확인
zed permission check <리소스>:<리소스ID> <권한> <리소스>:<리소스ID>
1000번 티켓에 대한 권한 확인
zed permission check ticket:1000 write member:alice # ✅ true
zed permission check ticket:1000 read member:alice # ✅ true
zed permission check ticket:1000 write member:bob # ❌ false
zed permission check ticket:1000 read member:bob # ✅ true
zed permission check ticket:1000 write member:charlie # ❌ false
zed permission check ticket:1000 read member:charlie # ✅ true
📌 결과
- alice → ticket:1000의 소유자(owner) 이므로 읽기(READ)와 쓰기(WRITE) 권한 모두 가짐 ✅
- bob, charlie → team 그룹의 멤버(member) 이므로 읽기(READ) 권한만 있음 ✅, 쓰기(WRITE) 권한 없음 ❌
💡 즉, team 그룹에 속한 멤버(bob, charlie)는 ticket:1000을 읽을 수 있지만, 오직 alice만 쓸 수 있다.
상위 그룹의 사용자 권한 확인
그렇다면 상위 그룹(super_team, admin_team)에 속한 사용자인 dog과 admin도 ticket:1000을 읽고 쓸 수 있을까?
zed permission check ticket:1000 write member:dog # ✅ true
zed permission check ticket:1000 read member:dog # ✅ true
zed permission check ticket:1000 write member:admin # ✅ true
zed permission check ticket:1000 read member:admin # ✅ true
📌 결과 해석
- dog → super_team의 관리자(host) 이므로 team의 모든 티켓에 대한 읽기/쓰기 권한을 상속받음 ✅
- admin → admin_team의 관리자(host) 이므로 super_team을 통해 team의 티켓에도 접근 가능 ✅
💡 team의 상위 그룹(super_team, admin_team)에 속한 사용자(dog, admin)도 ticket:1000을 읽고 쓸수 있다.
SpiceDB의 관계 기반 권한 시스템을 활용하면, 이런 복잡한 계층 구조를 별도 로직 없이 간결하게 관리할 수 있다.
다른 그룹(other_team)의 사용자 검증
이제 team과는 전혀 상관없는 다른 그룹(other_team)에 속한 사용자를 추가하고, 1000번 티켓에 접근이 제한되는지 확인해보자.
새로운 그룹 생성 및 사용자 추가
zed relationship create group:other_team host member:noose
zed relationship create group:other_team member member:noose

ticket:1000에 대한 접근 검증
zed permission check ticket:1000 read member:noose # ❌ false
zed permission check ticket:1000 write member:noose # ❌ false
📌 결과
- noose → other_team 그룹에 속해 있으므로 team 그룹의 티켓에 대한 권한이 없음 ❌
- SpiceDB의 관계 기반 권한 시스템이 제대로 동작하고 있음을 확인 ✅
💡 team 그룹과 관계없는 other_team의 사용자는 1000번 티켓을 읽거나 쓸 수 없다.

이제 우리는 명확하게 권한을 제한할 수 있는 모델을 구현할 수 있다!
특정 리소스의 권한을 트리(Tree) 형태로 시각화
SpiceDB는 특정 리소스가 어떤 경로를 통해 권한을 얻게 되었는지 트리 형태로 확인할 수 있는 기능을 제공한다.
zed permission expand read ticket:1000
# 결과 예시
ticket:1000->read
└── union
├── ticket:1000->write
│ └── union
│ ├── ticket:1000->owner
│ │ └── member:alice
│ └── ticket:1000->write
│ └── union
│ └── group:team->write
│ └── union
│ ├── group:team->host
│ │ └── member:alice
│ ├── group:team->parent
│ │ └── group:super_team
│ ├── group:team->write
│ │ └── union
│ │ └── group:super_team->member
│ │ └── member:dog
│ └── group:team->write
│ └── union
│ └── group:super_team->write
│ └── union
...
마무리
✅ SpiceDB를 사용하여 관계 기반의 권한 검증을 수행하는 방법을 익혔다.
✅ 복잡한 계층 구조에서도 권한을 간단하게 정의하고, 확인할 수 있다.
다음글에서 SpiceDB를 실제 애플리케이션(Kotlin)과 연동하여 활용하는 방법을 살펴보자 🚀