OAuth 2.0과 Keycloak
TL;DR
- OAuth 2.0과 Keycloak의 핵심 개념과 사용 범위를 한눈에 정리
- 등장 배경과 필요한 이유를 짚고 실무 적용 포인트를 연결
- 주요 특징과 체크리스트를 빠르게 확인
1. 개념
이 문서에서는 현대 웹/모바일 애플리케이션의 인증/인가 표준인 OAuth 2.0과 OpenID Connect(OIDC), 그리고 이를 구현한 오픈소스 IAM 솔루션 Keycloak에 대해 상세히 설명합니다.
2. 배경
OAuth 2.0과 Keycloak이(가) 등장한 배경과 기존 한계를 정리한다.
3. 이유
이 주제를 이해하고 적용해야 하는 이유를 정리한다.
4. 특징
- 배경: 왜 인증/인가가 복잡해졌나?
- OAuth 2.0이란?
- OpenID Connect (OIDC)
- Keycloak이란?
- 언제 Keycloak을 도입해야 하나?
5. 상세 내용
작성일: 2026-02-02 키워드: OAuth 2.0, OpenID Connect, OIDC, Keycloak, SSO, JWT, Access Token, Refresh Token, Authorization Server, IAM, 인증, 인가
개요
이 문서에서는 현대 웹/모바일 애플리케이션의 인증/인가 표준인 OAuth 2.0과 OpenID Connect(OIDC), 그리고 이를 구현한 오픈소스 IAM 솔루션 Keycloak에 대해 상세히 설명합니다.
1. 배경: 왜 인증/인가가 복잡해졌나?
1.1 과거의 단순한 세계
┌─────────────────────────────────────────────────────────────────────────────┐
│ 2000년대 초반: 단순한 인증 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 사용자 서버 데이터베이스 │
│ │ │ │ │
│ │ 1. ID/PW 입력 │ │ │
│ │ ─────────────────────→ │ │ │
│ │ │ 2. DB에서 비밀번호 확인 │ │
│ │ │ ─────────────────────────→ │ │
│ │ │ ←───────────────────────── │ │
│ │ 3. 세션 쿠키 발급 │ │ │
│ │ ←───────────────────── │ │ │
│ │ │ │ │
│ │ 4. 이후 요청마다 │ │ │
│ │ 세션 쿠키 전송 │ │ │
│ │ ─────────────────────→ │ │ │
│ │
│ 특징: │
│ - 하나의 서버, 하나의 서비스 │
│ - 세션은 서버 메모리에 저장 │
│ - 단순하고 이해하기 쉬움 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
1.2 현대의 복잡한 요구사항
┌─────────────────────────────────────────────────────────────────────────────┐
│ 현재: 복잡한 인증 요구사항 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ 사용자 Kim │ │
│ └────────┬────────┘ │
│ │ │
│ ┌─────────────────────────┼─────────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ 웹 앱 │ │ 모바일 앱 │ │ 데스크톱 앱 │ │
│ │ (React/Vue) │ │ (iOS/Android) │ │ (Electron) │ │
│ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │ │
│ └───────────────────────┼───────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ API Gateway │ │
│ └──────────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────┼───────────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 사용자 │ │ 주문 │ │ 결제 │ │
│ │ 서비스 │ │ 서비스 │ │ 서비스 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 🔴 발생하는 문제들: │
│ - 각 서비스마다 인증 로직 구현? (중복 코드 + 보안 위험) │
│ - 세션을 어디에 저장? (서비스가 여러 개인데 세션 공유?) │
│ - 모바일 앱은 쿠키를 어떻게 처리? │
│ - 외부 서비스(Google, Kakao 로그인)와 연동은? │
│ - SSO(한 번 로그인으로 모든 서비스 접근)는? │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
2. OAuth 2.0이란?
2.1 OAuth의 탄생 배경
┌─────────────────────────────────────────────────────────────────────────────┐
│ OAuth 탄생 스토리 (2006-2007) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 🔴 당시 문제 상황: │
│ │
│ "내가 만든 앱에서 사용자의 Twitter 친구 목록을 가져오고 싶어요" │
│ │
│ 기존 방법 (매우 위험): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 앱: "Twitter 아이디와 비밀번호를 알려주세요" │ │
│ │ 사용자: "뭐? 내 비밀번호를 다른 앱에?" │ │
│ │ │ │
│ │ 문제점: │ │
│ │ → 사용자 비밀번호를 제3자 앱에 직접 제공해야 함 │ │
│ │ → 보안 위험: 앱이 비밀번호 저장/악용 가능 │ │
│ │ → 권한 제한 불가: 앱이 사용자처럼 모든 것을 할 수 있음 │ │
│ │ → 비밀번호 변경 시 모든 앱 재설정 필요 │ │
│ │ → 특정 앱만 권한 해제 불가 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 💡 해결 아이디어: │
│ "비밀번호 대신 '제한된 권한을 가진 토큰'을 발급하면 어떨까?" │
│ │
│ 📅 타임라인: │
│ - 2006년: Twitter의 Blaine Cook과 Google 개발자들이 논의 시작 │
│ - 2007년: OAuth 1.0 초안 발표 │
│ - 2010년: OAuth 1.0a (보안 취약점 수정) │
│ - 2012년: OAuth 2.0 RFC 6749 표준화 (완전히 새로운 설계) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
2.2 OAuth 2.0 핵심 개념: 4가지 역할
┌─────────────────────────────────────────────────────────────────────────────┐
│ OAuth 2.0의 4가지 역할 (Roles) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1️⃣ Resource Owner (자원 소유자) │
│ ───────────────────────────────── │
│ - 보호된 리소스(데이터)에 대한 접근 권한을 가진 사람 │
│ - 보통 "최종 사용자(End User)" │
│ - 예: Twitter 계정을 가진 Kim │
│ │
│ 2️⃣ Resource Server (자원 서버) │
│ ─────────────────────────────── │
│ - 보호된 리소스를 호스팅하는 서버 │
│ - Access Token을 검증하고 요청에 응답 │
│ - 예: Twitter API 서버, Google Drive API │
│ │
│ 3️⃣ Client (클라이언트) │
│ ──────────────────────── │
│ - Resource Owner를 대신하여 보호된 리소스에 접근하려는 애플리케이션 │
│ - 예: "트위터 분석 앱", "Google Drive 동기화 앱" │
│ │
│ 4️⃣ Authorization Server (인가 서버) │
│ ────────────────────────────────────── │
│ - Resource Owner를 인증하고 권한을 확인한 후 Access Token 발급 │
│ - 예: Twitter 로그인 서버, Google OAuth 서버 │
│ - ⭐ Keycloak이 이 역할을 수행! │
│ │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 실제 예시로 이해하기: │
│ │
│ "내 트위터 분석 앱이 사용자의 트윗을 분석하고 싶다" │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Resource Owner : 트위터 사용자 Kim │ │
│ │ Client : 트위터 분석 앱 │ │
│ │ Authorization Server : Twitter OAuth 서버 │ │
│ │ Resource Server : Twitter API (트윗 데이터 제공) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
2.3 OAuth 2.0 Authorization Code Flow
가장 일반적이고 안전한 흐름입니다.
┌─────────────────────────────────────────────────────────────────────────────┐
│ Authorization Code Flow (상세) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 사용자(Kim) 클라이언트(앱) Authorization Resource │
│ │ │ Server Server │
│ │ │ │ │ │
│ │ 1. "로그인" 클릭 │ │ │ │
│ │ ─────────────────→ │ │ │ │
│ │ │ │ │ │
│ │ │ 2. 인가 요청 URL 생성 │ │
│ │ │ - client_id │ │
│ │ │ - redirect_uri │ │
│ │ │ - response_type=code │ │
│ │ │ - scope=read write │ │
│ │ │ - state=랜덤값 │ │
│ │ ←─────────────── 리다이렉트 ─────────│ │ │
│ │ │ │ │ │
│ │ 3. 로그인 페이지 표시 │ │ │
│ │ ─────────────────────────────────────→│ │ │
│ │ │ │ │
│ │ 4. ID/PW 입력 + 권한 동의 ("이 앱이 │ │ │
│ │ 내 프로필을 읽어도 될까요?" 확인) │ │ │
│ │ ─────────────────────────────────────→│ │ │
│ │ │ │ │
│ │ 5. Authorization Code 발급 │ │ │
│ │ ←── 리다이렉트: redirect_uri?code=abc123&state=랜덤값 ──│ │
│ │ ─────────────────→ │ │ │ │
│ │ │ │ │ │
│ │ │ 6. Code → Token 교환 │ │
│ │ │ POST /token │ │
│ │ │ - grant_type=authorization_code │ │
│ │ │ - code=abc123 │ │
│ │ │ - client_id + client_secret │ │
│ │ │ ────────────────→│ │ │
│ │ │ │ │ │
│ │ │ 7. 토큰 발급 │ │ │
│ │ │ - access_token │ │
│ │ │ - refresh_token │ │
│ │ │ - expires_in │ │
│ │ │ ←────────────────│ │ │
│ │ │ │ │ │
│ │ │ 8. API 호출 │ │
│ │ │ Authorization: Bearer {token} │ │
│ │ │ ─────────────────────────────────→ │ │
│ │ │ │ │ │
│ │ │ 9. 데이터 응답 │ │ │
│ │ │ ←───────────────────────────────── │ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
2.4 OAuth 2.0의 토큰들
┌─────────────────────────────────────────────────────────────────────────────┐
│ OAuth 2.0 토큰 종류 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1️⃣ Authorization Code (인가 코드) │
│ ───────────────────────────────── │
│ 수명: 매우 짧음 (보통 1-10분) │
│ 용도: Access Token과 교환하기 위한 일회용 코드 │
│ 전달: URL 파라미터 (노출 위험 → 짧은 수명으로 보완) │
│ │
│ 2️⃣ Access Token (접근 토큰) ⭐ │
│ ─────────────────────────────── │
│ 수명: 짧음 (보통 5분 ~ 1시간) │
│ 용도: API 호출 시 인증 수단 │
│ 형식: JWT (JSON Web Token) 또는 Opaque Token │
│ 사용: Authorization 헤더에 포함 │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6... │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 3️⃣ Refresh Token (갱신 토큰) │
│ ────────────────────────────── │
│ 수명: 김 (보통 7일 ~ 30일, 또는 더 길게) │
│ 용도: Access Token 만료 시 재발급 │
│ 저장: 안전하게 저장 필요 (HttpOnly Cookie, Secure Storage) │
│ │
│ 4️⃣ ID Token (OIDC에서 추가) │
│ ───────────────────────────── │
│ 용도: 사용자 정보 (이름, 이메일 등) 포함 │
│ 형식: JWT (서명 포함) │
│ 검증: 클라이언트에서 서명 검증으로 위변조 확인 │
│ │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 💡 왜 Access Token은 수명이 짧을까? │
│ │
│ Access Token: │
│ - 매 API 요청마다 네트워크로 전송됨 │
│ - 노출 위험 높음 │
│ - 짧은 수명 → 탈취되어도 피해 최소화 │
│ │
│ Refresh Token: │
│ - 토큰 갱신 시에만 사용 │
│ - 서버에만 전달 (더 안전) │
│ - 긴 수명 → 사용자가 자주 로그인 안 해도 됨 │
│ │
│ Access Token 만료 시 흐름: │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Client: "Access Token 만료됐네, Refresh Token으로 갱신하자" │ │
│ │ Client → Auth Server: POST /token (refresh_token=xxx) │ │
│ │ Auth Server → Client: 새 access_token + (새 refresh_token) │ │
│ │ → 사용자는 다시 로그인할 필요 없음! │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
2.5 OAuth 2.0 Grant Types
┌─────────────────────────────────────────────────────────────────────────────┐
│ OAuth 2.0 Grant Types (인가 방식) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1️⃣ Authorization Code (권장 ⭐) │
│ ───────────────────────────────── │
│ 사용처: 서버 사이드 웹 앱 │
│ 특징: 가장 안전, client_secret을 서버에서 안전하게 보관 │
│ 흐름: 앞서 설명한 전체 흐름 │
│ │
│ 2️⃣ Authorization Code + PKCE (모바일/SPA 권장 ⭐) │
│ ─────────────────────────────────────────────────── │
│ 사용처: SPA, 모바일 앱 (client_secret 저장 불가한 환경) │
│ 특징: code_verifier/code_challenge로 Authorization Code 탈취 방지 │
│ PKCE: Proof Key for Code Exchange │
│ │
│ 흐름: │
│ 1. Client가 code_verifier(랜덤 문자열) 생성 │
│ 2. code_challenge = SHA256(code_verifier) 계산 │
│ 3. 인가 요청 시 code_challenge 전송 │
│ 4. 토큰 교환 시 code_verifier 전송 │
│ 5. Auth Server가 검증: SHA256(code_verifier) == code_challenge? │
│ │
│ 3️⃣ Client Credentials │
│ ───────────────────────── │
│ 사용처: 서버 간 통신 (사용자 개입 없이) │
│ 예시: 백엔드 Service A → 백엔드 Service B │
│ │
│ 흐름: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ POST /token │ │
│ │ grant_type=client_credentials │ │
│ │ client_id=service-a │ │
│ │ client_secret=xxx │ │
│ │ │ │
│ │ → Access Token 발급 (사용자 정보 없음) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 4️⃣ Resource Owner Password (비권장 ⚠️) │
│ ───────────────────────────────────────── │
│ 사용처: 레거시 시스템, 매우 신뢰할 수 있는 자사 앱 │
│ 위험: 클라이언트가 사용자 비밀번호를 직접 받음 │
│ 현재: OAuth 2.1에서 제거 예정 │
│ │
│ 5️⃣ Implicit (폐기됨 ❌) │
│ ───────────────────────── │
│ 과거: SPA용으로 사용됨 (Access Token이 URL fragment로 직접 전달) │
│ 문제: Access Token이 URL에 노출, 보안 취약 │
│ 현재: PKCE로 완전히 대체됨, OAuth 2.1에서 제거 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
3. OpenID Connect (OIDC)
3.1 OAuth 2.0의 한계
┌─────────────────────────────────────────────────────────────────────────────┐
│ OAuth 2.0만으로는 부족한 점 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 🔴 OAuth 2.0의 원래 목적: │
│ "인가(Authorization)" = 무엇을 할 수 있는가? │
│ "사용자가 이 앱에 자신의 데이터 접근을 허용한다" │
│ │
│ 🔴 OAuth 2.0이 정의하지 않은 것: │
│ "인증(Authentication)" = 누구인가? │
│ - 사용자가 누구인지 확인하는 표준 방법 없음 │
│ - Access Token은 "누구"에 대한 정보를 포함하지 않을 수 있음 │
│ - 각 서비스마다 다른 방식으로 사용자 정보 제공 │
│ │
│ 각 서비스의 사용자 정보 API (다 다름): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Google: GET /userinfo → {"email": "...", "name": "..."} │ │
│ │ Facebook: GET /me → {"id": "...", "first_name": ...} │ │
│ │ Twitter: GET /account/verify_credentials → {"screen_name":...}│ │
│ │ GitHub: GET /user → {"login": "...", "email": ...} │ │
│ │ │ │
│ │ → API 경로도 다르고, 응답 형식도 다름 │ │
│ │ → 여러 소셜 로그인 통합하려면 각각 다르게 처리해야 함 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
3.2 OIDC = OAuth 2.0 + 인증 레이어
┌─────────────────────────────────────────────────────────────────────────────┐
│ OpenID Connect (OIDC) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 📅 탄생: 2014년 │
│ 📚 정의: OAuth 2.0 위에 구축된 인증(Authentication) 레이어 │
│ │
│ 구조: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌───────────────────────────────┐ │ │
│ │ │ OpenID Connect (OIDC) │ ← 인증 레이어 │ │
│ │ │ - ID Token │ │ │
│ │ │ - UserInfo Endpoint │ │ │
│ │ │ - 표준 Claims │ │ │
│ │ │ - Discovery │ │ │
│ │ └───────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────┴────────────┐ │ │
│ │ │ OAuth 2.0 │ ← 인가 프레임워크 │ │
│ │ │ - Access Token │ │ │
│ │ │ - Refresh Token │ │ │
│ │ │ - Grant Types │ │ │
│ │ └─────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ OIDC가 추가한 것: │
│ │
│ 1️⃣ ID Token (JWT) │
│ - 사용자 정보를 담은 서명된 토큰 │
│ - 클라이언트에서 서명 검증으로 위변조 확인 │
│ - 표준 Claims: sub, name, email, picture, iat, exp 등 │
│ │
│ 2️⃣ UserInfo Endpoint (표준화) │
│ - GET /userinfo → 모든 OIDC 제공자가 동일한 경로와 형식 │
│ - 통합이 쉬워짐 │
│ │
│ 3️⃣ Discovery (자동 설정 조회) │
│ - GET /.well-known/openid-configuration │
│ - 인가 서버의 모든 설정 정보 자동 조회 │
│ - 토큰 엔드포인트, 지원 스코프, 키 정보 등 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
3.3 ID Token 상세
┌─────────────────────────────────────────────────────────────────────────────┐
│ ID Token (JWT) 구조 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ID Token = Header.Payload.Signature (Base64로 인코딩) │
│ │
│ 예시: │
│ eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9. │
│ eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IktpbSIsImVtYWlsIjoiLi4uIn0. │
│ SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c │
│ │
│ 디코딩 결과: │
│ │
│ Header: │
│ { │
│ "alg": "RS256", // 서명 알고리즘 │
│ "typ": "JWT", │
│ "kid": "key-id-123" // 서명 검증에 사용할 키 ID │
│ } │
│ │
│ Payload (Claims): │
│ { │
│ // 필수 Claims │
│ "iss": "https://keycloak.example.com/realms/cck", // 발급자 │
│ "sub": "user-uuid-12345", // 사용자 식별자 │
│ "aud": "accio-frontend", // 대상 클라이언트 │
│ "exp": 1706860800, // 만료 시간 │
│ "iat": 1706857200, // 발급 시간 │
│ │
│ // 선택 Claims (OIDC 표준) │
│ "name": "Kim", │
│ "email": "kim@example.com", │
│ "email_verified": true, │
│ "picture": "https://...", │
│ │
│ // Keycloak 커스텀 Claims │
│ "realm_access": { │
│ "roles": ["admin", "user"] │
│ } │
│ } │
│ │
│ Signature: │
│ → 위 내용이 변조되지 않았음을 증명 │
│ → 공개키로 검증 가능 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
4. Keycloak이란?
4.1 Keycloak 개요
┌─────────────────────────────────────────────────────────────────────────────┐
│ Keycloak 소개 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 📚 정의: │
│ 오픈소스 IAM(Identity and Access Management) 솔루션 │
│ = OAuth 2.0 + OIDC + SAML + 사용자 관리 + SSO를 모두 제공 │
│ │
│ 📅 역사: │
│ - 2014년: Red Hat (JBoss) 팀에서 개발 시작 │
│ - 현재: CNCF(Cloud Native Computing Foundation) 인큐베이팅 프로젝트 │
│ - 기반: Java (WildFly → Quarkus로 전환 중) │
│ │
│ 🎯 핵심 기능: │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ 사용자 관리 │ │ SSO │ │ Social │ │ │
│ │ │ - 가입/로그인│ │ - 한 번 │ │ Login │ │ │
│ │ │ - 비밀번호 │ │ 로그인으로│ │ - Google │ │ │
│ │ │ 정책 │ │ 모든 앱 │ │ - Facebook │ │ │
│ │ │ - 프로필 │ │ 접근 │ │ - Kakao │ │ │
│ │ └──────────────┘ └──────────────┘ │ - GitHub │ │ │
│ │ └──────────────┘ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ OAuth 2.0/ │ │ 역할 기반 │ │ LDAP/AD │ │ │
│ │ │ OIDC 서버 │ │ 접근 제어 │ │ 연동 │ │ │
│ │ │ - 토큰 발급 │ │ - Role │ │ - 기업 계정 │ │ │
│ │ │ - 토큰 검증 │ │ - Permission│ │ 통합 │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ 2FA/MFA │ │ 세션 관리 │ │ 이벤트 │ │ │
│ │ │ - OTP │ │ - 세션 목록 │ │ 로깅 │ │ │
│ │ │ - WebAuthn │ │ - 강제 로그 │ │ - 감사 추적 │ │ │
│ │ │ │ │ 아웃 │ │ │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
4.2 Keycloak 핵심 개념
┌─────────────────────────────────────────────────────────────────────────────┐
│ Keycloak 핵심 용어 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1️⃣ Realm (영역) │
│ ───────────────── │
│ - 독립된 인증 공간 (테넌트 개념) │
│ - 각 Realm은 별도의 사용자, 클라이언트, 역할, 설정 보유 │
│ - 멀티테넌트 구현에 활용 │
│ - 기본 Realm: "master" (관리용) │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Keycloak Server │ │
│ │ │ │
│ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │
│ │ │ Realm: master │ │ Realm: cck │ │ Realm: demo │ │ │
│ │ │ (관리자용) │ │ - Users: 100 │ │ - Users: 10 │ │ │
│ │ │ │ │ - Clients: 5 │ │ - Clients: 2 │ │ │
│ │ └───────────────┘ └───────────────┘ └───────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 2️⃣ Client (클라이언트) │
│ ──────────────────────── │
│ - Keycloak에 등록된 애플리케이션 │
│ - 각 앱(웹, 모바일, 백엔드)마다 별도 Client 등록 │
│ - 설정: Client ID, Client Secret, Redirect URIs, 허용 Grant Type │
│ │
│ Client Types: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Confidential │ client_secret 안전하게 보관 가능 (백엔드) │ │
│ │ Public │ client_secret 노출됨 (SPA, 모바일) │ │
│ │ Bearer-only │ 토큰 검증만 함 (API 서버) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 3️⃣ User (사용자) │
│ ────────────────── │
│ - 로그인 가능한 사용자 계정 │
│ - 속성: username, email, firstName, lastName, attributes │
│ - Federation: LDAP/Active Directory와 연동 가능 │
│ - Identity Provider: 외부 IdP로 로그인 (Google, Kakao 등) │
│ │
│ 4️⃣ Role (역할) │
│ ──────────────── │
│ Realm Role: │
│ - 전체 영역에서 유효 │
│ - 예: admin, manager, user │
│ │
│ Client Role: │
│ - 특정 클라이언트에서만 유효 │
│ - 예: accio-admin, accio-viewer │
│ │
│ 5️⃣ Group (그룹) │
│ ───────────────── │
│ - 사용자를 묶어서 역할을 일괄 부여 │
│ - 계층 구조 가능 │
│ - 예: /company/engineering/backend → backend 팀원에게 특정 역할 부여 │
│ │
│ 6️⃣ Identity Provider (외부 IdP) │
│ ──────────────────────────────── │
│ - 외부 인증 제공자와 연동 │
│ - Social: Google, Facebook, Kakao, Naver, GitHub │
│ - Enterprise: SAML, OIDC 기반 기업 IdP │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
4.3 Keycloak 아키텍처
┌─────────────────────────────────────────────────────────────────────────────┐
│ Keycloak을 포함한 시스템 아키텍처 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ 사용자 │ │
│ └────────┬────────┘ │
│ │ │
│ ┌──────────────────────────┼──────────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ Accio Web │ │ Jump App │ │ Admin │ │
│ │ (React) │ │ (Mobile) │ │ (Angular) │ │
│ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │ │
│ │ 1. 로그인 필요 시 리다이렉트 │ │
│ │ │ │ │
│ └───────────────────────┼───────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ Keycloak │ │
│ │ (Authorization Server) │ │
│ │ │ │
│ │ - 로그인 UI 제공 │ │
│ │ - 사용자 인증 │ │
│ │ - Token 발급 │ │
│ │ - SSO 세션 관리 │ │
│ │ - Social Login 처리 │ │
│ │ │ │
│ └──────────────┬──────────────┘ │
│ │ │
│ 2. Token 발급 후 앱으로 리다이렉트 │
│ │ │
│ ┌────────────────────────┼────────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ Accio Web │ │ Jump App │ │ Admin │ │
│ │ (Token 저장) │ │ (Token 저장) │ │ (Token 저장) │ │
│ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │ │
│ │ 3. API 호출 시 Authorization 헤더에 Token 포함 │
│ │ │ │ │
│ └───────────────────────┼───────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────┐ │
│ │ API Gateway │ │
│ │ (또는 각 서비스에서) │ │
│ │ - Token 검증 │ │
│ │ - Role/Permission 확인 │ │
│ └──────────────┬──────────────┘ │
│ │ │
│ ┌────────────────────────┼────────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ Accio Backend │ │ Jump Backend │ │ Mothership │ │
│ │ Service │ │ Service │ │ Service │ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
4.4 Keycloak의 장점
┌─────────────────────────────────────────────────────────────────────────────┐
│ Keycloak 사용의 7가지 장점 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1️⃣ 인증 로직 중앙화 │
│ ───────────────────── │
│ Without Keycloak: │
│ - 각 서비스마다 로그인 로직 구현 (중복 코드) │
│ - 비밀번호 정책, 암호화 각각 관리 (불일치 위험) │
│ - 보안 취약점 발생 가능성 증가 │
│ │
│ With Keycloak: │
│ - 인증은 Keycloak만 담당 (단일 책임) │
│ - 서비스는 토큰 검증만 하면 됨 │
│ - 보안 업데이트 한 곳에서 관리 │
│ │
│ 2️⃣ SSO (Single Sign-On) │
│ ──────────────────────── │
│ - 한 번 로그인으로 모든 연결된 앱 접근 │
│ - Keycloak 세션이 유효한 동안 자동 로그인 │
│ - 사용자 경험 대폭 향상 │
│ - Single Logout도 지원 (모든 앱에서 로그아웃) │
│ │
│ 3️⃣ Social Login 쉬운 연동 │
│ ──────────────────────────── │
│ - Google, Facebook, Kakao 등 클릭 몇 번으로 설정 │
│ - 직접 OAuth 연동 코드 작성 불필요 │
│ - Identity Brokering: 외부 IdP를 내부 사용자로 매핑 │
│ │
│ 4️⃣ 기업 디렉토리 연동 │
│ ───────────────────────── │
│ - LDAP, Active Directory 연동 │
│ - 기존 사내 계정 그대로 사용 │
│ - User Federation: 외부 저장소의 사용자 동기화 │
│ │
│ 5️⃣ 세밀한 권한 관리 │
│ ───────────────────── │
│ - Role-Based Access Control (RBAC) │
│ - Fine-grained Authorization: 리소스/스코프 단위 권한 │
│ - Policy 기반 접근 제어 │
│ │
│ 6️⃣ 관리자 콘솔 제공 │
│ ───────────────────── │
│ - 웹 UI로 사용자/클라이언트/역할 관리 │
│ - 직접 관리 기능 구현 불필요 │
│ - 이벤트 로그 조회, 세션 관리, 통계 대시보드 │
│ │
│ 7️⃣ 오픈소스 & 무료 │
│ ───────────────────── │
│ - Apache 2.0 라이선스 │
│ - 상용 지원 필요 시: Red Hat SSO │
│ - 활발한 커뮤니티, 풍부한 문서 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
5. 언제 Keycloak을 도입해야 하나?
5.1 도입이 적합한 상황
┌─────────────────────────────────────────────────────────────────────────────┐
│ Keycloak 도입 권장 상황 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ✅ 다음 상황에서 Keycloak 도입 권장: │
│ │
│ 1. 마이크로서비스 아키텍처 │
│ └── 여러 서비스가 공통 인증 필요 │
│ └── API Gateway + Keycloak 조합 │
│ └── 서비스 간 토큰 기반 인증 │
│ │
│ 2. 여러 애플리케이션 운영 │
│ └── 웹, 모바일, 데스크톱 앱 동시 운영 │
│ └── SSO로 사용자 경험 통일 │
│ └── 일관된 인증 정책 적용 │
│ │
│ 3. B2B 플랫폼 / 멀티테넌트 │
│ └── 고객사별 다른 인증 정책 필요 │
│ └── Realm으로 테넌트 분리 │
│ └── 각 고객사의 IdP 연동 │
│ │
│ 4. Social Login 필요 │
│ └── Google, Kakao, Naver 로그인 │
│ └── 직접 구현 대비 80% 시간 절약 │
│ │
│ 5. 기업 시스템 연동 │
│ └── LDAP/Active Directory 사용 중 │
│ └── SAML 기반 기존 SSO와 연동 │
│ │
│ 6. 보안 요구사항 높음 │
│ └── 2FA/MFA 필요 │
│ └── 비밀번호 정책 강제 │
│ └── 세션 관리, 감사 로그 필수 │
│ │
│ 7. 직접 운영 능력/의지 있음 │
│ └── DevOps 팀 있음 │
│ └── 인프라 직접 관리 가능 │
│ └── 커스터마이징 필요 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
5.2 도입이 과할 수 있는 상황
┌─────────────────────────────────────────────────────────────────────────────┐
│ Keycloak이 과할 수 있는 상황 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ⚠️ 다음 상황에서는 다른 옵션 고려: │
│ │
│ 1. 단일 모놀리식 앱 │
│ └── 서비스가 하나뿐이면 세션 기반 인증으로 충분 │
│ └── Spring Security + Session만으로 해결 가능 │
│ │
│ 2. 소규모 팀 / 초기 스타트업 │
│ └── Keycloak 운영 오버헤드가 큼 │
│ └── 관리형 서비스가 더 효율적일 수 있음 │
│ │
│ 3. 직접 운영 원하지 않음 │
│ └── Auth0, Firebase Auth 등 SaaS 고려 │
│ └── 운영 부담 없음 │
│ │
│ 4. 빠른 MVP 개발이 우선 │
│ └── 나중에 필요할 때 도입 │
│ └── 처음부터 완벽한 인증 불필요 │
│ │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 💡 대안 비교: │
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 솔루션 │ 특징 │ 비용 │ │
│ ├───────────────────────────────────────────────────────────────────┤ │
│ │ Keycloak │ 셀프호스팅, 완전한 커스터마이징 │ 무료 (운영비) │ │
│ │ Auth0 │ SaaS, 빠른 도입, 관리 불필요 │ 유료 │ │
│ │ Firebase │ Google 생태계, 간편함 │ 프리티어 있음 │ │
│ │ AWS Cognito │ AWS 통합, 서버리스 │ 사용량 기반 │ │
│ │ Okta │ 엔터프라이즈급, 풍부한 기능 │ 고가 │ │
│ │ Supabase │ Firebase 대안, PostgreSQL 기반 │ 프리티어 있음 │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
6. 실제 구현 예시
6.1 Spring Boot + Keycloak (Resource Server)
// build.gradle.kts
dependencies {
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
implementation("org.springframework.boot:spring-boot-starter-security")
}
// application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://keycloak.example.com/realms/cck
# 또는 직접 JWK Set URI 지정
# jwk-set-uri: https://keycloak.example.com/realms/cck/protocol/openid-connect/certs
// SecurityConfig.kt
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.authorizeHttpRequests { auth ->
auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/api/**").authenticated()
.anyRequest().authenticated()
}
.oauth2ResourceServer { oauth2 ->
oauth2.jwt { jwt ->
jwt.jwtAuthenticationConverter(keycloakJwtConverter())
}
}
return http.build()
}
@Bean
fun keycloakJwtConverter(): Converter<Jwt, AbstractAuthenticationToken> {
return KeycloakJwtAuthenticationConverter()
}
}
// KeycloakJwtAuthenticationConverter.kt
// Keycloak의 realm_access.roles를 Spring Security 권한으로 변환
class KeycloakJwtAuthenticationConverter : Converter<Jwt, AbstractAuthenticationToken> {
override fun convert(jwt: Jwt): AbstractAuthenticationToken {
val authorities = extractRoles(jwt)
return JwtAuthenticationToken(jwt, authorities, jwt.getClaimAsString("preferred_username"))
}
private fun extractRoles(jwt: Jwt): Collection<GrantedAuthority> {
val realmAccess = jwt.getClaimAsMap("realm_access") ?: return emptyList()
val roles = realmAccess["roles"] as? List<*> ?: return emptyList()
return roles
.filterIsInstance<String>()
.map { SimpleGrantedAuthority("ROLE_${it.uppercase()}") }
}
}
// Controller에서 사용
@RestController
@RequestMapping("/api/user")
class UserController {
@GetMapping("/me")
fun getCurrentUser(authentication: JwtAuthenticationToken): UserInfo {
val jwt = authentication.token
return UserInfo(
id = jwt.getClaimAsString("sub"),
username = jwt.getClaimAsString("preferred_username"),
email = jwt.getClaimAsString("email"),
roles = authentication.authorities.map { it.authority }
)
}
}
6.2 React + Keycloak 연동
// keycloak.ts
import Keycloak from 'keycloak-js';
const keycloak = new Keycloak({
url: 'https://keycloak.example.com',
realm: 'cck',
clientId: 'accio-frontend'
});
export default keycloak;
// App.tsx
import { ReactKeycloakProvider } from '@react-keycloak/web';
import keycloak from './keycloak';
function App() {
return (
<ReactKeycloakProvider
authClient={keycloak}
initOptions=
onEvent={(event, error) => {
console.log('Keycloak event:', event, error);
}}
>
<RouterProvider router={router} />
</ReactKeycloakProvider>
);
}
// useAuth.ts - 커스텀 훅
import { useKeycloak } from '@react-keycloak/web';
export function useAuth() {
const { keycloak, initialized } = useKeycloak();
return {
isAuthenticated: keycloak.authenticated,
user: keycloak.tokenParsed,
token: keycloak.token,
login: () => keycloak.login(),
logout: () => keycloak.logout({ redirectUri: window.location.origin }),
hasRole: (role: string) => keycloak.hasRealmRole(role),
initialized
};
}
// api.ts - API 호출 시 토큰 포함
import keycloak from './keycloak';
const api = {
async fetch(url: string, options: RequestInit = {}) {
// 토큰 만료 임박 시 자동 갱신
if (keycloak.isTokenExpired(30)) {
await keycloak.updateToken(30);
}
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${keycloak.token}`,
'Content-Type': 'application/json'
}
});
}
};
export default api;
// ProtectedRoute.tsx
import { useAuth } from './useAuth';
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ children, requiredRole }: Props) {
const { isAuthenticated, hasRole, initialized } = useAuth();
if (!initialized) {
return <div>Loading...</div>;
}
if (!isAuthenticated) {
return <Navigate to="/login" />;
}
if (requiredRole && !hasRole(requiredRole)) {
return <Navigate to="/unauthorized" />;
}
return children;
}
7. 요약
┌─────────────────────────────────────────────────────────────────────────────┐
│ OAuth 2.0 & Keycloak 핵심 요약 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 📚 OAuth 2.0 │
│ ───────────── │
│ - 2012년 표준화된 인가(Authorization) 프레임워크 │
│ - "비밀번호 공유 없이 제3자 앱에 제한된 권한 부여" │
│ - 4가지 역할: Resource Owner, Client, Auth Server, Resource Server │
│ - 토큰: Authorization Code, Access Token, Refresh Token │
│ - 권장 Grant Type: Authorization Code + PKCE │
│ │
│ 📚 OpenID Connect (OIDC) │
│ ───────────────────────── │
│ - OAuth 2.0 위의 인증(Authentication) 레이어 │
│ - ID Token (JWT)으로 "사용자가 누구인지" 확인 │
│ - 표준화된 UserInfo Endpoint │
│ - Discovery로 자동 설정 조회 │
│ │
│ 📚 Keycloak │
│ ───────────── │
│ - Red Hat의 오픈소스 IAM 솔루션 │
│ - OAuth 2.0 + OIDC + SAML + SSO + 사용자 관리 통합 │
│ - 핵심 개념: Realm, Client, User, Role, Group │
│ │
│ 장점: │
│ ✓ 인증 로직 중앙화 │
│ ✓ SSO (Single Sign-On) │
│ ✓ Social Login 쉬운 연동 │
│ ✓ LDAP/AD 연동 │
│ ✓ 세밀한 권한 관리 (RBAC) │
│ ✓ 관리자 콘솔 제공 │
│ ✓ 무료 & 오픈소스 │
│ │
│ 🎯 도입 시점 │
│ ────────────── │
│ ✅ 마이크로서비스 아키텍처 │
│ ✅ 여러 앱에서 공통 인증/SSO 필요 │
│ ✅ Social Login / 기업 디렉토리 연동 │
│ ✅ 직접 운영 능력 및 의지 있음 │
│ │
│ ⚠️ 단일 앱, 소규모 팀 → 세션 기반 또는 SaaS 고려 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
참고 자료
- OAuth 2.0 RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749
- OpenID Connect 명세: https://openid.net/specs/openid-connect-core-1_0.html
- Keycloak 공식 문서: https://www.keycloak.org/documentation
- Spring Security OAuth2: https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html
관련 키워드
OAuth 2.0, OpenID Connect, OIDC, Keycloak, SSO, Single Sign-On, JWT, Access Token, Refresh Token, ID Token, Authorization Code, PKCE, Grant Type, Realm, Client, IAM, 인증, 인가, Identity Provider, RBAC