혼자서도 만드는 OAuth2 Provider (1)

Bellmin·2025년 11월 9일
post-thumbnail

OAuth2 Provider란, 우리가 손쉽게 사용하는 카카오 로그인 하기 같은 기능을 제공하는 서버라고 생각하면 되겠다.

즉, 내가 자체적으로 개발한 서비스가 있고 회원이 존재할 때, 나만의 OAuth2 를 만들 수 있는 것이다.

스프링에서는 Spring Authorization Server (SAS) 라는 프레임워크를 사용하면, 손쉽게 OAuth2 및 OIDC 스펙을 구현할 수 있다.

오늘은 Spring Authorization Server에 대해서 알아보자.

Spring Authorization Server

  • Spring에서 제공하는 OAuth2.1 과 OIDC1.0의 명세에 맞게 구현할 수 있도록 인증/인가 등의 기능을 제공하는 프레임워크.
  • Spring Security를 기반으로 하여 시큐리티 필터체인에서 동작한다.

줄여서 SAS 라고 부른다.
이 프레임워크를 사용하기 위해서는 OAuth2.1 과 OIDC 와 같은 인증/인가 모델에 대해 먼저 알아야 한다.

OAuth2.1 과 OIDC

OAuth2.1은 OAuth2.0 방식에서 약간의 보안을 더 개선한 버전이다.
OAuth2.0은 우리가 흔히 알고 있는 일반적인 '카카오 로그인' , '구글 로그인'이다.

이러한 동작의 흐름은 많아 알려져있다시피 아래와 같은 흐름으로 이루어진다.

  1. ~로 로그인 클릭
  2. OAuth Provider 로그인 페이지 이동
  3. 사용자 로그인 성공
  4. authorization_codestate 값과 함께 클라이언트의 redirect_uri로 이동
  5. 클라이언트는 authorization_code를 통해 액세스 토큰 발급
  6. 액세스 토큰으로 사용자 신원 조회

하지만 이 과정 중에서 authorization_code를 발급 받는 중에 가로채기 공격을 당할 수 있다.

이 문제를 해결하기 위해서 등장한 기법이 바로 PKCE (Proof Key Code Exchange)라는 기법이다.

여기서 추가로 두 가지 개념이 도입되는데 아래와 같다.

  • code_verifier : 애플리케이션(클라이언트)이 생성하는 일회용 비밀번호로, 클라이언트만 소유한다.
  • code_challenge : 위 비밀번호를 해시 함수에 넣은 값

PKCE에 의해 달라진 점

  1. authorization_code를 발급할 때 code_challenge를 OAuth 인증 서버에 같이 전달함으로써,
    authorization_code 에 대한 자물쇠가 code_challenge임을 기록한다.

  2. 이후 클라이언트가 authorization_code 로 AccessToken을 발급받을 때,
    자물쇠의 키인 code_verifier를 같이 보내야만, AccessToken을 발급할 수 있도록 한다.

즉, authorization_code 만 탈취해서는 이제 AccessToken을 발급받을 수 없다.
공격자는 code_verifier 까지 알아내야 한다.

OAuth2.1에 추가된 내용이 PKCE 뿐만은 아니다. OAuth2.0 방식의 문제점을 해결하기 위해서, 아직도 많이 수정되고 개선하고 있다고 한다.

OAuth2.1에 더 자세한 내용은 여기를 참고하면 되겠다.

OIDC

OIDC는 OpenID Connect라는 뜻으로 OAuth2.0 위에 얇은 인증 기능을 추가한 것이다.

OAuth2.0 과정에서 AccessToken을 발급한 것은 애플리케이션에게 API를 호출하는 권한을 주는 것이다.

하지만 OIDC에서는 IDToken이라는 개념이 추가된다.

  • IDToken
    • AccessToken과 달리, 로그인한 사용자의 실제 신원 정보를 담고 있는 토큰이다.
      AccessToken은 API 엔드포인트를 호출할 수 있는 권한을 가진 토큰이어서 사용자 정보를 조회하는 API를 호출해야 신원 정보를 알 수 있지만
      IdToken은 토큰 자체에 신원 정보가 담겨 있으므로, API를 호출하지 않아도 애플리케이션은 사용자의 신원 정보를 알 수 있다.

IDToken이 등장함으로써 OIDC Client는 사용자의 신원 정보를 조회하는 API를 호출할 필요가 없고, IDToken에 담긴 사용자의 신원 정보를 바로 확인할 수 있는 것이다.

(네트워크 요청이 줄어들어 더 빨라지는 것이다.)

Authorization Server 와 Resource Server

한글로 직역하면, 인증 서버와 자원 서버로 부를 수 있는데 역할은 아래와 같다.

Authorization Server
인증 서버는 말 그대로 인증을 수행하는 서버이다.
OAuth 클라이언트와 OIDC 클라이언트 모두 인증/인가를 담당한다.

  • OAuth 클라이언트 및 OIDC 클라이언트 인증/인가 수행
  • authorization_code 및 AccessToken 발급

Resource Server
리소스 서버는 토큰을 검증하고, 권한과 정책에 맞게 보호된 자원을 제공하는 역할을 한다.

  • AccessToken 검증 (올바른 토큰인가? 유효한 토큰인가?)
  • 보호된 리소스 제공 (사용자의 신원 정보 등)

