보안

OAuth 2.0 OIDC: 실무에서 적용하는 안전한 사용자 인증 구축 가이드

강코의 코딩 일기 2026. 5. 2. 20:07
반응형

OAuth 2.0과 OIDC를 활용한 안전한 사용자 인증 시스템 구축 경험을 공유합니다. 인가와 인증의 차이부터 PKCE 적용, 토큰 관리까지 실용적인 전략을 만나보세요.

안녕하세요, 수많은 개발 프로젝트 현장에서 고군분투하고 계실 동료 개발자 여러분!

오늘은 제가 직접 겪었던 경험을 바탕으로 안전한 사용자 인증 시스템을 구축하는 데 필수적인 OAuth 2.0OpenID Connect (OIDC) 활용 전략에 대해 이야기해보려 합니다. 혹시 이런 고민 해보신 적 있으신가요? "우리 서비스에 소셜 로그인을 붙여야 하는데, 그냥 가져다 쓰면 되는 건가?", "사용자 정보는 어떻게 안전하게 관리해야 하지?", "API 보안은 어떻게 강화해야 할까?" 저 역시 이런 질문들 앞에서 수없이 밤을 새웠던 기억이 납니다.

사용자 인증은 서비스의 첫인상이자, 동시에 가장 중요한 보안 접점입니다. 잘못된 설계는 해킹의 문을 활짝 열어줄 뿐만 아니라, 사용자 경험까지 저해할 수 있죠. 저도 처음에는 단순히 라이브러리만 가져다 쓰면 되겠지 하는 안일한 생각으로 접근했다가, 나중에 심각한 보안 이슈에 직면할 뻔했습니다. 그때부터 OAuth 2.0OIDC의 깊이를 파고들게 되었고, 그 결과 훨씬 견고하고 안전한 시스템을 구축할 수 있었습니다. 이 글을 통해 제가 얻은 실질적인 노하우와 주의사항들을 공유하고자 합니다.


OAuth 2.0과 OIDC를 활용한 안전한 사용자 인증 구현 전략 - city, bridge, cityscape, urban, flow, road, architecture, berlin

Image by wal_172619 on Pixabay

OAuth 2.0, 그 오해와 진실: 인증 vs 인가

OAuth 2.0을 처음 접하는 많은 개발자분들이 가장 먼저 헷갈려 하는 부분이 바로 '인증(Authentication)'과 '인가(Authorization)'의 차이입니다. 저 역시 그랬습니다. "로그인 기능 구현하려고 OAuth 2.0을 쓴다"고 말하는 분들을 종종 보는데, 사실 OAuth 2.0인가(Authorization) 프레임워크입니다. 사용자가 누구인지 '인증'하는 기능 자체는 제공하지 않습니다.

간단히 비유하자면, 인증은 '당신이 누구인지 신분증으로 확인하는 과정'이고, 인가는 '신분증이 확인된 당신에게 특정 문을 열어줄 권한이 있는지 확인하는 과정'입니다. OAuth 2.0은 후자, 즉 특정 자원(Resource)에 대한 접근 권한을 안전하게 위임하는 표준화된 방법을 제공합니다.

제가 실제로 외부 API 연동 프로젝트를 진행할 때였습니다. 사용자 정보 없이 특정 API만 호출해야 하는 상황이었죠. 이때 OAuth 2.0의 Client Credentials Grant 타입을 활용하여, 사용자 개입 없이 애플리케이션 자체의 권한으로 API를 호출할 수 있었습니다. 사용자 비밀번호를 직접 저장하거나 관리할 필요 없이, 오직 필요한 권한만 획득하는 것이죠. 이것이 바로 OAuth 2.0의 핵심 가치입니다.

핵심 용어 정리: 이 정도는 알고 가야!

  • 리소스 소유자 (Resource Owner): 보호된 자원에 접근 권한을 부여하는 주체 (대부분의 경우 사용자).
  • 클라이언트 (Client): 리소스 소유자를 대신하여 보호된 자원에 접근하려는 애플리케이션. (예: 여러분이 만드는 웹/모바일 앱)
  • 인가 서버 (Authorization Server): 리소스 소유자의 인증을 수행하고, 클라이언트에게 액세스 토큰을 발급하는 서버.
  • 리소스 서버 (Resource Server): 보호된 자원을 호스팅하며, 액세스 토큰을 사용하여 요청의 유효성을 검증하는 서버.

