1.8OAuth 2.0
사용자가 비밀번호를 넘기지 않고도 다른 애플리케이션에게 제한된 범위 내에서 자신의 데이터에 접근할 수 있도록 권한을 위임하게 해주는 프로토콜, OAuth 2.0
TL;DR
추억의 쪽지 시험
참고 글: https://www.geeksforgeeks.org/software-engineering/workflow-of-oauth-2-0/
OAuth 2.0 개요
OAuth(Open Authorization) 2.0은 사용자가 자신의 계정 비밀번호를 직접 넘기지 않고도, 다른 애플리케이션에게 제한된 범위 내에서 자신의 데이터에 접근할 수 있도록 권한을 위임해주는 표준 프로토콜이다.
OAuth 2.0이 없다면, 만약 내가 만든 서비스에서 다른 회사에서 만든 서비스를 사용하려면 사용자의 계정/비밀번호를 직접 받아와 해당 서비스에 로그인해야 한다. 물론 이 방식은 비밀번호가 유출된다는 심각한 문제가 있고, 사용자가 원래 서비스의 비밀번호를 바꾸면 연결이 깨진다. 또, 특정 기능만 허용하는 등 부분 권한 부여(granular access levels)를 하기도 어렵다.
OAuth 2.0은 아래와 같은 방식으로 이러한 문제를 해결한다.
- 비밀번호 대신 토큰을 사용
- 토큰의 범위(scope), 유효기간, 취소(revoke)를 관리
- 서비스 간에 일관된 방식으로 권한 위임
OAuth 1.0은 클라이언트와 서버가 공유하는 비밀 키를 바탕으로, 매 요청마다 복잡한 서명(signature) 을 계산해서 보내야 했는데 이 서명은 HTTP 메서드, URL, 쿼리 파라미터, 타임스탬프, nonce 등을 모두 모아 문자열을 만들고, HMAC-SHA1 같은 알고리즘으로 서명한 뒤 헤더에 싣는 방식이었다. 이 방식은 보안적으로는 탄탄했지만 구현이 어렵고 번거로웠기에 기피되는 프로토콜이었다.
이에 OAuth 2.0에선 요청 서명을 제거하고, HTTPS 위에서 Bearer Token을 쓰는 구조로 단순화해 클라이언트에선 헤더 하나만 붙여서 호출하고, 통신 경로 보안은 HTTPS(TLS)가 책임지게 한다.
OAuth 2.0 상세
OAuth는 크게 아래 4가지 요소로 나눠 바라볼 수 있다.
- Actors
- Scopes and Consent
- Tokens
- Grant Type
1. Actors
OAuth는 아래 4개의 주체(Actors)의 상호작용이다. OAuth를 주체의 관점에서 요약하면, 리소스를 실제 소유한 사용자가 애플리케이션을 통해 다른 곳에 있는 리소스를 가져와 뭔가 하려고 할 때, 애플리케이션 쪽에서 인가 서버(Authorization Serer)를 통해 필요한 권한을 위임받아 리소스 서버에서 데이터를 가져올 수 있도록 과정을 표준화한 프로토콜이다.
- Resource Owner(리소스 소유자) : 필요한 리소스를 소유하고 있는 사람으로, 보통 사용자이다. 카카오톡 계정, 구글 드라이브 파일 등 데이터의 실제 주인이다.
- Client(클라이언트 애플리케이션) : 사용자의 권한을 빌려서 다른 서비스에 접근하려고 하는 앱이다. 프론트엔드 개발자 입장에선, 내가 개발중인 앱이다.
- Resource Server(리소스 서버) : 실제 리소스가 저장된 서버로, 카카오 사용자 정보 API나 구글 드라이브 API 서버 등이다.
- Auth Server(인가 서버) : 로그인 화면 및 동의 화면을 띄우고, 사용자가 인가하면 토큰을 발급해주는 서버다. 리소스 서버와 같은 시스템인 경우가 많으나, 논리적으로는 역할이 분리된다.
여기서, OAuth 2.0은 "인증(Authentication)"보다는 "인가(Authorization)"에 초점이 맞춰져 있다. "이 사람이 누구냐"를 증명하는 것 자체보다는, "이 앱이 이 사람 대신 무엇을 할 수 있느냐"가 핵심이다. 로그인/인증은 OpenID Connect에서 다룬다.

