역할 기반 권한 RBAC¶
한 줄 요약: 로그인은 "누구인지"만 알려 준다. "관리자인지"는 앱이 규칙으로 정한다. 그리고 그 확인은 화면이 아니라 서버 API가 요청마다 해야 한다(로그인 안 함 401, 권한 없음 403).
📍 만난 상황¶
- 2026-11-09 17:42 — 가짜 로그인 서버로 SSO 흐름 전체를 시험하는 방법(②안)을 들은 직후였다. → 그날 일지
- 물었다: "일단 마이페이지 같은 걸 만들어야 하는 거 아니야? 그리고 관리자와 일반 사용자는 어떻게 구분하지?"
- 답 1: 마이페이지는 "내 즐겨찾기"라서, "나"가 누구인지 알려 줄 로그인이 먼저다.
- 답 2: 로그인은 신원만 준다. 관리자 여부는 우리 앱이 관리자 목록으로 정하고, 서버 API에서 막는다.
🧩 쉽게 말하면¶
회사 출입증과 서버실 출입 명단에 빗대면 된다. 출입증(로그인)은 "이 사람은 직원 ○○이다"만 보여 준다. 서버실에 들어가도 되는지는 출입증에 적혀 있지 않다.
서버실 앞 경비원이 명단(역할 규칙)을 보고 들여보낸다. 안내판에서 "서버실" 글자를 지워도(버튼 숨기기) 길을 아는 사람은 찾아온다. 그래서 문 앞 경비가 진짜 보안이다.
이게 없으면 로그인만 하면 누구나 승인 버튼을 누를 수 있다. 또는 사람마다 권한을 하나하나 따로 적어야 해서 관리가 안 된다.
📖 제대로 알기¶
인증과 인가¶
- 인증(Authentication: "누구인가" 확인). 로그인이 하는 일이다. 결과는 사용자 ID(
sub), 이름, 이메일, 그룹 정도다. - 인가(Authorization: "이걸 해도 되나" 판정). 앱이 규칙으로 정한다.
- 영어로 둘 다 Auth로 시작해서 자주 섞인다. 줄여서 AuthN(인증), AuthZ(인가)라고 쓰기도 한다.
RBAC¶
- RBAC(Role-Based Access Control: 역할 기반 접근 제어). 사람에게 권한을 직접 주지 않고, 역할에 권한을 묶은 뒤 사람에게 역할을 준다.
- 역할(role):
admin,user같은 이름표. - 권한(permission): "제안 승인", "문의 답변" 같은 행동 하나.
- 사람 100명에 권한 10개면 직접 주는 조합이 1,000개다. 역할 2개로 묶으면 "누가 어느 역할인가" 100개만 관리하면 된다.
역할을 정하는 방법¶
| 방법 | 예 | 맞는 경우 |
|---|---|---|
| 관리자 목록(설정 값) | ADMIN_EMAILS 환경 변수 |
처음, 관리자 몇 명 |
| 그룹 ↔ 역할 표 | 로그인 groups → group_role_map |
조직 그룹으로 관리할 때 |
| 사용자 표의 열 | app_user.role |
기록·표시용(판정의 원본은 위 둘) |
세션과 상태 코드¶
- 세션 쿠키(session cookie: 로그인 상태를 담아 브라우저가 매번 보내는 작은 값). 역할을 여기 적되, 서버가 서명(위조 방지 표시)한다. 이용자가
user를admin으로 고치면 서명이 깨져 거절된다. - 401 Unauthorized: 누구인지 모름. 로그인 안 했거나 세션이 만료됨.
- 403 Forbidden: 누구인지는 앎. 하지만 이 행동은 안 됨.
⚙️ 어떻게 동작하나¶
- 로그인(인증): 로그인 서버가 "이 사람은 sub=…, 이메일=…"을 알려 준다.
- 역할 정하기(인가 규칙): 앱이 관리자 목록이나 그룹 대응표를 본다. 있으면
admin, 없으면user. - 기록하기: 서명한 세션 쿠키에 역할을 적는다.
app_user.role에도 남긴다(누가 승인했는지 이력에 쓰려고). - 요청마다 검사: 관리자 API(
/api/admin/...)는 맨 앞에서 세션을 읽는다. 없으면 401,admin이 아니면 403. - 화면은 편의: 관리자에게만 "관리자 메뉴", "승인" 버튼을 보여 준다. 숨기기일 뿐 보안이 아니다.
sequenceDiagram
participant B as 브라우저
participant A as 서버 API
participant D as 대시보드 DB
B->>A: POST 승인 요청 (세션 쿠키)
A->>A: 세션 서명 확인
alt 세션 없음
A-->>B: 401 로그인 필요
else role이 user
A-->>B: 403 권한 없음
else role이 admin
A->>D: 제안 승인 + 변경 기록
A-->>B: 200 완료
end
검사는 DB를 건드리기 전에 서버에서 끝난다. 버튼을 숨겼든 아니든, 주소를 직접 불러도 같은 검사를 거친다.
🧪 예시¶
- 예시 1 · 일상에 빗대어: 직원 50명 회사에 서버실 담당이 3명이다. 출입증은 50명 모두 같다. 경비원 명단에 3명만 있다. 47명 중 누가 서버실 문을 두드리면 경비원이 "직원은 맞지만 명단에 없습니다"라고 돌려보낸다(403). 출입증 없는 외부인은 "누구신지 먼저 확인할게요"(401)다.
- 예시 2 · 실제로는: 관리자 API 맨 앞에 넣는 검사다.
// src/app/api/admin/suggestions/[id]/approve/route.ts
export async function POST(req: Request) {
const me = await getSessionUser(); // ① 서명된 세션 읽기
if (!me) return json(401, "로그인 필요"); // ② 누구인지 모름
if (me.role !== "admin")
return json(403, "권한 없음"); // ③ 누구인지 알지만 안 됨
// ④ 여기부터 실제 승인 처리
return approveSuggestion(req, me.id);
}
admin이 아니면 403. 버튼이 없던 사람이 주소를 직접 불러도 여기서 막힌다.
- ④ me.id를 넘겨 "누가 승인했는지"를 변경 기록에 남긴다.
역할을 정하는 쪽은 이렇게 생긴다.
const admins = (process.env.ADMIN_EMAILS ?? "").split(",");
const role = admins.includes(profile.email) ? "admin" : "user";
⚖️ 비교 · 흔한 오해¶
401과 403¶
| 코드 | 뜻 | 이용자가 할 일 |
|---|---|---|
| 401 | 누구인지 모름 | 로그인한다 |
| 403 | 누구인지 알지만 안 됨 | 권한을 요청한다 |
오해 1: "로그인하면 역할도 알려 준다"¶
- 실제: 로그인 서버는 신원과 그룹 정도만 준다. "이 그룹이면 대시보드 관리자"라는 규칙은 대시보드가 정한다. 같은 사람이 A 앱에선 관리자, B 앱에선 일반 사용자일 수 있다.
- 왜 그렇게 생각하기 쉬운가: 회사 시스템에 로그인하면 바로 내 메뉴가 보이는 경험 때문이다. 로그인 직후 일어나는 역할 판정이 눈에 보이지 않아서 로그인이 다 해 준 것처럼 느껴진다.
오해 2: "버튼을 숨기면 안전하다"¶
- 실제: 버튼은 화면일 뿐이다. 개발자 도구나
curl로 같은 주소를 직접 부를 수 있다. 서버가 막아야 한다.
오해 3: "역할을 쿠키에 적으면 된다"¶
- 실제: 그냥 적으면 이용자가 고친다. 서명된 쿠키여야 한다. 또는 쿠키엔 세션 번호만 두고 역할은 서버에서 찾는다.
비교: 플랫폼 권한과는 층이 다르다¶
dhub2 플랫폼은 "누가 어떤 데이터셋을 읽을 수 있나"를 따로 판정한다. 대시보드 RBAC는 "누가 대시보드의 승인·관리 기능을 쓰나"만 정한다. 둘은 겹치지 않고 각자 자기 것을 지킨다.
🛠️ 실무에서는¶
- 기본은 거부: 역할을 모르면
user로, 규칙에 없으면 막는다. "일단 열고 나중에 막기"는 사고가 난다. - 체크리스트
- 관리자 API마다 맨 앞에 401·403 검사가 있다(공통 함수로 묶기).
- 세션 쿠키가 서명돼 있다.
- 역할 판정 원본이 한 곳이다(관리자 목록 또는 그룹 대응표).
- 승인 같은 행동에 "누가 했는지"를 기록한다.
- 개발용 페르소나 전환(관리자로 보기/일반으로 보기)은 운영에서 꺼져 있다.
- 역할 변경 반영 시점: 역할을 세션에 적었다면, 목록에서 빼도 다음 로그인까지 옛 역할이 남는다. 바로 막아야 하면 세션을 짧게 하거나 요청 때 다시 확인한다.
- 문제가 생기면 먼저 볼 것
- 관리자인데 403 → 이메일 대소문자·공백, 환경 변수 반영 여부, 다시 로그인했는지.
- 다들 401 → 쿠키 이름·도메인, 서명 키가 서버마다 다른지.
❓ 자주 묻는 질문¶
- Q. 이메일로 관리자를 정해도 되나?
A. 처음 몇 명이면 간단해서 괜찮다. 다만 이메일은 바뀔 수 있어서, 사람이 늘면
sub나 그룹 기준으로 옮기는 편이 안전하다. - Q. 403 대신 404를 돌려주는 곳도 있던데? A. "그런 기능이 있다는 것 자체"를 숨기고 싶을 때 쓴다. 관리자 화면 정도면 403이 디버깅에 더 편하다.
- Q. 화면 숨기기는 아예 안 해도 되나? A. 해야 한다. 보안이 아니라 사용성이다. 못 누르는 버튼을 보여 주면 헷갈린다.
- Q. 역할이 더 잘게 필요해지면? A. "데이터셋 담당자" 같은 역할을 더하거나, 대상별 권한(누가 어떤 데이터셋을)을 따로 둔다. 후자는 관계 기반 권한(ReBAC)이라 부른다.
🔭 더 깊이 보기¶
- 세션 쿠키와 JWT는 어떻게 다른가? — 역할을 어디에 담고 어떻게 위조를 막는지가 갈린다.
- ABAC·ReBAC는 RBAC와 무엇이 다른가? — 플랫폼 권한 모델처럼 "대상별 권한"이 필요할 때의 다음 단계다.
- OWASP의 접근 제어 취약점은 무엇이 있나? — 서버 검사를 빠뜨린 실제 사고 유형을 익힐 수 있다.
- 관련 주제: OIDC 로그인 흐름 · 프론트엔드와 백엔드 서버 구조 · ERD 관계와 카디널리티
📚 참고 자료¶
- HTTP 인증 (MDN 한국어) — 401 응답과 인증 흐름의 기본 · 링크
- 403 Forbidden (MDN 한국어) — 401과 403을 언제 쓰는지 · 링크
- Authorization Cheat Sheet (OWASP, 영문) — 기본 거부, 서버 쪽 검사 같은 인가 원칙 · 링크
🗓️ 이력¶
- 2026-11-09: 마이페이지보다 로그인이 먼저인지, 관리자와 일반 사용자를 어떻게 구분하는지 물음. 인증·인가 구분과 서버 API의 401·403 검사를 정리 → 일지
🕸️ 연결된 주제¶
바로 이어진 주제 3개전체 지도에서 보기 →