요약하자면 우리가 authorization_code 와 액세스 토큰을 발급 받는 것은 Authorization Server 로부터 하는 것이고
카카오로부터 사용자의 정보를 조회하는 것은 Resource Server 로부터 하는 것이다.

Spring Authorization Server

OAuth 와 OIDC 의 개념을 어느정도 이해했다면, 이를 실제로 구현해볼 수 있다.
바로 Spring에서 제공하는 Authorization Server를 활용하면, OAuth2.1 및 OIDC Provider 를 구축할 수 있다.

SAS는 Spring Security 위에서 동작하기 때문에, Security가 수행하는 인증/인가의 처리 과정 등을 알아놓아야 한다.

SAS는 내부적으로 어떠한 과정을 거쳐서 OAuth2.1 및 OIDC 를 구현하는지 알아보자.

엔드포인트

OAuth2.0 및 OAuth2.1 에서 authorization_code 를 발급 받고 AccessToken을 발급 받을 때 API를 호출하는 것처럼, SAS에서도 기본 엔드포인트를 제공한다.

물론 커스텀이 가능하다.

엔드포인트용도
/oauth2/authorize클라이언트가 사용자의 동의를 얻기 위해서 접근하는 엔드포인트
/oauth2/token클라이언트가 authorization_code를 AccessToken으로 교환하는 엔드포인트
/oauth2/introspect토큰이 유효한지 검사하는 엔드포인트 (리소스 서버가 호출한다.)
/oauth2/jwksJWT 서명 검증에 필요한 공개키를 노출하는 엔드포인트

SAS에서는 내부적으로 모두 JWT를 사용한다.

이때 Resource Server 와 Authorization Server 가 분리되어 있을 경우,
Resource Server는 클라이언트로부터 받은 JWT를 해독하기 위해,
Authorization Server 와 통신하여 공개키 교환을 수행해야 한다.

이때 사용하는 키가 바로 JWK(JSON Web Key)이다.
(인터넷 상에서도 Key를 전달하기 위해 만들어진 데이터)

JWK 키교환

여기서 ResourceServer 와 AuthorizationServer 가 분리되어 있다보니,
AccessToken이 유효한지 확인하기 위해서 AuthorizationServer 키가 필요하다.

비대칭키 방식을 사용해서, AuthorizationServer의 비밀키로 JWT를 서명하는 경우에
AccessToken의 유효성을 검증할 때 AuthorizationServer의 공개키가 필요하다.

그래서 ResourceServer는 AuthorizationServer에게 /oauth2/jwks 엔드포인트로 요청을 보내서,
JWK 형태로 공개키를 받아온다.

자세한 동작 과정은 아래의 그림과 같다.

컴포넌트

이제 SAS 내부에서 어떤 컴포넌트, 빈들이 OAuth2.0 / 2.1 , OIDC에 관여하는지 알아보자.

다 설명하면 너무 많으니까 핵심적인 부분만 알아보자.

RegisteredClientRepository

  • 클라이언트 등록 정보 저장소로 client_id , client_secret , redirect_uri 등을 저장한다.
  • SAS는 OAuth2 , OIDC Provider 를 구축하려고 사용하는 프레임워크이다.
    이때 필요한 기능이 클라이언트 동적 등록인데, 등록된 여러 클라이언트를 저장하기 위해 사용되는 컴포넌트이다.
  • InMemory 와 Jdbc 버전 두 가지가 존재한다.

OAuth2AuthorizationService

  • 새로운 Authorization을 저장하고 기존 Authorization을 조회하는 핵심 구성요소이다.
  • 클라이언트 인증, 권한 부여, 토큰 검사, 토큰 취소, 클라이언트 동적 등록 등의 과정에서 사용된다.
  • InMemory 와 Jdbc 버전과 두 가지가 존재한다.

AuthorizationEndPointFilter

  • /oauth2/authorize 요청을 처리한다.
  • authorization_code를 발급한다.

TokenEndpointFilter

  • /oauth2/token 요청을 처리한다.
  • AccessToken을 발급한다.

JwtEncoder / JwtDecoder

  • JWT를 생성/해독, 디코딩/인코딩 하는 역할을 담당한다.
  • 인증/인가를 구현할 때, 외부 라이브러리를 별도로 설치해서, 직접 구현해줘야 했었다. 하지만 SAS에는 nimbus-jose 라는 JWT 인/디코딩 라이브러리를 내장하고 있어서 손쉽게 사용할 수 있다.

OAuth2TokenCustomizer

  • 토큰을 커스텀하여 Claim을 쉽게 추가/수정할 수 있다.

요약

  • Spring Authorization Server 는 Spring Security을 기반으로 동작하며, AuthetnicationManager , AuthenticationProvider , AuthenticationFilter 메커니즘을 똑같이 사용해서 OAuth2를 구현한다.
    • 그래서 SAS를 사용한다면, 스프링 시큐리티에 대한 이해는 필수적이다.
  • 또한 Spring Authorization Server는 단순히 인증/인가 서버의 역할을 하는 것이고
    Spring Resource Server 라는 프레임워크가 별도로 존재한다.
    해당 프레임워크를 사용하면 ResourceServer 역시 손쉽게 구현할 수 있겠다.

참고자료

0개의 댓글