OAuth 2.0 및 OIDC 아키텍처 심층 분석과 JWT 보안 전략

Access-Control-Allow-Origin: * 설정만큼이나 위험한 것이 잘못 구현된 인증 로직입니다. 특히 SPA(Single Page Application)나 모바일 앱 환경에서 레거시 방식인 Implicit Grant Flow를 여전히 사용하고 있거나, Access Token을 LocalStorage에 평문으로 저장하여 XSS(Cross-Site Scripting) 공격 표면을 넓히는 사례가 빈번합니다. 프로덕션 레벨의 인증 시스템은 단순한 로그인 성공 여부를 넘어, 토큰의 생명주기 관리와 전송 보안을 아키텍처 수준에서 보장해야 합니다.

OAuth 2.0과 OIDC의 결정적 차이: 인가와 인증

OAuth 2.0은 본질적으로 인가(Authorization) 프레임워크입니다. 즉, "이 사용자가 리소스에 접근할 권한이 있는가?"를 다룹니다. 반면, OIDC(OpenID Connect)는 OAuth 2.0 위에 구축된 인증(Authentication) 레이어로, "이 사용자가 누구인가?"를 식별합니다.

많은 엔지니어들이 Access Token을 마치 사용자 신분증처럼 취급하는 오류를 범합니다. Access Token은 특정 리소스 서버에 접근하기 위한 '열쇠'일 뿐이며, 사용자 정보를 담는 표준 규격이 아닙니다. 사용자 신원 확인은 OIDC를 통해 발급받은 ID Token을 통해 수행되어야 합니다.

구분 Access Token (OAuth 2.0) ID Token (OIDC)
목적 리소스 접근 권한 위임 사용자 신원 확인 및 프로필 정보
수신자 Resource Server (API) Client Application
포맷 Opaque(불투명) 또는 JWT 반드시 JWT (JSON Web Token)
보안 검증 Introspection Endpoint 또는 서명 검증 클라이언트에서 서명 및 클레임 검증

Authorization Code Grant Flow 상세 분석 및 PKCE

보안상의 이유로 Implicit Flow는 더 이상 권장되지 않으며, OAuth 2.1 초안에서는 완전히 제거되었습니다. 현대적인 애플리케이션은 반드시 Authorization Code Grant Flow with PKCE (Proof Key for Code Exchange)를 사용해야 합니다. PKCE는 code_verifiercode_challenge를 사용하여 Authorization Code 탈취 공격(Authorization Code Interception Attack)을 방지합니다.

PKCE 흐름은 다음과 같은 메커니즘으로 동작합니다:

  1. Client: 암호화된 랜덤 문자열 code_verifier 생성.
  2. Client: code_verifier를 SHA-256으로 해싱하여 code_challenge 생성.
  3. Authorization Request: code_challenge와 함께 인가 요청 전송.
  4. Authorization Server: 사용자 인증 후 Authorization Code 발급 (이 시점에 code_challenge 저장).
  5. Token Request: 클라이언트는 Authorization Code와 원본 code_verifier를 전송.
  6. Validation: 서버는 수신한 code_verifier를 해싱하여 저장된 code_challenge와 비교. 일치할 경우에만 토큰 발급.

모바일 앱에서의 PKCE 적용 방법: 모바일 앱은 커스텀 URL 스킴을 사용하므로 다른 악성 앱이 리다이렉트 URI를 가로챌 위험이 웹보다 높습니다. 따라서 네이티브 앱 환경에서는 PKCE가 선택이 아닌 필수 사항입니다.

Node.js를 이용한 PKCE 챌린지 생성 예제

const crypto = require('crypto');

// 1. Code Verifier 생성 (랜덤 문자열)
function generateCodeVerifier() {
    return base64URLEncode(crypto.randomBytes(32));
}

// 2. Code Challenge 생성 (SHA256 해싱)
function generateCodeChallenge(verifier) {
    const hash = crypto.createHash('sha256').update(verifier).digest();
    return base64URLEncode(hash);
}

