Phase 1: 왜 만들어졌는가 — 비밀번호 공유의 위험
2010년 이전 웹 애플리케이션 시대를 상상해보세요. 트위터에 자동으로 글을 올려주는 서드파티 앱을 사용하려면, 그 앱에 트위터 아이디와 비밀번호를 직접 입력해야 했습니다. 이는 심각한 보안 문제를 야기했습니다.
기존 방식의 치명적 결함
비밀번호 직접 공유 방식은 다음과 같은 문제를 가졌습니다:
- 전체 권한 노출: 제3자 앱이 사용자 계정의 모든 권한을 얻게 됨
- 비밀번호 저장 위험: 서드파티 앱이 사용자 비밀번호를 저장해야 함
- 선택적 권한 불가: 특정 기능만 허용할 수 없음
- 권한 취소 불가: 비밀번호를 바꾸지 않으면 앱의 접근을 막을 수 없음
사용자가 여러 서드파티 앱을 사용한다면, 그만큼 많은 곳에 비밀번호를 맡겨야 했고, 그 중 한 곳이라도 해킹당하면 모든 계정이 위험해졌습니다.
한 줄 요약: OAuth 2.0 Authorization Code Flow는 비밀번호 공유 없이 제한된 권한을 안전하게 위임하기 위해 만들어졌습니다.
Phase 2: 핵심 개념 정의
한 문장 정의
OAuth 2.0 Authorization Code Flow는 사용자가 비밀번호를 공유하지 않고도 제3자 애플리케이션이 특정 권한으로 사용자 데이터에 접근할 수 있게 하는 인증 방식입니다.
공식 정의
The authorization code grant type is used to obtain both access tokens and refresh tokens and is optimized for confidential clients. Since this is a redirection-based flow, the client must be capable of interacting with the resource owner's user-agent (typically a web browser) and capable of receiving incoming requests (via redirection) from the authorization server. — RFC 6749, Section 4.1
RFC 정의를 풀어보면, Authorization Code Grant는 액세스 토큰과 리프레시 토큰을 모두 얻을 수 있으며, 클라이언트 비밀을 안전하게 보관할 수 있는 기밀 클라이언트에 최적화된 방식입니다.
핵심 구성 요소
- Resource Owner (리소스 소유자): 보호된 리소스에 대한 액세스를 부여할 수 있는 엔티티 (보통 사용자)
- Client (클라이언트): 리소스 소유자를 대신하여 보호된 리소스 요청을 하는 애플리케이션
- Authorization Server (권한 부여 서버): 리소스 소유자를 성공적으로 인증하고 권한을 얻은 후 클라이언트에게 액세스 토큰을 발행하는 서버
- Resource Server (리소스 서버): 보호된 리소스를 호스팅하고 액세스 토큰을 사용하여 보호된 리소스 요청을 수락하고 응답할 수 있는 서버
관련 용어 비교표
| 용어 | 설명 |
|---|---|
| Authorization Code | 액세스 토큰 교환용 임시 코드. 일회성이며 수 분 내 만료 |
| Access Token | API 호출 시 사용하는 토큰. 제한된 권한(scope), 1-24시간 유효 |
| Refresh Token | 액세스 토큰 갱신용 장기 토큰. 30일-1년 유효 |
| Client Secret | 클라이언트 앱 인증용 비밀키. 서버에만 저장, 수동 갱신 |
Phase 3: 동작 원리 — 내부에서 일어나는 일
전체 흐름 개요
OAuth 2.0 Authorization Code Flow는 이중 인증 구조 를 가집니다. 먼저 사용자가 권한을 부여하면 임시 코드를 받고, 이 코드를 실제 토큰으로 교환하는 2단계 과정을 거칩니다.
이 흐름에서 핵심은 5단계와 6단계의 분리 입니다. Authorization Code(5단계)만으로는 리소스에 접근할 수 없고, 반드시 Client Secret과 함께 토큰 교환(6단계)을 거쳐야 합니다. 이 이중 구조가 OAuth 2.0 Authorization Code Flow의 보안 핵심입니다.
1단계: 권한 부여 요청 (Authorization Request)
사용자가 제3자 앱에서 "Google로 로그인" 버튼을 클릭하면, 앱은 사용자를 Google의 권한 부여 서버로 리다이렉트합니다.
GET /oauth/authorize?
response_type=code&
client_id=s6BhdRkqt3&
state=xyz&
redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb&
scope=read_profile
Host: authorization-server.com
각 파라미터의 역할:
response_type=code: Authorization Code를 요청한다는 의미client_id: 미리 등록된 클라이언트 앱의 식별자state: CSRF 공격 방지용 랜덤 값redirect_uri: 인증 완료 후 돌아올 주소scope: 요청하는 권한 범위
2단계: 사용자 인증 및 권한 승인
권한 부여 서버는 사용자에게 로그인 화면과 권한 승인 화면을 보여줍니다. 사용자가 승인하면 Authorization Code가 생성됩니다.
3단계: Authorization Code 반환
HTTP/1.1 302 Found
Location: https://client.example.com/cb?
code=SplxlOBeZQQYbYS6WxSbIA&
state=xyz
핵심 보안 특징:
code는 일회성 이며 짧은 시간 내에 사용해야 함 (RFC 6749 Section 4.1.2에서는 최대 10분을 권고)state값이 1단계와 동일한지 검증하여 CSRF 공격 방지- Authorization Code 자체로는 아무것도 할 수 없음 (추가 인증 필요)
4단계: 액세스 토큰 교환 (Token Exchange)
클라이언트 앱은 받은 Authorization Code와 자신의 Client Secret 을 함께 권한 부여 서버에 전송합니다.
POST /oauth/token HTTP/1.1
Host: authorization-server.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=SplxlOBeZQQYbYS6WxSbIA&
redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb&
client_id=s6BhdRkqt3&
client_secret=gX1fBat3bV
왜 Client Secret이 필요한가:
- Authorization Code만으로는 부족함
- 클라이언트 앱의 정당성을 추가로 검증
- 중간자 공격(MITM)으로 코드를 탈취해도 토큰 교환 불가
5단계: 액세스 토큰 발급
{
"access_token": "2YotnFZFEjr1zCsicMWpAA",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
"scope": "read_profile"
}
토큰 구조 분석:
access_token: 실제 API 호출에 사용하는 토큰expires_in: 3600초(1시간) 후 만료refresh_token: 액세스 토큰 갱신용 장기 토큰scope: 실제로 부여된 권한 범위
6단계: API 호출
GET /api/profile HTTP/1.1
Host: resource-server.com
Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA
리소스 서버는 액세스 토큰을 검증하고 요청된 리소스를 반환합니다.
일상 비유로 이해하기
OAuth 2.0 Authorization Code Flow는 호텔 키카드 발급 과정 과 비슷합니다:
- 프론트 데스크 방문: 사용자가 권한 부여 서버에 접근
- 신분증 확인: 사용자 인증 (로그인)
- 임시 영수증 발급: Authorization Code 발급
- 키카드 교환: 영수증 + 예약 확인서(Client Secret)로 실제 키카드(Access Token) 받기
- 객실 입장: 키카드로 보호된 리소스 접근
임시 영수증(Authorization Code)만으로는 객실에 들어갈 수 없고, 반드시 예약 확인서와 함께 제시해야 키카드를 받을 수 있는 구조입니다.
토큰 검증 흐름
리소스 서버가 액세스 토큰을 받으면 어떻게 검증할까요?
토큰 검증은 다음 순서로 진행됩니다:
- 헤더 확인:
Authorization: Bearer <token>형식인지 검사 - 토큰 파싱: JWT인 경우 서명 검증, 불투명 토큰인 경우 인트로스펙션 엔드포인트 호출
- 만료 시간 확인:
exp클레임이 현재 시간 이후인지 검사 - Scope 검증: 요청된 리소스에 접근할 권한이 있는지 확인
토큰 갱신 과정 (Refresh Token 사용)
액세스 토큰이 만료되면 Refresh Token으로 새로운 액세스 토큰을 발급받습니다:
POST /oauth/token HTTP/1.1
Host: authorization-server.com
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&
refresh_token=tGzv3JOkF0XG5Qx2TlKWIA&
client_id=s6BhdRkqt3&
client_secret=gX1fBat3bV
Refresh Token의 보안 설계:
- 사용자 재인증 없이 토큰 갱신 가능
- 액세스 토큰보다 훨씬 긴 수명 (보통 30일-1년)
- 탈취 시 피해를 줄이기 위해 토큰 로테이션 적용 (새 Refresh Token 발급)
엣지 케이스와 변형
PKCE (Proof Key for Code Exchange) 확장
모바일 앱이나 SPA(Single Page Application)처럼 Client Secret을 안전하게 저장할 수 없는 환경에서는 PKCE 를 사용합니다:
# 1단계: code_challenge 생성
GET /oauth/authorize?
response_type=code&
client_id=s6BhdRkqt3&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
code_challenge_method=S256&
redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
# 2단계: code_verifier로 검증
POST /oauth/token
grant_type=authorization_code&
code=SplxlOBeZQQYbYS6WxSbIA&
code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
PKCE는 Client Secret 대신 동적으로 생성된 code_challenge/code_verifier 쌍 을 사용하여 보안을 강화합니다.
왜 Authorization Code를 바로 사용하지 않는가?
Authorization Code Flow의 핵심 질문: "왜 Authorization Server가 바로 Access Token을 주지 않고 굳이 Code를 거치는가?"
이유 1: 프론트채널 vs 백채널 분리
- Authorization Code는 브라우저 리다이렉트(프론트채널)로 전달됨 → 노출 위험
- Access Token 교환은 서버 간 직접 통신(백채널)으로 수행 → 안전
이유 2: Client 인증
- Authorization Code만으로는 어떤 앱이 요청했는지 확인 불가
- Token 교환 시 Client Secret을 함께 제출하여 앱의 정당성 검증
이유 3: 일회성 보장
- Authorization Code는 한 번 사용되면 즉시 무효화
- 코드 탈취 시에도 이미 사용된 후라면 공격 실패
브라우저 (프론트채널) 서버 (백채널)
│ │
│ ←── Authorization Code ───│ (노출 가능하지만 단독 사용 불가)
│ │
│ │──→ Code + Client Secret ──→ Auth Server
│ │←── Access Token ←─────────────│
│ │ (안전한 서버 간 통신)
Phase 4: 보안 관점 — 공격자는 이걸 어떻게 보는가
공격자가 노리는 약점
OAuth 2.0 Authorization Code Flow에서 공격자는 주로 Authorization Code 탈취 와 토큰 교환 과정 조작 을 노립니다. 이 흐름의 핵심 약점은 브라우저 리다이렉트 에 의존한다는 점입니다.
1) Redirect URI 검증 우회 공격
CVE-2023-6927: Keycloak 오픈 리다이렉트 취약점
공격 시나리오: Keycloak < 23.0.4 버전에서 와일드카드로 끝나는 redirect URI를 가진 OAuth 2.0 클라이언트는 redirect URI 검증을 우회할 수 있었습니다.
# 정상적인 redirect URI
https://app.example.com/callback
# 공격자가 조작한 URI
https://app.example.com/callback/../../../evil.com/steal
공격 과정:
- 공격자가 조작된 redirect URI로 OAuth 요청 생성
- 피해자가 정상적으로 로그인하고 권한 승인
- Authorization Code가 공격자 제어 도메인으로 전송
- 공격자가 탈취한 코드로 액세스 토큰 교환
방어 방법:
- Redirect URI 정확한 매칭 검증 (와일드카드 금지)
- 허용된 URI 목록을 화이트리스트 방식으로 관리
- URI 정규화 후 검증 수행
2) Authorization Code 중간 탈취
CVE-2023-28131: Expo Auth Session 라이브러리 취약점
공격 메커니즘: expo-auth-session 라이브러리에서 authorization code가 안전하지 않은 채널을 통해 전송되어 공격자가 탈취할 수 있었습니다.
// 취약한 코드 예시
const result = await AuthSession.startAsync({
authUrl: `https://provider.com/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}`,
// returnUrl이 안전하지 않게 처리됨
});
// 공격자가 authorization code 탈취 후 토큰 교환
const tokenResponse = await fetch('https://provider.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `grant_type=authorization_code&code=${stolenCode}&client_id=${clientId}`
});
피해 범위:
- Facebook, Google, Twitter 등 주요 플랫폼 계정 탈취
- 신용카드 정보 접근
- 완전한 계정 장악
3) ConsentFix 공격 (2025년 발견)
공격 원리: ConsentFix는 OAuth 2.0 authorization code flow를 악용하여 Microsoft Entra ID의 장치 규정 준수 확인과 조건부 액세스 정책을 우회합니다.
# 공격자가 생성한 악의적 로그인 URI
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?
client_id=legitimate_client_id&
response_type=code&
redirect_uri=http://localhost:8080/callback&
scope=https://graph.microsoft.com/.default&
state=malicious_state
공격 단계:
- 공격자가 합법적인 Microsoft Entra 로그인 URI 생성
- localhost redirect를 이용하여 피해자로부터 authorization code 추출
- 탈취한 코드로 권한 있는 access token 획득
- 장치 정책과 조건부 액세스 우회
4) State 파라미터 누락 (CSRF 공격)
공격 코드:
<!-- 공격자가 피해자에게 전송하는 악의적 링크 -->
<a href="https://oauth-provider.com/authorize?
response_type=code&
client_id=victim_app_id&
redirect_uri=https://victim-app.com/callback&
scope=read_write
<!-- state 파라미터 의도적 누락 -->
">무료 쿠폰 받기</a>
피해자가 이미 OAuth 제공자에 로그인된 상태라면, 공격자의 계정이 피해자의 OAuth 앱 계정과 연결됩니다.
올바른 구현 체크리스트
Authorization Server 측
- Redirect URI 정확한 매칭: 와일드카드나 부분 매칭 금지
- Authorization Code 수명: RFC 권고에 따라 최대 10분 이내로 제한
- Code 일회성 보장: 사용된 코드는 즉시 무효화
- State 파라미터 검증: 요청-응답 간 state 값 일치 확인
Client 측
- Client Secret 보호: 서버 사이드에만 저장, 브라우저/앱에 노출 금지
- HTTPS 강제: 모든 OAuth 통신에 TLS 적용
- PKCE 적용: 공개 클라이언트(모바일/SPA)에서 반드시 사용
- 토큰 안전 저장: 액세스 토큰을 안전한 저장소에 보관
Phase 5: 정리와 연결
OAuth 2.0 Authorization Code Flow는 비밀번호 공유 없이 안전한 권한 위임을 구현하는 표준 방식으로, 이중 인증 구조를 통해 보안을 강화했지만 구현 과정에서 다양한 취약점이 발생할 수 있어 신중한 설계가 필요합니다.
현대 웹 보안에서 OAuth는 핵심 인증 인프라가 되었지만, 네트워크 ACL 우회: 방화벽 정책을 무력화하는 원리에서 다룬 것처럼 보안 정책 우회 공격의 대상이 되기도 합니다. 특히 다운그레이드 공격: 보안 기능이 무력화되는 원리에서 설명한 보안 기능 무력화 패턴은 OAuth 구현에서도 자주 발견됩니다.
관련 글
OAuth 2.0에서 사용하는 토큰의 구조를 이해하려면 JWT를 알아야 합니다. JWT 동작 원리와 보안 취약점에서 토큰의 서명 검증 원리와 보안 취약점을 확인해보세요.
다음에 알아볼 심화 주제:
- OpenID Connect (OIDC): OAuth 2.0 위에 구축된 신원 인증 레이어
- OAuth 2.1: 현재 개발 중인 차세대 OAuth 스펙
참고 자료
- RFC 6749: The OAuth 2.0 Authorization Framework — OAuth 2.0 공식 표준 문서
- CVE-2023-6927 - Keycloak Redirect URI Validation Bypass
- CVE-2023-28131 - Expo Auth Session Library Vulnerability
- ConsentFix: OAuth Attack on Microsoft Entra ID
안내 및 법적 고지
AI 활용 안내 이 글은 AI(Claude)의 도움을 받아 작성되었습니다. 기술적 내용은 공식 문서, RFC, 신뢰할 수 있는 자료를 기반으로 하며, 보안 교육 목적으로 제공됩니다.
면책 조항 본 글은 보안 인식 제고를 위한 교육 목적으로 작성되었습니다. 언급된 공격 기법을 실제로 시도하는 행위는 「정보통신망법」, 「형법」 등에 따라 처벌받을 수 있으며, 본 블로그는 이에 대한 법적 책임을 지지 않습니다.
COMMENTS (0)
댓글은 익명으로 작성되며, 삭제 비밀번호를 설정하면 본인만 삭제할 수 있습니다. 비밀번호를 설정하지 않은 댓글은 누구나 삭제할 수 있습니다.