2. Scopes and Consent
Scope는 Client가 Resource Owner를 대신해 어떤 권한들을 수행할 수 있는지를 지정하는 것이다. 즉, "앱에게 어디까지 허용할지"를 표현하는 문자열들이다.
예를 들어, 다음과 같은 스코프들이 있을 수 있다.
profile: 기본 프로필 정보 접근email: 이메일 주소 읽기drive.readonly: 구글 드라이브 파일 읽기 전용
사용자는 동의 화면(Consent)에서 이 Scope들을 보고 허용 여부를 결정한다. 스코프 덕분에 "이메일만 가져가도 됨"이나 "읽기만 되고 쓰기는 안 됨" 같은 부분 권한 부여가 가능해진다.
3. Tokens
토큰은 "누가 어떤 범위로 무엇에 접근할 수 있는지"에 대한 정보를 담은 권한 증표다.
OAuth 2.0 스펙 자체는 토큰 포맷을 강제하지 않지만, 무작위한 것으로 보이는 문자열(불투명 문자열, opaque token이라고 함)이나 JWT가 자주 쓰인다.
OAuth 2.0에선 Access Token과 Refresh Token 두 개의 토큰을 활용한다.
Access Token은 클라이언트가 API를 호출할 때 Authorization: Bearer <access_token> 형식으로 내보내는 토큰으로, 클라이언트가 해당 범위(scope)의 리소스에, 토큰 만료 시간까지 접근해도 좋다는 의미를 가진다. Access Token은 짧은 유효기간(예: 1시간)을 가진다.
Refresh Token은 Access Token이 만료되었을 때, 다시 로그인이나 동의를 할 필요 없이 새 Access Token을 발급 받을 수 있도록 해주는 토큰으로 주로 클라이언트 서버에서 관리한다.
일반적으로 Access Token과 Refresh Token을 활용하는 패턴은 아래와 같다:
- 최초 인가 과정에서 Authorization Server가 Access Token + Refresh Token을 함께 발급한다.
- 클라이언트(대개 백엔드 서버)는 Refresh Token을 안전한 저장소에 보관한다.
- Access Token이 만료되면, 클라이언트는 Refresh Token을 사용해 Authorization Server에 새 Access Token을 요청한다.
- Authorization Server는 여전히 유효한 Refresh Token이라면 새 Access Token을 발급한다.
Refresh Token은 만료 기간이 길기 때문에 반드시 서버 쪽 안전한 저장소(예: 데이터베이스, 암호화된 키 스토어 등)에 보관해야 한다.
4. Grant Type
OAuth 2.0에는 상황에 따라 여러 가지 Grant Type(인가 방식)이 있는데, 클라이언트가 어떤 방식으로 토큰을 얻을지에 대한 시나리오를 뜻한다. Flow라고 부르던 과거에는 Implicit, Resource Owner Password Credentials 같은 방식도 쓰였지만, 보안상 이유로 지금은 비권장되고, 요즘 쓰이는 것은 아래 세 가지다.
4-1. Authorization Code (+ PKCE)
웹/모바일에서 가장 많이 사용되고, 권장되는 방식이다. 특히 SPA나 모바일 앱처럼 Client Secret을 안전하게 숨기기 어려운 환경에서 PKCE(Proof Key for Code Exchange)와 함께 사용한다.
- 우선, 사용자는 Client 앱에서 "구글로 로그인" / "카카오 계정 연결" 버튼을 클릭한다. Client는 사용자를 Authorization Server(구글/카카오 로그인 페이지)로 리다이렉트하는데, 이 때 요청 URL에 response_type=code, client_id, redirect_uri, scope, 그리고 PKCE를 위한 code_challenge 등을 붙인다.~
- 사용자가 Authorization Server에서 로그인 + 동의를 진행한다.
- 동의를 받은 Authorization Server는 Client에 Authorization Code를 전달한다. 동의가 끝나면, Authorization Server는 미리 등록해둔 redirect_uri로 사용자를 다시 돌려보내고, 쿼리 파라미터로 짧게 유효한 일회용 코드(Authorization Code)를 함께 전달한다.
- Client(주로 백엔드)는 전달 받은 Authorization Code로 토큰 교환을 요청한다. 백엔드는 Authorization Server에 Authorization Code + code_verifier(PKCE용)를 보내며 이 코드와 클라이언트 정보가 맞으니 Access Token(필요시 Refresh Token)을 달라고 요청한다.
- Authorization Server가 Access Token(+ Refresh Token)을 발급한다. 백엔드는 이 토큰들을 안전하게 저장하고, 이후 Resource Server API 호출 시 Access Token을 사용한다.
이 과정에서 클라이언트 앱은 사용자의 비밀번호를 알 수 없다. 또, Authorization Code는 짧은 수명을 가지고 있어 중간에 탈취되더라도 피해가 제한된다. OAuth 2.0에서 권장하는 Auth Code의 만료 시간은 10분 이하이다.
PKCE는 이 시나리오에서 사용자가 로그인 및 동의를 모두 하고(위 흐름도에서 2번), Authorization Server가 redirect_uri로 보낸 Authorization Code를 공격자가 가로챘을 때를 대비한 것이다. 이는 특히 모바일이나 SPA처럼 Client Secret을 숨기기 힘든 환경에서 노출된 보안 위협이다.
Authorization Code를 요청한 진짜 클라이언트가 사용한게 맞는지 Authorization Server에선 어떻게 확인할 수 있을까? PKCE에선 code_verifier와 code_challenge라는 두 요소를 활용한다.
**code_verifier**는 클라이언트가 앱 안에서 생성하는 길고 랜덤한 문자열이다. 이 값은 외부로 노출하지 않고, 앱 안에서만 가지고 있는다. **code_challenge**는 code_verifier를 가공해서 만든 값으로, 보통 code_challenge = BASE64URL-ENC(SHA256(code_verifier))식으로 생성한다.
그럼 다시, 위의 흐름도에서 앱에서 Authorization Server에 인가를 요청할 때(/authorize) code_challenge와 code_challenge_method=S256을 포함한다. Authorization Server는 이 정보를 보관하고, 유저를 로그인 + 동의로 리다이렉트하고(흐름도 1번), 유저는 로그인 + 동의를 진행한다(흐름도 2번). 이를 통해 Authorization Code가 발급된다(흐름도 3번).
여기서 Auth Code의 탈취가 일어날 수 있다. PKCE의 핵심은 여기서 /token 요청을 할 때, Client가 code_verifier를 Authorization Server에 보내는 것이다. Authorization Server는 앞서 보관한 code_challenge를 code_verifier를 통해 계산해본다. 이게 기존의 code_challenge와 동일하면 처음에 인가 요청을 보낸 클라이언트임을 알 수 있고, 다르면 위조된 요청으로 간주할 수 있게 된다.
4-2. Client Credentials
사용자 없이, 서버 간 통신에서 사용하는 방식이다.
- A 백엔드 서비스가 B 백엔드 서비스의 API를 호출해야 할 때
- 내부 관리용 API, 배치 작업, 마이크로서비스 간 호출 등
이 방식에서 Client는 자신을 식별하는 client_id와 client_secret으로 Authorization Server에 직접 토큰을 요청한다. 즉, 이 방식엔 Resource Owner(사용자)가 등장하지 않는다. 발급받은 Access Token은 “이 클라이언트 애플리케이션이 자신을 인증한 것”에 대한 증표이다.
4-3. Refresh Token Grant
위에서 본 Refresh Token도 Grant Type의 한 종류로 볼 수도 있다.
Access Token이 만료됐을 때 클라이언트는 다음과 같이 요청을 보내 새 토큰을 발급받는다.
- 요청:
grant_type=refresh_token과 함께refresh_token=<기존 Refresh Token>을 인가 서버에 전송 - 응답: 새로운 Access Token (필요하다면 새 Refresh Token 포함)
이 방식 덕분에 사용자는 매번 로그인/동의 화면을 다시 거치지 않고도, 일정 기간 동안 서비스를 계속 이용할 수 있다. 대신 Refresh Token이 유출되면 새 Access Token을 무한정 뽑을 수 있으므로, 서버 쪽에서 안전하게 보관하고 필요시 폐기(revoke)할 수 있어야 한다. Refresh Token을 강력하게 만드는 핵심은 Blacklist 기능이다.