이 네 가지 주체 간의 관계를 명확히 이해하는 것이 OAuth 2.0 흐름을 파악하는 데 가장 중요합니다.


OpenID Connect (OIDC): OAuth 2.0 위에 꽃핀 사용자 인증

그렇다면 '로그인' 기능, 즉 사용자 '인증'은 어떻게 구현해야 할까요? 여기서 OpenID Connect (OIDC)가 등장합니다. OIDCOAuth 2.0 프레임워크 위에 구축된 간단한 ID 계층입니다. 즉, OAuth 2.0의 인가 기능을 활용하면서, 사용자 '인증' 정보를 추가적으로 제공하는 표준 프로토콜입니다. 저도 처음에는 OAuth 2.0만 알면 되는 줄 알았다가, OIDC를 배우면서 비로소 완전한 그림을 그릴 수 있었습니다.

OIDC의 가장 큰 특징은 ID 토큰(ID Token)이라는 개념을 도입한다는 점입니다. ID 토큰은 JWT(JSON Web Token) 형식으로, 인증된 사용자의 식별 정보(예: 사용자 ID, 이메일, 이름 등)를 포함하고 있습니다. 이 토큰은 인가 서버가 사용자 인증을 완료한 후, 액세스 토큰(Access Token)과 함께 클라이언트에게 발급됩니다.

제가 싱글 사인온(SSO) 시스템을 구축할 때 OIDC를 핵심적으로 활용했습니다. 여러 서비스가 하나의 인증 서버를 통해 사용자를 식별하고, 각 서비스에서는 ID 토큰을 검증하여 사용자를 신뢰할 수 있었죠. 덕분에 사용자들은 한 번의 로그인으로 여러 서비스에 접근할 수 있게 되었고, 각 서비스는 개별적으로 사용자 정보를 관리할 필요가 없어 보안 및 관리 효율성을 크게 높일 수 있었습니다.

ID 토큰 (JWT) 들여다보기

ID 토큰은 Base64 URL로 인코딩된 세 부분으로 구성됩니다: 헤더(Header), 페이로드(Payload), 서명(Signature). 제가 디버깅할 때 JWT 디코더를 자주 사용하는데, 실제 페이로드에는 다음과 같은 클레임(Claim)들이 포함되어 있습니다.

{
  "iss": "https://accounts.google.com", // 발행자 (Issuer)
  "azp": "클라이언트 ID", // 인가된 파티 (Authorized Party)
  "aud": "클라이언트 ID", // 수신자 (Audience)
  "sub": "사용자 고유 ID", // 주체 (Subject)
  "email": "user@example.com", // 이메일
  "email_verified": true, // 이메일 검증 여부
  "iat": 1678886400, // 발행 시간 (Issued At)
  "exp": 1678890000, // 만료 시간 (Expiration Time)
  "name": "홍길동", // 사용자 이름
  "picture": "프로필 사진 URL" // 프로필 사진
}

이러한 정보들을 통해 클라이언트는 사용자를 식별하고, 필요한 사용자 프로필 데이터를 얻을 수 있습니다. 중요한 것은 클라이언트가 ID 토큰의 서명을 검증하여 토큰이 위변조되지 않았음을 확인해야 한다는 점입니다.


OAuth 2.0/OIDC 흐름 완전 정복: 실전 시나리오

실제 서비스에 OAuth 2.0OIDC를 적용할 때 가장 많이 사용되는 흐름은 Authorization Code Grant Type with PKCE (Proof Key for Code Exchange)입니다. 특히 SPA(Single Page Application)나 모바일 앱과 같은 퍼블릭 클라이언트에서는 이 방식이 필수적입니다. 제가 모바일 앱 백엔드를 개발할 때 PKCE를 적용하지 않았다가 보안 검토에서 지적받고 부랴부랴 적용했던 경험이 있습니다. 그만큼 중요한 부분입니다.

