OAuth 2.0 Authorization Code Grant와 PKCE

양성준·2025년 6월 28일

스프링

목록 보기
42/49

OAuth 2.0의 주요 컴포넌트

OAuth 2.0은 네 가지 주요 구성 요소(컴포넌트)로 이루어진다:

  • 리소스 소유자(Resource Owner): 보호된 자원의 소유자(일반적으로 사용자).
  • 클라이언트(Client): 리소스 소유자를 대신해 보호된 자원에 접근하려는 애플리케이션(웹, 모바일 앱 등).
  • 리소스 서버(Resource Server): 보호된 자원을 실제로 보관하고 있는 서버(API 서버 등).
  • 권한 부여 서버(Authorization Server): 인증 및 권한 부여를 수행하고, 액세스 토큰을 발급하는 서버.

Authorization Code Grant 흐름

Authorization Code Grant는 OAuth 2.0에서 가장 널리 사용되는 권한 부여 방식으로, 특히 보안이 중요한 웹/모바일 앱에서 표준적으로 채택된다.

1) 클라이언트 등록

  • 클라이언트(애플리케이션)는 권한 부여 서버에 등록되어 client_id, client_secret, redirect_uri를 발급받음.

2) 사용자 인증 및 권한 부여 요청

  • 사용자가 클라이언트에서 "로그인" 또는 "연동" 버튼을 클릭하면, 클라이언트는 권한 부여 서버(Authorization server)로 리다이렉트한다.
  • 이때 client_id, redirect_uri, scope, state 등의 정보가 포함됨.
  • 사용자는 권한 부여 서버에서 로그인 및 권한 범위(scope) 허용 여부를 결정.

3) Authorization Code 발급

  • 사용자가 인증 및 권한을 허용하면, 권한 부여 서버는 redirect_uri로 짧은 수명의 authorization code를 전달한다.

4) Access Token 요청 및 발급

  • 클라이언트는 받은 authorization code, client_id, client_secret, redirect_uri를 권한 부여 서버에 POST로 전송해 access token을 요청한다.
  • 권한 부여 서버는 코드의 유효성을 검증한 뒤 access token(및 필요 시 refresh token)을 발급한다.
  • 백채널(server-to-server) 통신 사용: 사용자의 브라우저를 거치지 않고, 클라이언트 서버가 직접 Authorization Server에 POST 요청을 보내는 방식
    • 이렇게 하면 Access Token이 사용자 브라우저에 절대 노출되지 않음
    • Access Token 발급에는 client_secret(클라이언트 비밀 키) 같은 민감 정보가 포함되어 노출되면 위험하다.
    • Access Token 자체는 공개될 수 있지만, 그걸 "얻는 방법"은 반드시 안전해야 함
      • 비밀 키가 탈취된다면 언제든 위조 가능 / API 요청은 프론트 채널 통신으로 이뤄지지만, Access Token의 유효시간이 짧기 때문에 탈취되어도 어느정도 안전.

5) 자원 접근

  • 클라이언트는 발급받은 access token을 사용해 리소스 서버의 보호된 API에 접근.
  • 리소스 서버는 access token의 유효성을 검증한 뒤 자원을 제공한다.
클라이언트 → 권한 부여 서버: 권한 요청 (사용자 인증 및 동의)
권한 부여 서버 → 클라이언트: Authorization Code 전달 (redirect)
클라이언트 → 권한 부여 서버: Authorization Code로 Access Token 요청
권한 부여 서버 → 클라이언트: Access Token 발급
클라이언트 → 리소스 서버: Access Token으로 보호 자원 요청
리소스 서버 → 클라이언트: 보호 자원 제공