// Base64URL 인코딩 (Padding 제거, +, / 치환)
function base64URLEncode(str) {
    return str.toString('base64')
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
}

const verifier = generateCodeVerifier();
const challenge = generateCodeChallenge(verifier);

console.log(`Verifier: ${verifier}`);
console.log(`Challenge: ${challenge}`);
// Verifier는 세션이나 로컬 스토리지에 임시 저장 후 토큰 교환 시 사용

JWT 토큰 저장 및 탈취 방지 전략

Stateless한 인증을 위해 JWT(JSON Web Token)를 사용할 때 가장 큰 논쟁거리는 "어디에 저장할 것인가"입니다. 브라우저의 LocalStorage는 JavaScript로 쉽게 접근 가능하므로, XSS 취약점이 하나라도 발생하면 토큰이 즉시 탈취됩니다.

보안 모범 사례: HttpOnly Cookie와 SameSite

가장 강력한 보안 전략은 Access Token과 Refresh Token을 브라우저 스크립트가 접근할 수 없는 HttpOnly Cookie에 저장하는 것입니다. 이 방식은 CSRF(Cross-Site Request Forgery) 공격에 노출될 수 있으나, 이는 SameSite=Strict 속성과 CSRF 토큰을 병행하여 방어할 수 있습니다.

JWT 파싱 주의사항: 클라이언트 측에서 JWT 페이로드(Payload)를 읽어야 할 경우(예: 사용자 이름 표시), 토큰 자체는 쿠키에 저장하되, 별도의 엔드포인트를 통해 사용자 정보를 조회하거나 ID Token의 내용을 비보안 쿠키로 병행 전달하는 방식을 고려해야 합니다.

Refresh Token Rotation 전략

Refresh Token이 탈취될 경우 공격자는 Access Token이 만료되어도 지속적으로 새로운 토큰을 발급받을 수 있습니다. 이를 방지하기 위해 Refresh Token Rotation을 구현해야 합니다.

  • 클라이언트가 Refresh Token으로 Access Token을 재발급받을 때, 서버는 새로운 Refresh Token도 함께 발급합니다.
  • 이전에 사용된 Refresh Token은 즉시 무효화(Invalidate) 처리합니다.
  • 만약 이미 사용된(무효화된) Refresh Token으로 재요청이 들어올 경우, 서버는 해당 사용자 계열의 모든 활성 토큰을 강제로 만료시키는 탐지 로직을 가동해야 합니다.

알고리즘 혼동 공격 (Algorithm Confusion Attack): JWT 검증 시 헤더의 alg 필드를 맹신해서는 안 됩니다. 공격자가 alg: none으로 조작하거나, RSA 공개키를 HMAC 비밀키로 둔갑시켜 서명을 위조할 수 있습니다. 검증 로직에서는 반드시 허용된 알고리즘(예: RS256)을 명시적으로 고정해야 합니다.

OAuth 2.1 변경 사항 및 보안 강화

OAuth 2.1은 기존 2.0의 보안 권고안(BCP)을 통합하여 표준을 단순화하고 강화하는 것을 목표로 합니다. 주요 변경점은 다음과 같습니다:

  • PKCE 필수화: 모든 클라이언트(Public 및 Confidential)는 Authorization Code Flow 사용 시 PKCE를 반드시 적용해야 합니다.
  • Implicit Grant 제거: Access Token이 브라우저 URL Fragment에 노출되는 Implicit Flow는 공식적으로 스펙에서 제외됩니다.
  • Resource Owner Password Credentials Grant 제거: 사용자가 자신의 비밀번호를 클라이언트에 직접 입력하는 방식은 더 이상 지원되지 않습니다.

단일 로그인(SSO) 구현 가이드나 마이크로서비스 간의 트러스트 모델을 설계할 때, OAuth 2.0과 OIDC는 단순한 선택지가 아니라 필수적인 백본입니다. 보안은 경계선에서 끝나는 것이 아니라, 토큰이 흐르는 모든 파이프라인에서 지속적으로 검증되어야 함을 명심해야 합니다.

Post a Comment