흐름을 단계별로 살펴보겠습니다.

  1. 클라이언트, 인가 요청: 사용자가 클라이언트(앱/웹)에서 '로그인' 버튼을 클릭합니다. 클라이언트는 인가 서버로 사용자 인증 및 인가 코드 요청을 보냅니다. 이때 response_type=code, scope (예: openid profile email), client_id, redirect_uri, 그리고 PKCE를 위한 code_challengecode_challenge_method를 포함합니다.
  2. 사용자 인증 및 동의: 인가 서버는 사용자를 로그인 페이지로 리다이렉트하고, 사용자가 로그인하면 클라이언트가 요청한 권한(scope)에 대한 동의를 구합니다.
  3. 인가 코드 발급: 사용자가 동의하면, 인가 서버는 미리 등록된 redirect_uri인가 코드(Authorization Code)를 리다이렉트합니다. 이 코드는 일회성이며 짧은 유효 시간을 가집니다.
  4. 액세스 토큰 및 ID 토큰 요청: 클라이언트는 받은 인가 코드PKCE를 위한 code_verifier, client_id, redirect_uri를 포함하여 인가 서버의 토큰 엔드포인트에 POST 요청을 보냅니다. 이때 클라이언트 시크릿(Client Secret)이 필요한 경우도 있습니다 (컨피덴셜 클라이언트).
  5. 토큰 발급: 인가 서버인가 코드code_verifier를 검증한 후, 액세스 토큰, 리프레시 토큰(Refresh Token), 그리고 ID 토큰을 클라이언트에게 발급합니다.
  6. 리소스 서버 접근: 클라이언트는 발급받은 액세스 토큰을 HTTP Authorization 헤더에 담아 리소스 서버에 보호된 자원 접근을 요청합니다.
  7. 자원 제공: 리소스 서버액세스 토큰의 유효성을 검증하고, 유효하다면 요청된 자원을 클라이언트에게 제공합니다.

이 과정에서 PKCE는 인가 코드를 가로채더라도 액세스 토큰을 획득하기 어렵게 만드는 중요한 보안 장치입니다. 클라이언트가 인가 요청 시 생성한 code_verifier의 해시값인 code_challenge를 보내고, 토큰 요청 시 원본 code_verifier를 보내 인가 서버가 이를 검증하도록 합니다. 제가 겪어보니, 특히 모바일 앱이나 SPA처럼 클라이언트 시크릿을 안전하게 저장하기 어려운 환경에서는 PKCE가 필수 불가결한 요소였습니다.


OAuth 2.0과 OIDC를 활용한 안전한 사용자 인증 구현 전략 - bridge, flow, cityscape, architecture, building, berlin, structures, city, bridge, berlin, berlin, berlin, berlin, berlin

Image by wal_172619 on Pixabay

OAuth 2.0/OIDC 구현 시 고려해야 할 보안 전략 및 팁

OAuth 2.0OIDC는 강력한 도구이지만, 잘못 사용하면 오히려 보안 취약점을 만들 수 있습니다. 제가 직접 운영하면서 겪었던 몇 가지 중요한 보안 팁들을 공유합니다.

1. 토큰 관리: 액세스 토큰, 리프레시 토큰의 운명

  • 액세스 토큰 (Access Token): 짧은 유효 기간(보통 5분~1시간)을 갖도록 설계하고, 민감한 정보는 담지 않는 것이 좋습니다. 탈취되어도 피해를 최소화하기 위함입니다. 클라이언트에서는 메모리나 IndexedDB 등 비교적 안전한 공간에 저장하고, 서버로 보낼 때는 항상 HTTPS를 사용해야 합니다.
  • 리프레시 토큰 (Refresh Token): 액세스 토큰보다 긴 유효 기간을 가지며, 새로운 액세스 토큰을 발급받는 데 사용됩니다. 리프레시 토큰은 탈취될 경우 심각한 위험을 초래하므로, HTTP Only Secure Cookie에 저장하는 것이 가장 안전합니다. 자바스크립트에서 접근할 수 없으므로 XSS 공격으로부터 보호받을 수 있습니다.

