-
OAuth 2.0: 비밀번호를 넘기지 않고 권한만 빌려주는 방법IT 2026. 3. 20. 21:00
들어가며 — 비밀번호를 알려줘야만 했던 시절
2007년쯤, 웹 서비스들이 서로 연동되기 시작했다. "이 앱에서 내 Google 연락처를 불러올게요"라는 기능이 등장한 것이다. 그런데 문제가 있었다. 연락처를 불러오려면 내 Google 비밀번호를 그 앱에 직접 입력해야 했다.
잠깐 비유를 들어보자. 집 열쇠를 택배기사에게 맡기는 상황을 상상해보라. 택배기사는 현관에 물건만 놓으면 되는데, 내 집 마스터키를 통째로 넘겨야 한다. 기사가 나쁜 마음을 먹으면? 열쇠를 복사하면? 이게 바로 OAuth 이전 시대의 현실이었다.
- 사용자 비밀번호가 제3자 앱 서버에 저장됨 → 해킹 시 비밀번호 유출
- 앱에 모든 권한이 부여됨 → 연락처만 필요한데 메일, 드라이브까지 접근 가능
- 권한 회수가 불가능 → 비밀번호를 바꾸는 수밖에 없음
- 어떤 앱이 내 계정에 접근 중인지 추적 불가
이 문제를 해결하기 위해 Twitter, Google 등에서 각자 다른 방식의 API 인증을 만들었는데, 표준이 없다 보니 앱 개발자들은 서비스마다 다른 인증 방식을 일일이 구현해야 했다. 이런 혼란 속에서 OAuth 1.0(2007)이 나왔고, 복잡한 서명(signature) 방식을 개선한 OAuth 2.0(2012, RFC 6749)이 현재의 표준이 되었다.
OAuth 2.0이 해결하려는 핵심 문제
한 문장으로 요약하면 이것이다:
"비밀번호를 알려주지 않고도, 필요한 권한만 골라서, 기간을 정해서 빌려줄 수 있을까?"
앞의 택배 비유로 돌아가면, OAuth 2.0은 이런 해결책이다:
- 마스터키 대신 "현관문만 열 수 있는 임시 카드키"를 발급한다
- 카드키에는 유효기간이 있다 (만료되면 자동 무효화)
- 집주인이 언제든 카드키를 원격으로 비활성화할 수 있다
- 카드키로 어떤 문을 몇 번 열었는지 기록이 남는다
이것을 기술 용어로 바꾸면:
비유 OAuth 2.0 용어 설명 집주인 Resource Owner (리소스 소유자) 데이터의 주인, 즉 사용자 본인 택배기사 Client (클라이언트) 사용자 데이터에 접근하려는 제3자 앱 집 (데이터 저장소) Resource Server (리소스 서버) 실제 데이터가 있는 API 서버 (예: Google API) 카드키 발급 데스크 Authorization Server (인가 서버) 토큰을 발급하는 서버 (예: accounts.google.com) 임시 카드키 Access Token (접근 토큰) API 호출 시 제출하는 임시 자격 증명 "현관문만" 제한 Scope (범위) 토큰이 접근할 수 있는 권한의 범위
실생활 활용 사례
사례 1: "Google로 로그인" 버튼
쇼핑몰에서 회원가입할 때 "Google로 로그인" 버튼을 누른 적 있을 것이다. 이때 쇼핑몰은 우리의 Google 비밀번호를 절대 모른다. Google이 "이 사람이 본인 맞습니다"라는 확인과 함께 이름, 이메일 정도만 알려주는 것이다. 이게 OAuth 2.0이 실제로 작동하는 모습이다.
사례 2: 캘린더 앱 연동
Notion이나 Slack에서 "Google Calendar 연결"을 누르면, Google 로그인 화면이 뜨고 "이 앱이 캘린더를 읽는 것을 허용하시겠습니까?"라고 묻는다. 여기서 "허용"을 누르면 Notion은 캘린더 읽기 권한만 받는다. 메일을 보거나 드라이브 파일을 삭제할 수는 없다.
핵심: Authorization Code Flow 단계별 해부
OAuth 2.0에는 여러 Grant Type(인가 방식)이 있지만, 가장 안전하고 널리 쓰이는 것은 Authorization Code Flow(인가 코드 방식)이다. 웹 서버가 있는 앱에서 사용하는 표준 방식이다.
전체 흐름을 먼저 그림으로 보자:
이제 각 단계를 하나씩 뜯어보자.
사전 단계: 앱 등록 (Client Registration)
왜 이걸 하는가? 인가 서버가 "이 앱이 누구인지" 알아야 가짜 앱에게 토큰을 발급하지 않기 때문이다.
개발자가 Google Cloud Console 같은 곳에서 앱을 등록하면 두 가지를 받는다:
client_id— 앱의 공개 식별자 (ID카드 번호 같은 것)client_secret— 앱의 비밀 키 (절대 외부에 노출하면 안 됨)
또한
redirect_uri(콜백 URL)를 미리 등록한다. 나중에 인가 코드를 이 주소로만 전달하겠다는 약속이다. 이걸 미리 고정해두지 않으면 공격자가 자기 서버 주소로 코드를 가로챌 수 있다.① 사용자가 "로그인" 버튼 클릭
왜 이 단계가 필요한가? 모든 것은 사용자의 명시적 행동에서 시작해야 한다. 앱이 몰래 권한을 얻어가는 것을 방지한다.
사용자가 앱에서 "Google로 로그인" 버튼을 클릭하면, 앱은 사용자를 Google의 인가 서버로 보낸다.
② 인가 서버로 리다이렉트
왜 이 단계가 필요한가? 사용자의 비밀번호는 반드시 Google(인가 서버)에게만 입력해야 하므로, 브라우저를 Google 페이지로 보내는 것이다.
앱은 아래와 같은 URL로 브라우저를 리다이렉트한다:
https://accounts.google.com/o/oauth2/auth? response_type=code ← "인가 코드를 달라"는 요청 &client_id=앱의_ID ← 어떤 앱이 요청하는지 &redirect_uri=https://앱.com/callback ← 코드를 받을 주소 &scope=email%20profile ← 이메일과 프로필 정보만 요청 &state=xyz789 ← CSRF 공격 방지용 랜덤 값주의할 점:
state파라미터는 선택이 아니라 사실상 필수다. 이 값을 콜백에서 검증하지 않으면, 공격자가 자기 인가 코드를 피해자의 세션에 주입하는 CSRF(Cross-Site Request Forgery, 사이트 간 요청 위조) 공격이 가능하다.③ 사용자 인증 + 동의
왜 이 단계가 필요한가? "정말 이 앱에 이 권한을 줄 건지" 사용자가 직접 확인하고 결정해야 하기 때문이다.
Google 로그인 화면이 뜨고, 사용자가 비밀번호를 입력한다. 이 비밀번호는 Google 서버에만 전달되고, 앱은 전혀 볼 수 없다. 로그인 후 동의 화면이 나온다:
"캘린더앱이 다음 권한을 요청합니다:
✅ 이메일 주소 확인
✅ 프로필 정보 보기
[허용] [거부]"사용자가 "허용"을 누르면 다음 단계로 넘어간다.
④ Authorization Code 발급
왜 이 단계가 필요한가? 여기가 OAuth 2.0의 가장 영리한 부분이다. 바로 Access Token을 주지 않고, 1회용 교환권(Authorization Code)을 먼저 준다.
왜 한 단계를 더 거칠까? 이유는 보안이다:
- 이 코드는 브라우저의 URL에 노출된다 (리다이렉트 주소에 붙어서 전달)
- URL은 브라우저 히스토리, 서버 로그, 리퍼러 헤더 등 여러 곳에 남을 수 있다
- 그래서 이 코드만으로는 아무것도 할 수 없게 만든다 —
client_secret과 함께 제출해야만 진짜 토큰으로 교환된다 - 코드의 유효기간은 보통 30초~10분이고, 1번만 사용 가능하다
HTTP/1.1 302 Found Location: https://앱.com/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz789⑤ Authorization Code → Token 교환
왜 이 단계가 필요한가? 이 교환은 앱의 백엔드 서버에서 직접 인가 서버로 요청한다 (브라우저를 거치지 않는 서버 간 통신). 덕분에
client_secret이 브라우저에 노출되지 않는다.POST /token HTTP/1.1 Host: accounts.google.com Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=SplxlOBeZQQYbYS6WxSbIA ← 방금 받은 1회용 코드 &redirect_uri=https://앱.com/callback ← 등록한 것과 동일해야 함 &client_id=앱의_ID &client_secret=앱의_비밀키 ← 서버에서만 사용하는 비밀 값실패하면? 코드가 만료되었거나, 이미 사용되었거나,
redirect_uri가 등록 값과 다르거나,client_secret이 틀리면 요청이 거부된다. 모든 조건이 맞아야만 토큰이 발급된다.⑥ Access Token + Refresh Token 발급
왜 두 개의 토큰인가?
토큰 역할 유효기간 비유 Access Token API 호출 시 제출하는 "출입증" 짧음 (보통 1시간) 하루짜리 방문자 카드 Refresh Token Access Token 만료 시 새로 발급받는 "재발급권" 길음 (수개월~무기한) 방문자 카드 재발급 신청서 Access Token의 수명이 짧은 이유는 유출 시 피해를 최소화하기 위해서다. 설령 토큰이 탈취되더라도 1시간이면 만료된다. Refresh Token은 앱 서버에 안전하게 보관하고, Access Token이 만료될 때마다 조용히 새 토큰을 받아온다.
{ "access_token": "ya29.a0AfH6SMBx...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "1//0gdBhS3Q...", "scope": "email profile" }⑦-⑧ API 호출 및 데이터 반환
왜 이 단계가 필요한가? 드디어 목적 달성이다. 앱이 Access Token을 들고 실제 데이터를 요청한다.
GET /userinfo HTTP/1.1 Host: www.googleapis.com Authorization: Bearer ya29.a0AfH6SMBx...리소스 서버(Google API)는 토큰을 검증하고, 토큰에 부여된 scope 범위 내의 데이터만 반환한다:
{ "email": "user@gmail.com", "name": "홍길동", "picture": "https://..." }주의할 점: 토큰이 만료되면 리소스 서버는
401 Unauthorized를 반환한다. 이때 앱은 Refresh Token으로 새 Access Token을 발급받아 재시도한다.
왜 굳이 이렇게 복잡할까? — 각 단계의 보안 효과
"그냥 바로 토큰을 주면 안 되나?"라고 생각할 수 있다. 실제로 그런 방식도 있었다 (Implicit Flow). 하지만 보안상 문제가 많아 현재는 권장되지 않는다. Authorization Code Flow가 복잡한 이유가 있다:
한눈에 비교: OAuth 이전 vs 이후
항목 OAuth 이전 (비밀번호 공유) OAuth 2.0 (토큰 기반) 비밀번호 노출 제3자 앱에 직접 입력 ❌ 인가 서버에만 입력 ✅ 권한 범위 전체 계정 접근 ❌ 필요한 scope만 ✅ 권한 회수 비밀번호 변경뿐 ❌ 토큰 개별 무효화 ✅ 유출 영향 계정 전체 탈취 ❌ 1시간 후 자동 만료 ✅ 접근 추적 불가능 ❌ 앱별·scope별 기록 ✅ 표준화 서비스마다 다름 ❌ RFC 6749 표준 ✅
마무리
OAuth 2.0의 핵심은 결국 이것이다: 신뢰의 분리. 사용자는 인가 서버만 신뢰하고 비밀번호를 입력한다. 앱은 제한된 토큰만 받아서 필요한 데이터에만 접근한다. 인가 서버는 중간에서 "이 앱에 이 권한을 줘도 되겠습니까?"라고 사용자에게 확인한다.
"Google로 로그인" 버튼 하나 뒤에 이렇게 정교한 보안 프로토콜이 숨어있다. 다음에 이 버튼을 누를 때, "아, 지금 Authorization Code가 발급되고 있겠구나" 하고 떠올려보면 재미있을 것이다.
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
벡터 DB 3대장 비교, 나에게 맞는 선택은? (0) 2026.03.23 AI 검색시스템에 '무엇을' 넣을지 정하는 법 (0) 2026.03.23 OpenClaw를 250줄 게이트웨이로 교체한 이유 (0) 2026.03.22 GPU 하나로 AI 작업 두 개 돌리기 — 우선순위 스케줄러 만들기 (1) 2026.03.21 pyannote 화자 분리가 GPU에서 안 돌아갈 때 — Blackwell nvrtc 패치 1줄의 힘 (0) 2026.03.19 녹음 파일을 넣으면 요약이 나온다 — 음성 자동 전사 & 요약 파이프라인 구축기 (0) 2026.03.19 음성을 텍스트로, 목소리를 사람으로 — Whisper와 pyannote가 풀어낸 두 가지 문제 (0) 2026.03.19 수만 장 가족사진에 AI가 메타데이터를 입히는 과정 — Immich + VLM 파이프라인 해부 (1) 2026.03.18 gogcli에서 gws로: REST API → CLI → AI Agent, 도구의 진화를 따라가다 (0) 2026.03.17 DGX Spark에서 Immich로 가족앨범 GPU 가속 관리하기 (1) 2026.03.16