장점 (Authorization Code)

  • Access Token 교환은 클라이언트와 권한 부여 서버 간의 백채널(서버 대 서버 통신)에서만 이루어져, 민감 정보(비밀 키 등) 노출을 최소화함.
  • Refresh Token 발급 가능
    • Authorization Code 방식은 장기 인증 유지를 위한 Refresh Token 발급을 허용
    • Implicit Flow 등 다른 방식에선 Refresh Token 발급이 불가능하거나 제한됨
  • 세분화된 권한 제어 가능 (Scope)
    • 토큰 발급 시 scope를 지정해 특정 리소스/권한만 허용 가능
    • 예: 이메일만 읽기, 캘린더만 조회 등
  • 확장성과 실무 호환성
    • Spring Security, Express, Django 등 주요 프레임워크에서 지원도 안정적
  • PKCE와 결합 시 SPA/모바일(Public client)에서도 안전하게 사용 가능
    • SPA/모바일 환경에선 JS 코드나 .apk 파일이 유저에게 공개되기 때문에 클라이언트 시크릿 사용 불가능
    • Authorization Code + PKCE를 사용하면 클라이언트 시크릿 없이도 안전하게 토큰 발급 가능

PKCE (Proof Key for Code Exchange)

: client secret을 사용하는게 아닌, 인증 요청 시 생성한 임시 비밀번호(code_verifier)를 서버에 증명하여, 인가 코드 탈취를 막는 메커니즘
모바일 앱이나 SPA처럼 client Secret을 안전하게 저장하기 어려운 환경에서 인증 코드 탈취 공격(Authorization Code Interception Attack)을 방지하기 위해 사용

PKCE의 필요성

  • 모바일이나 SPA 환경에서는 client secret을 안전하게 보관할 수 없기 때문에, 공격자가 인증 코드(authorization code)를 가로채면 토큰을 탈취할 위험이 있다.

PKCE의 목적:

  • 인증 코드가 탈취되더라도, 추가적으로 필요한 정보(code_verifier)가 없으면 토큰을 발급받지 못하도록 하여 보안을 강화한다.

PKCE 동작 원리

1) code_verifier 생성
: 클라이언트(앱)는 인증 요청 전에 암호학적으로 안전한 무작위 문자열인 code_verifier를 생성.

2) code_challenge 생성
: code_verifier를 변환(주로 SHA-256 해시 후 Base64URL 인코딩)하여 code_challenge를 만든다.
변환 방식은 S256(권장) 또는 plain.

3) 인증 요청
: 클라이언트는 인증 요청 시 code_challenge와 code_challenge_method를 함께 전송.

4) 사용자 인증 및 인가 코드 발급
: 인증 서버는 인증 절차를 거쳐 인가 코드(authorization code)를 클라이언트에 반환한다.

5) 토큰 요청
: 클라이언트는 인가 코드와 함께 최초에 생성한 code_verifier를 토큰 엔드포인트로 전송함.

6) 서버의 검증 및 토큰 발급
: 인증 서버는 받은 code_verifier를 동일 방식으로 변환하여, 최초 요청 때 받은 code_challenge와 비교한다.
=> 값이 일치하면 토큰을 발급하고, 일치하지 않으면 거부.

PKCE의 보안 효과

  • 인증 코드 탈취 공격 방지:

    • 공격자가 중간에서 인증 코드를 가로채더라도, code_verifier를 모르면 토큰을 얻을 수 없다.
    • client secret은 항상 고정돼있어서 탈취에 취약하지만, code_verifier는 요청마다 새로 만들어져서 탈취에 강하다. (일회성 시크릿)
      • 만약 탈취되더라도, 해당 요청에서만 유효함, 재사용 불가능
      • 이 구조 덕분에 client secret보다 훨씬 강력한 보안을 제공!
  • 클라이언트 시크릿 없이도 안전:
    클라이언트 시크릿을 저장하지 않아도 안전하게 인증 과정을 진행할 수 있다.

  • 모바일·SPA 등 퍼블릭 클라이언트에 필수:
    별도의 시크릿 없이도 안전하게 OAuth 2.0 인증을 구현할 수 있다.

profile
백엔드 개발자를 꿈꿉니다.

0개의 댓글