제가 운영하던 서비스에서 액세스 토큰의 유효 기간을 너무 길게 설정했다가, 짧은 기간이지만 토큰 탈취 위험성이 크다는 지적을 받았습니다. 이후 유효 기간을 15분으로 대폭 줄이고, 리프레시 토큰을 이용해 자동 갱신하는 방식으로 변경했습니다. 이처럼 토큰의 수명 주기를 적절히 관리하는 것이 중요합니다.

2. 스코프(Scope) 최소화 원칙

클라이언트가 요청하는 스코프는 필요한 최소한의 권한만을 요청해야 합니다. 예를 들어, 사용자 이메일만 필요하다면 openid email만 요청하고, profile 같은 추가 정보는 요청하지 않는 것이 좋습니다. "혹시 나중에 필요할지도 모르니 다 받아두자"는 생각은 보안에 치명적입니다. 사용자가 과도한 권한 요청에 거부감을 느낄 수도 있습니다.

3. CSRF 및 XSS 방어

OIDC 로그인 과정에서도 CSRF(Cross-Site Request Forgery) 공격에 취약할 수 있습니다. 이를 방어하기 위해 state 파라미터를 반드시 사용해야 합니다. 클라이언트가 인가 요청 시 고유한 state 값을 생성하여 URL에 포함하고, 인가 서버로부터 리다이렉트 받을 때 이 state 값이 일치하는지 확인해야 합니다. 제가 직접 state 파라미터 누락으로 인한 보안 경고를 받은 후, 모든 로그인 흐름에 state 검증 로직을 추가했습니다.

XSS(Cross-Site Scripting) 공격은 클라이언트 측에서 사용자 입력을 제대로 검증하지 않을 때 발생할 수 있습니다. ID 토큰이나 액세스 토큰을 저장할 때 자바스크립트 접근이 가능한 localStoragesessionStorage 대신, HTTP Only Cookie를 활용하거나, 최소한 토큰을 다루는 스크립트 코드에 대한 보안을 강화해야 합니다.

4. OAuth 2.0 Grant Type 선택 가이드

OAuth 2.0에는 여러 Grant Type이 존재하지만, 보안 관점에서 모든 타입이 동등하게 권장되지는 않습니다. 다음은 제가 실무에서 주로 고려하는 비교표입니다.

Grant Type 설명 주요 사용처 보안 고려사항 권장 여부
Authorization Code + PKCE 인가 코드를 통해 토큰을 발급받고, PKCE로 코드 가로채기 방지 SPA, 모바일 앱, 웹 애플리케이션 가장 안전하며, 인가 코드 유출 시에도 안전 강력 권장
Client Credentials 클라이언트 자격 증명(ID/Secret)으로 직접 토큰 발급 서버 간 통신, 기계 간 API 호출 클라이언트 시크릿 관리가 중요 특정 시나리오에서 권장
Resource Owner Password Credentials 사용자 ID/비밀번호를 클라이언트가 직접 받아 토큰 발급 인가 서버와 클라이언트가 동일한 서비스일 경우 (레거시) 사용자 비밀번호 유출 위험이 커서 보안에 매우 취약 비권장
Implicit 인가 코드를 거치지 않고 액세스 토큰을 바로 URL Fragment로 전달 과거 SPA에서 사용 액세스 토큰이 URL에 노출되어 탈취 위험이 큼 비권장 (PKCE로 대체)

테이블에서 보듯이, Authorization Code Grant with PKCE가 퍼블릭 클라이언트를 위한 가장 안전하고 권장되는 방법입니다. 제가 직접 프로젝트에 적용해 보니, 복잡해 보여도 초기 설정만 잘 해두면 이후 유지보수와 보안 관리 측면에서 훨씬 유리하다는 것을 깨달았습니다.


OAuth 2.0과 OIDC를 활용한 안전한 사용자 인증 구현 전략 - flow, moated castle, hamburg, architecture, elbe, historical, hamburg, hamburg, hamburg, hamburg, hamburg, architecture, architecture

Image by dmncwndrlch on Pixabay

OAuth 2.0과 OIDC, 언제 어떻게 활용해야 할까?

OAuth 2.0OIDC는 단순히 '로그인' 기능을 넘어 다양한 시나리오에서 강력한 보안 솔루션을 제공합니다.

