인증된 사용자가 무엇을 할 수 있는지 결정하는 과정(Authorization, AuthZ).
모델 세 가지
① RBAC (역할 기반) — 가장 널리 쓰인다 사용자 → 역할 → 권한 김철수 → ADMIN → [사용자관리, 결제조회, 설정변경]
- 이영희 → USER — → [본인정보조회] 역할만 바꾸면 권한이 한꺼번에 바뀌어 관리가 쉽다.
② ABAC (속성 기반) — 조건으로 판단 if (사용자.부서 == 문서.소유부서 && 시간 in 업무시간 && 위치 == 사내)
- 허용 훨씬 유연하지만 규칙이 복잡해져 어떤 규칙이 적용됐는지 추적이 어렵다.
③ ReBAC (관계 기반) — 자원과의 관계로 판단 "이 문서의 소유자인가?" "이 프로젝트의 멤버인가?" 구글 Zanzibar가 대표적. 공유·협업 서비스에 잘 맞는다.
실무에서는 섞어 쓴다
역할로 큰 틀을 나누고(RBAC), 세부는 소유권으로 판단(ReBAC)
@PreAuthorize("hasRole('USER')") ← 역할 검사 public Order getOrder(Long id, User me) { Order o = repo.findById(id);
- if (!o.getOwner().equals(me)) — ← 소유권 검사
- throw new AccessDeniedException(); return o; }
가장 흔한 취약점 — IDOR
Insecure Direct Object Reference: 남의 자원 ID를 넣으면 그냥 보이는 문제.
-
GET /api/orders/1000 — → 내 주문 ✅
-
GET /api/orders/1001 — → 남의 주문인데 그냥 보인다 ✗
-
인증은 통과했지만 인가 검사를 안 한 것
"로그인했으니 됐다"가 아니라, 요청한 자원이 그 사용자의 것인지 매번 확인해야 한다. OWASP Top 10에서 수년째 1위(Broken Access Control)다.
어디서 검사해야 하나 — 프런트는 방어선이 아니다
❌ 프런트엔드에서 버튼을 숨기는 것만으로 끝
- 브라우저 도구로 API를 직접 호출하면 뚫린다
✅ 서버에서 반드시 검사한다. 프런트의 숨김은 UX일 뿐 보안이 아니다
검사 위치 — 계층별 특성
| 위치 | 장점 | 한계 |
|---|---|---|
| 게이트웨이 | 중앙 집중, 일관성 | 자원 소유권 같은 세부는 모른다 |
| 컨트롤러 | 진입점에서 일괄 | 도메인 규칙은 표현하기 어렵다 |
| 서비스 | 도메인 규칙 반영 가능 | 빠뜨리기 쉽다 |
| 데이터 계층 | 확실(행 수준 보안) | 성능·복잡도 |
거친 권한은 위에서, 세밀한 소유권은 아래에서 검사하는 다층 구조가 일반적이다.
기본값은 거부(deny by default)
❌ "차단 목록에 없으면 허용" → 새 엔드포인트를 추가하면 무방비로 열린다 ✅ "허용 목록에 있어야 허용" → 빠뜨려도 안전한 쪽으로 실패한다
스프링 시큐리티에서 anyRequest().authenticated() 를 마지막에 두는 이유다.
최소 권한 원칙
필요한 만큼만 준다. 개발 편의로 모두에게 ADMIN을 주는 것이 사고의 시작이다. 서비스 계정, DB 계정, 클라우드 IAM 역할 모두 마찬가지다.