
현대오토에버 코딩 SW 스쿨에서 서버부터 클라이언트까지 전반적인 개발을 경험하면서 배운 것 중 하나가 있다. 바로 인증(Authentication)과 인가(Authorization)의 중요성이다. 대부분의 웹 및 모바일 어플리케이션은 로그인을 기반으로 동작하며, 사용자 정보를 안전하게 보호하고 권한을 제어하는 것이 필수이다.
프로젝트를 진행하면서 로그인 처리, 세션 유지, 토큰 관리, 소셜 로그인(Kakao OAuth) 등 인증 기능을 여러번 구현해야했다. 당시에 나는 OAuth라는게 있는지도 몰랐으며, 이러한 상태로 기능을 구현했으니 당연히 구조적 취약점을 고려하지 못한 채로 기능이 구현되었다. 프로젝트 발표 및 멘토링 과정에서 여러 취약점을 지적받고 이때 처음으로 OAuth라는게 있다는 것을 알게되었다.
이러한 과정을 통해서 인증 및 인가를 제대로 이해하여 구현해보고자하는 목표가 생겼고, 프로젝트 기간동안 RFC 6749 등의 공식 문서를 기반으로 OAuth 2.0에 대해 공부했다. 이 글에서는 그 과정에서 정리한 OAuth 2.0 Framework의 개념과 동작 방식을 공유하고자 한다.
추가로 최근 쿠팡에서 개인정보 유출 사고가 발생했는데, 해당 사고는 OAuth 자체의 취약점이라기보다는, 토큰 관리 및 검증 방식의 구현 상 문제와 연관된 사례로 보인다.
![]()
OAuth 2.0을 이해하기 위해서는 먼저 인증(Authentication)과 인가(Authorization)의 개념을 먼저 명확히 구분해야한다.
"이 사용자가 누구인가?"를 확인하는 과정을 의미한다. 즉, 시스템에 접근하려는 주체가 주장하는 신원이 사실인지를 검증하는 절차이다.
따라서 일반적으로 인증은 다음과 같은 정보들을 통해 수행이 된다.
사용자가 알고있는 정보(ID/ Password)
사용자가 가지고 있는 것(OTP, 인증 앱 등)
사용자의 고유한 특성(안면 인식, 지문 인식)
웹 어플리케이션 환경에서 일반적인 인증 과정은 로그인(login)이다. 간단히 말해서 사용자가 ID와 비밀번호를 입력하면, 서버는 이를 검증하고 해당 사용자가 누구인지 확인하는 것이다.
인가는 일반적으로 인증 이후 수행되는 개념으로, "이 사용자가 무엇을 할 수 있는가?"를 검증하는 단계이다.
즉, 이미 인증된 사용자에게 어떤 리소스에, 어떤 범위까지 접근을 허용할 것인지를 판단하는 절차이다.
예시로 다음과 같은 상황이 있을 수 있다.
이때 두 사용자는 모두 인증은 완료되었지만, 각각에게 부여된 권한(Authorization)이 서로 다르다.
이처럼 인증과 인가는 서로 다른 목적과 역할을 가지는 개념이다.
추가로, 엄밀히 말하면 OAuth 2.0은 로그인(Authentication)을 위한 표준이 아니라, 사용자의 리소스에 대한 접근 권한을 제3자 애플리케이션에 위임하기 위한 권한 위임(Authorization) 프레임워크이다.
실제 로그인 기능은 OAuth 위에서 동작하는 OpenID Connect(OIDC)를 통해 구현된다.
많은 애플리케이션이 등장하면서 각 서비스에서 다른 서비스를 연동하여 확장할 필요성이 생겼다. 또한, 로그인의 경우 각각의 서비스에서 하나의 사용자가 여러 ID를 생성하게 되는데 이러한 경우에도 밀접한 서비스 간에는 하나의 ID로 관리를 할 필요성이 생겼다.
이러한 요구사항을 해결하기 위해 OAuth라는 권한 위임 프레임워크가 등장하였고, 이후 이를 개선한 OAuth 2.0이 표준으로 자리잡게 되었다.
각 서비스들이 다른 서비스와 연동 시, 데이터를 다루는 방식, 특히 사용자의 자원에 제3자 애플리케이션이 접근해야하는 상황이 증가하면서, 기존 방식은 여러 문제점을 보였다. 이러한 문제를 해결하기 위해 등장한 것이 OAuth 2.0이다. OAuth 2.0의 목적은 다음과 같다.
다음으로는 OAuth 2.0 Framework에서 제안하는 역할을 기반으로 기존의 문제점을 다루고, OAuth에서는 이를 어떻게 극복했는지를 다루고자 한다.
먼저 OAuth 2.0에서의 정의하고 있는 역할이다. OAuth에서는 4가지 역할을 정의하고 있다.
보호된 리소스에 접근을 허가할 수 있는 주체(entity)로 리소스 소유자가 사람인 경우, 이를 엔드 유저(end-user)라고 부른다.
즉, 사용자라고 생각하면 된다.
보호된 리소스를 호스팅하며, 액세스 토큰을 사용하여 보호된 리소스에 대한 요청을 받아드이고 응답할 수 있는 서버이다.
즉, 사용자의 리소스를 가지고 있는 서버이다.
리소스 소유자의 승인을 얻어, 리소스 소유자를 대신하여 보호된 리소스 요청을 수행하는 애플리케이션.
즉, 사용자에게 동의를 얻어 액세스 토큰으로 사용자의 자원을 요청하는 주체이다.
리소스 소유자를 인증하고, 권한(Authorization)을 획득한 후 클라이언트에게 액세스 토큰을 발급하는 서버
즉, 사용자 인증과 동의 절차를 수행하고, 그 결과로 클라이언트에게 액세스 토큰을 발급하는 서버
인가 서버와 리소스 서버 간의 상호작용은 이 OAuth 2.0 Framework의 사양의 범위를 벗어난다. 즉, 인가 서버와 리소스 서버는 동일한 서버일 수도 있고 별개의 서버일 수도 있다. 또한, 하나의 인가 서버가 여러 리소스 서버에서 받아들여지는 액세스 토큰을 발급할 수도 있다.
이는 이후 구현 시, 설명을 위해 RFC 6749의 전문을 가져온 내용이다.
위 4가지 역할을 기반으로 우리가 왜 OAuth가 필요했는지에 대한 배경을 이해해보고자 한다. 먼저, OAuth 2.0 Framework가 없는 상황에서 제 3자 애플리케이션이 사용자의 리소스를 요청하는 상황을 가정해보자.
이 가정은 위 OAuth의 역할에 다음과 같이 매칭된다.
| 예시 | OAuth 역할 | 설명 |
|---|---|---|
| 사용자(본인) | 리소스 소유자(resource owner) | 자신의 일정 데이터에 대한 접근을 허가할 수 있는 리소스 소유자 |
| Google Calendar | 리소스 서버(resource server) | 일정 데이터를 저장하고 호스팅하는 서버 |
| 구글 로그인 창(OAuth 서버) | 인가 서버(authorization server) | 지금 섹션에는 직접적으로 나오지는 않지만 실제 역할 상 존재한다. |
| 통합 캘린서 서비스 | 클라이언트 | 사용자를 대신하여 캘린더 API 접근을 요청하는 제 3자 앱 |
위 상황에서 제 3자 애플리케이션이 사용자의 리소스인 일정 데이터를 접근하기 위해 가장 간단한 방법은 사용자의 id와 비밀번호를 사용하여 Google Calendar에 로그인 하는 것이다.
위 상황에서 제 3자 애플리케이션이 일정 데이터를 가져오게 된다면 발생하는 문제점은 대표적으로 3가지이다.
추가적으로, 제3자 애플리케이션은 사용자의 전체 계정에 대한 과도한 접근 권한을 갖게 되며, 사용자는 특정 애플리케이션만 선택적으로 접근을 철회할 방법이 없는 등의 문제가 존재한다.
앞서 살펴본 문제들을 해결하기 위해서 OAuth 2.0 Framework는 인가 레이어를 도입하고, 사용자 비밀번호를 직접 공유하지 않고도 제3자 애플리케이션이 보호된 리소스에 접근할 수 있는 방식으로 토큰 기반 접근을 제안했다.
OAuth의 인가 레이어의 설명을 위해, 먼저 토큰에 대해서 알아보자.
토큰은 권한이나 신분을 나타내는 증표로 인증키, 출입증이라고 생각하면 된다.
토큰 방식을 사용하면 다음 문제를 해결할 수 있다.
OAuth의 핵심은 access token이며, refresh token은 장기 세션 유지를 위해 선택적으로 발급될 수 있다.
다만, 토큰의 형식은 OAuth 2.0 표준에서 정의하지는 않는데, JWT(Json Web Token)은 실무에서 보편적으로 사용되는 구현 방식 중 하나이다.
액세스 토큰은 제3자 애플리케이션인 클라이언트가 리소스 서버(API)에 특정 요청을 보낼 수 있도록 인가 서버가 발급하는 권한 증명서이다. 즉, "이 앱이 사용자의 허락을 받아서 이 API를 호출할 수 있습니다."를 증명하는 토큰이다.
액세스 토큰이 JWT 형식으로 발급되는 경우, PAYLOAD에는 토큰의 의미를 나타내는 클레임이 포함될 수 있다.
예를 들어 다음과 같은 정보가 들어갈 수 있다.
scope: 허용된 권한 범위iss: 토큰 발급자(issuer)aud: 토큰 대상(resource server 또는 intended audience)exp: 토큰 만료 시각iat: 토큰 발급 시각sub, client_id, jti 등의 추가 클레임리프레시 토큰은 단순하다. 리프레시 토큰은 말 그래도 액세스 토큰을 갱신(리프레시)를 하기 위해 존재하는 토큰이다.
액세스 토큰이 만료되면 클라이언트는 리프레시 토큰을 사용해서 다시 액세스 토큰을 발급 받으며, 클라이언트는 리프레시 토큰을 이용하여 보안과 편의성을 모두 달성할 수 있다.
OAuth 2.0 Framework에서는 인가를 부여받는 절차를 그랜트(Grant)라고 부르며, 총 4가지의 방식이 존재한다.
이 중 실제 서비스 환경에서 사용되는 방식은 인가 코드 방식이다.
다른 방식이 사용되지 않는 이유는 OAuth 공식 홈페이지의 Grant Type에서 확인할 수 있다.
OAuth 2.0의 인가 코드 그랜트 방식은 사용자가 직접 인가 서버에서 로그인하고 동의를 마친 뒤, 그 결과로 발급된 인가 코드(Authorization Code)를 이용하여 클라이언트가 액세스 토큰과 리프레시 토큰을 발급 받는 방식으로 권한을 인가한다.
RFC 6749에서는 위와 같이 플로우가 설명이 되어 있으나, 이해를 위해 다시 아래와 같이 정리해보았다.
다른 방식들의 한계점만 간단히 설명하자면 다음과 같다.
위 문제들을 인가 코드 그랜드 방식은 다음 이점으로 표준이 되었다.
정리하자면, Authorization Code Grant 방식은 사용자의 비밀번호를 클라이언트가 직접 다루지 않으면서, 액세스 토큰 발급을 authorization response와 분리된 token point 교환 단계에서 처리할 수 있다는 점이 핵심이다.
여기에 PKCE를 결합하면 코드 탈취 및 주입 공격에 대한 방어가 강화되어, 현재의 기본 권장 흐름으로 자리 잡았다.
이러한 이유로 오늘날의 일반적인 로그인 및 제3자 위임 시나리오에서는 Authorization Code Grant 기반 구조가 사실상의 표준으로 자리잡고 있다.
특히 브라우저 기반 앱과 모바일 앱에서는 PKCE와 함께 사용하는것이 최신 권고와 부합한다.
먼저, OAuth의 등장 배경과 가장 많이 사용되며 표준이 된 방식인 인가 코드 그랜트 방식까지 다루어 보았다. 다음으로는 이를 실제로 프로젝트에서 어떻게 구현을 했으며, 이어 OAuth 2.1에서는 어떤 변화가 있으며 이제 어떠한 방식으로 구현을 해야 하는지까지 정리해보고자 한다.
화이팅!