1. 싱글 사인온 (SSO) 구현

저희 회사에서 여러 내부 서비스에 대한 SSO를 구축할 때 OIDC를 활용했습니다. 사용자들은 한 번의 로그인으로 인사 시스템, 프로젝트 관리 도구, 사내 메신저 등 모든 서비스에 접근할 수 있게 되었습니다. OIDCID 토큰을 통해 각 서비스는 사용자 신원을 신뢰하고, 액세스 토큰으로 각 서비스의 API에 접근 권한을 부여했습니다. 덕분에 사용자 편의성과 더불어 관리자의 계정 관리 부담도 크게 줄일 수 있었습니다.

2. 마이크로서비스 아키텍처에서의 인증/인가

복잡한 마이크로서비스 환경에서 각 서비스가 개별적으로 사용자 인증을 처리하는 것은 비효율적이고 보안 위험도 높습니다. 이때 인가 서버(Authorization Server)를 중심으로 OIDC를 활용하여 사용자를 인증하고, 발급된 액세스 토큰을 통해 각 마이크로서비스 API에 접근 권한을 부여할 수 있습니다. 게이트웨이에서 토큰을 검증하고, 유효한 토큰만 내부 서비스로 전달하는 방식이 일반적입니다. 제가 맡았던 프로젝트에서 마이크로서비스 간의 통신 보안을 강화할 때 이 방식을 채택하여 일관된 보안 정책을 유지할 수 있었습니다.

3. 외부 서비스 연동 (Open Banking, 소셜 로그인 등)

금융 서비스의 Open Banking API나 카카오, 네이버, 구글 등 소셜 로그인 기능은 모두 OAuth 2.0OIDC를 기반으로 합니다. 제가 직접 소셜 로그인 기능을 추가할 때, 각 플랫폼의 인가 서버리소스 서버 역할을 이해하고, 스코프를 적절히 요청하여 사용자 정보를 가져오는 과정을 구현했습니다. 이 과정에서 redirect_uri의 중요성과 state 파라미터의 필요성을 다시 한번 체감할 수 있었습니다.


마무리: 안전한 인증, 개발자의 필수 역량

지금까지 OAuth 2.0OIDC를 활용한 안전한 사용자 인증 구현 전략에 대해 저의 실무 경험을 바탕으로 이야기해 보았습니다. 핵심을 다시 한번 요약하자면, OAuth 2.0인가를 위한 프레임워크이고, OIDCOAuth 2.0 위에 얹어진 인증 계층이라는 점입니다. 그리고 퍼블릭 클라이언트에서는 Authorization Code Grant with PKCE 방식이 가장 안전하며 권장됩니다.

토큰 관리, 스코프 최소화, CSRF/XSS 방어 등 보안 고려사항들을 철저히 지키는 것이 중요합니다. 이 모든 것이 처음에는 다소 복잡하게 느껴질 수 있지만, 한 번 제대로 이해하고 구축해두면 서비스의 신뢰성과 안전성을 크게 높일 수 있습니다. 제가 이 과정을 직접 겪으며 느낀 것은, 보안은 선택이 아니라 서비스의 성공을 위한 필수 요소라는 것입니다. 항상 새로운 공격 기법이 등장하므로, 지속적으로 학습하고 시스템을 개선하는 노력이 필요합니다.

이 글이 여러분의 서비스에 안전한 사용자 인증 시스템을 구축하는 데 조금이나마 도움이 되기를 바랍니다. 궁금한 점이나 여러분의 경험을 댓글로 공유해주세요. 함께 성장해나가는 개발 공동체가 되기를 희망합니다!

📌 함께 읽으면 좋은 글

  • [보안] API Gateway를 활용한 마이크로서비스 API 보안 강화: 핵심 전략 분석
  • [이슈 분석] IT 경기 둔화 속 개발자 채용 시장 변화와 커리어 성장 전략
  • [보안] 클라우드 환경 시크릿 관리: HashiCorp Vault와 AWS Secrets Manager 심층 비교

이 글이 도움이 되셨다면 공감(♥)댓글로 응원해 주세요!
궁금한 점이나 다루었으면 하는 주제가 있다면 댓글로 남겨주세요.

반응형