접근 제어 목록(ACL)은 리소스에 연결되어 어떤 사용자나 역할이 접근할 수 있고 각각 무엇을 할 수 있는지 명시하는 목록입니다. 핵심은 권한을 어디에 연결하느냐에 있습니다. 역할 시스템이 권한을 사람에 거는 반면, ACL은 권한을 객체에 겁니다. 모든 레코드가 자신만의 출입 명단을 갖고, 모든 항목(접근 제어 항목, ACE)은 주체 하나와 그 주체의 권한을 짝짓습니다. ACL은 컴퓨팅에서 가장 오래된 보안 구성 요소 중 하나이며, 애플리케이션 백엔드에서는 “Ada와 편집자만 이 문서를 만질 수 있다”는 규칙을 별도 기능이 아니라 필드 하나로 만드는 방법입니다.
핵심 요약
| 질문 | 답변 |
|---|---|
| 구조 | 객체 → 항목 목록 · 각 항목 = 주체 + 권한 |
| 세 가지 의미 | 네트워크 트래픽 필터 · 파일 시스템 권한 · 레코드별 애플리케이션 ACL |
| vs. RBAC | ACL의 질문은 “누가 이 객체를 만질 수 있는가?”, RBAC의 질문은 “이 역할은 무엇을 할 수 있는가?” |
| 확장 문제의 해법 | 역할 항목 + 기본 ACL + 일반적인 경우를 위한 클래스 수준 규칙 |
| 철칙 | 서버 측에서 모든 요청마다 평가 · 클라이언트에서의 평가는 금지 |
한 객체의 출입 명단
문서 "Q3 roadmap" — ACL
┌────────────────────┬──────┬──────┐
│ 주체 │ 읽기 │ 쓰기 │
├────────────────────┼──────┼──────┤
│ user usr-8fk2 (Ada)│ 예 │ 예 │ ← 소유자
│ user usr-2mq7 (Bob)│ 예 │ — │ ← 개별 부여
│ role editors │ 예 │ 예 │ ← 역할 하나가 항목 하나
│ public (전체) │ — │ — │ ← 기본값: 닫힘
└────────────────────┴──────┴──────┘
레코드 자체에 저장된 데이터 형태:
{ "title": "Q3 roadmap",
"ACL": { "usr-8fk2": { "read": true, "write": true },
"usr-2mq7": { "read": true },
"role:editors": { "read": true, "write": true } } }
애플리케이션 코드에서 이 출입 명단을 작성하는 방법입니다.
// JavaScript / Node.js — Back4app JS SDK
// A per-object ACL: this document's own guest list
const doc = new Parse.Object('Document');
doc.set('title', 'Q3 roadmap');
const acl = new Parse.ACL(currentUser); // owner: read + write
acl.setReadAccess(reviewerId, true); // one user: read only
acl.setRoleWriteAccess('editors', true); // a role as an entry
acl.setPublicReadAccess(false); // everyone else: nothing
doc.setACL(acl);
await doc.save(); // enforced server-side on every future request // Flutter / Dart — Back4app Flutter SDK
// A per-object ACL: this document's own guest list
final doc = ParseObject('Document')..set('title', 'Q3 roadmap');
final acl = ParseACL(owner: currentUser); // owner: read + write
acl.setReadAccess(userId: reviewerId, allowed: true); // one user: read only
acl.setRoleWriteAccess('editors', true); // a role as an entry
acl.setPublicReadAccess(allowed: false); // everyone else: nothing
doc.setACL(acl);
await doc.save(); // enforced server-side on every future request // iOS / Swift — Back4app Swift SDK
// A per-object ACL: this document's own guest list
var doc = Document()
doc.title = "Q3 roadmap"
var acl = ParseACL()
acl.setReadAccess(user: currentUser, value: true) // owner: read…
acl.setWriteAccess(user: currentUser, value: true) // …and write
acl.setReadAccess(objectId: reviewerId, value: true) // one user: read only
acl.setWriteAccess(roleName: "editors", value: true) // a role as an entry
acl.publicRead = false // everyone else: nothing
doc.ACL = acl
try await doc.save() // enforced server-side on every future request // Android / Kotlin — Back4app Android SDK
// A per-object ACL: this document's own guest list
val doc = ParseObject("Document")
doc.put("title", "Q3 roadmap")
val acl = ParseACL(ParseUser.getCurrentUser()) // owner: read + write
acl.setReadAccess(reviewerId, true) // one user: read only
acl.setRoleWriteAccess("editors", true) // a role as an entry
acl.publicReadAccess = false // everyone else: nothing
doc.acl = acl
doc.save() // enforced server-side on every future request ”ACL”의 세 가지 의미
대부분의 설명은 별다른 언급 없이 그중 한 가지 의미만 다루지만, 이 용어는 실제로 세 가지 메커니즘을 가리킵니다.
| 계열 | 연결 대상 | 항목의 형태 | 평가 주체 |
|---|---|---|---|
| 네트워크 ACL | 라우터/방화벽 인터페이스 | IP, 포트, 프로토콜에 대한 허용/거부 규칙 – 순서가 있고, 처음 일치한 규칙이 적용되며, 마지막은 암묵적 거부 | 네트워크 장비 |
| 파일 시스템 ACL | 파일과 디렉터리 | user:ada:rw- – owner/group/other의 확장(POSIX acl(5)) | 운영체제 |
| 애플리케이션 ACL | 행, 문서, 객체 | 레코드에 붙은 사용자/역할 → 읽기/쓰기 플래그 | 백엔드, 요청마다 |
셋은 형태를 공유합니다. 리소스를 지키는 주체-권한 항목의 목록이라는 점입니다. 그 밖의 모든 것은 다릅니다. 이 글이 다루는 것은 세 번째 의미, 즉 애플리케이션 백엔드의 레코드별 권한 목록입니다. 가장 덜 다뤄지면서도 제품 개발자가 가장 많이 쓰는 의미입니다.
요청이 평가되는 방식: 2단계 관문 모델
계층화는 성숙한 시스템이 거친 제어와 세밀한 제어를 조화시키는 방법입니다. 클래스 수준 권한이 카테고리 전체의 정책(“인증된 사용자만, 삭제는 모더레이터만”)을 정하고, 객체별 ACL이 개별 레코드를 결정합니다. 요청은 두 관문을 모두 통과해야 합니다. 따라서 ACL 설정을 빠뜨려도 클래스 규칙이 닫아 둔 것을 열 수 없고, 관대한 클래스 규칙도 잠긴 객체를 노출할 수 없습니다. 같은 계층화 논리는 한 단계 아래에서 행 수준 보안으로 다시 나타납니다. 데이터베이스 자체가 행 조건(predicate)을 강제하는 경우입니다.
ACL vs. RBAC vs. ABAC
| ACL | RBAC | ABAC | |
|---|---|---|---|
| 권한이 연결되는 곳 | 각 객체 | 사용자에게 할당된 역할 | 속성에 대한 규칙 |
| 본래의 질문 | 누가 이 객체를 만질 수 있는가? | 이 역할은 무엇을 할 수 있는가? | 이 접근이 이 맥락에서 허용되는가? |
| 세분성 | 가장 세밀한 수준 – 레코드별, 사용자별 | 거친 수준 – 직무별 | 임의 수준 – 조건별 |
| 관리 비용 | 객체 × 주체에 비례해 증가 | 역할 수에 비례해 증가 | 규칙 복잡도에 비례해 증가 |
| ”누가 X를 보는가?” 감사 | 간단 – X의 목록 확인 | 간접적 – 역할 전개 | 어려움 – 규칙 평가 |
| ”Ada는 무엇을 볼 수 있는가?” 감사 | 어려움 – 모든 객체 스캔 | 간단 – Ada의 역할 확인 | 어려움 |
| 약점 | 난립 | 역할 폭발, 객체별 세부 조정 불가 | 불투명한 정책 디버깅 |
현실적인 답은 경쟁이 아니라 조합입니다. 직무를 따르는 접근은 역할이 처리하고, 역할로는 표현할 수 없는 객체별 결정(“이 초안, 이 두 명의 검토자”)은 ACL이 처리하며, 맥락(시간, 테넌트, 상태)이 중요할 때는 속성 규칙이 나섭니다. 앞의 두 방식을 실제로 잇는 연결 고리는 역할 항목 ACE입니다. 주체가 역할인 ACL 항목으로, 객체 수준의 제어는 유지하면서 구성원 변동은 역할 시스템에 맡깁니다.
확장 문제와 완화 사다리
단순한 객체별 ACL은 객체 N개 × 주체 M개로 커집니다. 문서 100만 개가 각각 개별 사용자를 나열한다면, 입사, 퇴사, 조직 개편이 있을 때마다 데이터셋 전체에 흩어진 목록을 수정해야 합니다. 모든 교과서가 언급하는 “관리하기 어렵다”는 말이 구체적으로 뜻하는 바입니다. 완화 사다리를 오를 순서대로 정리하면 다음과 같습니다. 역할 항목(ACE 하나가 변하는 집단 전체를 담당하고, 구성원 변경은 한곳에서 이루어집니다), 기본 ACL(새 객체는 생성 시점부터 소유자 읽기/쓰기와 적절한 역할 항목을 갖습니다. 디렉터리에 적용되는 POSIX 기본 ACL의 애플리케이션 버전입니다), 일반적인 경우를 위한 클래스 수준 규칙(객체별 목록은 예외에만 사용합니다), 그리고 관계가 복잡한 규모에서는 목록을 저장하는 대신 관계로부터 접근을 도출하는 그래프 기반 인가(ReBAC)입니다. 사다리를 건너뛴 시스템은 ACL을 포기하는 것이 아니라 ACL에 파묻힙니다.
강제 적용: 서버 측에서, 예외 없이
클라이언트에서 강제하는 ACL은 권고에 불과합니다. 버튼 숨기기, JavaScript로 목록 필터링하기, 앱이 허용된 ID만 보낼 것이라고 믿기는 모두 같은 방식으로 실패합니다. 공격자는 UI가 아니라 요청을 수정합니다. /documents/41을 /documents/42로 바꾸면 다른 사람의 레코드를 읽게 됩니다. 이런 실패 유형, 즉 OWASP API 보안 목록의 최상위 항목인 객체 수준 인가 취약점(BOLA)이야말로 객체별 ACL이 막기 위해 존재하는 대상이며, OWASP 가이드는 단호합니다. 인가 검사는 서버 측에서, 요청마다, 객체마다 실행되어야 하며, 어떤 유형의 객체에 접근할 수 있다고 해서 그 유형의 모든 객체에 접근할 수 있는 것은 아닙니다. 평가는 어떤 클라이언트 경로로도 우회할 수 없는 데이터 계층에 있어야 합니다.
일반적인 사용 사례
- 사용자 생성 콘텐츠 – 각 게시물, 파일, 메모는 작성자가 소유하며 레코드 단위로 공유됩니다.
- 문서 협업 – 문서별 뷰어/편집자 목록입니다. 공유 대화상자는 UX의 옷을 입은 ACL 편집기입니다.
- 예외가 있는 다중 사용자 레코드 – HR 사례입니다. 레코드의 당사자는 읽고, 그 관리자는 쓰며, auditors 역할은 모든 것을 읽습니다.
- 테넌트 및 팀 범위 지정 – 공유 클래스에 팀별 역할 항목을 두고, 팀 간 예외는 객체별 부여로 처리합니다.
- 기본 비공개 앱 – 메시징, 헬스케어, 금융 앱에서는 모든 객체가 생성 시점에 닫혀 있고, 명시적 항목으로만 열립니다.
ACL과 역할 중 무엇을 선택해야 할까? 결정 매트릭스
| 상황 | 선택 |
|---|---|
| 여러 레코드에 걸쳐 직무를 따르는 접근 | 역할(RBAC) |
| 레코드마다 고유한 공유 결정이 필요한 경우 | ACL |
| 두 패턴이 동시에 필요한 경우(대부분의 실제 앱) | 클래스 규칙 + 역할 항목 ACL |
| ”모두 읽기 가능, 소유자만 쓰기 가능” | ACL public read 플래그 + 소유자 항목 |
| 규칙이 맥락(시간, 상태, 테넌트)에 좌우되는 경우 | ACL 위에 속성 조건 추가 |
| 깊은 관계 로직(조직도, 중첩 그룹) | ReBAC 방식의 시스템 |
한계와 트레이드오프
- 난립이 기본 궤적입니다. 역할 항목과 기본값이 없으면 객체별 목록은 감사가 불가능할 만큼 잘게 흩어집니다. 규모가 커지면 완화 사다리는 선택 사항이 아닙니다.
- “이 사용자는 무엇에 접근할 수 있는가?”가 비싼 쿼리입니다. ACL은 객체별 감사에 최적화되어 있습니다. 주체별 목록을 만들려면 스캔이나 보조 인덱스가 필요합니다.
- 잘못된 기본값은 조용한 침해입니다. public read 상태로 생성된 객체는 누군가 알아챌 때까지 공개 상태로 남습니다. 기본 ACL은 코드와 같은 수준의 리뷰를 받아야 합니다.
- 성능은 검사에 달려 있습니다. 모든 읽기가 ACL로 필터링됩니다. 평가는 인덱스를 활용해야 하며, 엔드포인트마다 덧붙이는 것이 아니라 데이터 계층에서 강제되어야 합니다.
- ACL은 인가할 뿐 인증하지 않습니다. 목록은 제시된 신원만큼만 믿을 수 있습니다. 세션과 토큰이 그 상류 의존성입니다.
Back4app에서의 ACL
Back4app은 관리형 데이터베이스, 자동 생성되는 REST 및 GraphQL API, 인증, 파일 스토리지, Cloud Code 서버리스 함수를 결합한 오픈소스 Backend as a Service(BaaS) 플랫폼입니다. 여기서 ACL은 일급 필드입니다. 모든 객체가 ACL을 하나씩 가지며, 위의 코드 탭이 API의 전부입니다. 소유자 기본값, 사용자별 부여, 역할 항목, public 플래그가 그것입니다. ACL은 모든 REST, GraphQL, Live Query 요청마다 Back4app 안에서 강제되므로, 실시간 구독도 쿼리와 똑같은 출입 명단을 따릅니다. 2단계 관문 모델도 그대로 제공됩니다. 클래스 수준 권한이 대시보드에서 카테고리 정책을 정하고, 객체별 ACL이 레코드 단위로 이를 세분화하며, 기본 ACL 설정은 새 객체를 생성 시점부터 소유자 전용 비공개 객체로 만듭니다. 확장 사다리도 내장되어 있습니다. 역할은 다른 데이터와 똑같이 관리하는 객체입니다. 사용자의 몫으로 남는 것은 강제 장치가 아니라 설계 결정입니다.
자주 묻는 질문
ACL을 쉽게 설명하면 무엇입니까?
각 리소스에 붙은 출입 명단입니다. 그 특정 객체에 누가 접근할 수 있고 각자 무엇을 할 수 있는지 명시합니다. Ada는 읽고 쓸 수 있고, Bob은 읽기만 할 수 있으며, 나머지는 모두 들어올 수 없습니다. 목록이 객체와 함께 다니므로 객체마다 규칙을 다르게 둘 수 있습니다.
접근 제어 항목(ACE)이란 무엇입니까?
목록의 한 줄입니다. 주체(사용자, 역할 또는 "모든 사람")와 그 주체에게 부여되거나 거부된 권한을 짝지은 것입니다. ACL은 하나의 리소스에 연결된 ACE의 순서 있는 모음일 뿐입니다.
ACL에는 어떤 종류가 있습니까?
같은 이름을 쓰는 세 계열이 있습니다. 네트워크 ACL(라우터와 방화벽의 순서 있는 트래픽 필터), 파일 시스템 ACL(owner/group/other를 확장하는 파일별 권한 목록), 애플리케이션 또는 데이터베이스 ACL(데이터 계층의 레코드별 권한 목록)입니다. 백엔드 개발에서는 보통 세 번째를 가리킵니다.
ACL과 RBAC의 차이는 무엇입니까?
연결 방향입니다. ACL은 권한을 각 리소스에 주체별로 겁니다. 개별 객체마다 개별 결정이 필요할 때 이상적입니다. RBAC은 권한을 역할에 걸고 사용자를 역할에 할당합니다. 접근이 여러 리소스에 걸쳐 직무를 따를 때 이상적입니다. 실제 시스템은 둘을 결합합니다. 큰 틀은 역할로, 객체별 예외는 ACL로 처리합니다.
데이터베이스에서 ACL은 어떻게 동작합니까?
각 행이나 문서가 자신의 권한 목록을 직접 담거나 참조합니다. 일반적으로 사용자 ID와 역할 이름을 읽기/쓰기 플래그에 매핑하는 ACL 필드입니다. 데이터베이스나 백엔드가 모든 작업마다 이를 평가하며, 이 방식은 행 수준 보안 및 테이블별 권한 계층과 자연스럽게 맞물립니다.
ACL과 자격 목록(capability list)의 차이는 무엇입니까?
같은 접근 행렬을 보는 두 관점입니다. ACL은 열입니다. 객체와 함께 저장되며 그 객체의 주체를 나열합니다. 자격 목록은 행입니다. 주체와 함께 저장되며 그 주체의 객체를 나열합니다. ACL은 "누가 이 객체를 만질 수 있는가?"를 즉시 감사할 수 있게 해 주고, 자격 목록은 "이 사용자가 무엇을 만질 수 있는가?"를 쉽게 만들지만 권한 폐기를 어렵게 합니다.
ACL만으로는 왜 확장되지 않습니까?
관리 작업이 객체 × 주체로 늘어나기 때문입니다. 입사, 퇴사, 팀 변경이 있을 때마다 수백만 개 객체에 흩어진 목록을 수정해야 합니다. 완화책은 역할 항목(ACE 하나가 변하는 그룹 전체를 담당), 생성 시 적용되는 기본 ACL, 그리고 일반적인 경우를 처리하는 클래스 수준 규칙입니다. 그러면 객체별 목록은 예외만 다루게 됩니다.
객체별 권한과 클래스별 권한의 차이는 무엇입니까?
세분성입니다. 클래스별(또는 테이블별) 권한은 "로그인한 사용자만 Documents를 조회할 수 있다"처럼 카테고리 전체를 통제합니다. 객체별 ACL은 "Ada만 이 문서를 읽을 수 있다"처럼 레코드 하나를 통제합니다. 계층형 시스템은 클래스 관문을 먼저 확인한 뒤 객체의 ACL을 확인하며, 요청은 둘 다 통과해야 합니다.