Spring

[Spring] OAuth 2.0 개념과 원리

PlatinumeOlive 2024. 3. 19. 14:27

 

Spring Boot로 새로운 토이 프로젝트를 시작하게 되었습니다. 이번 프로젝트에서는 처음으로 카카오 페이 송금 링크 기능을 사용하기 위해 소셜 간편 로그인을 사용하기로 하였습니다. 그래서 단순히 코드만 붙여 넣지 말고, 제대로 한번 이해하고 공부해보는 겸, Spring Security도 복습할 겸 글을 작성해보려고 합니다.

OAuth란?

OAuth는 구글, 페이스북, 네이버, 카카오 등 다양한 플랫폼의 특정 사용자 데이터에 접근하기 위해 제 3자 클라이언트 (우리의 서비스)가 사용자 접근 권한을 위임(Delegated Authorization)받을 수 있는 표준 프로토콜입니다. 쉽게 말해, 어떤 서비스를 만들 때 사용자에게 아이디와 패스워드를 비롯한 개인정보와 인증에 대한 부분을 타 플랫폼에 위임하는 서비스 입니다. 

 

어떤 플랫폼에서 카카오로 간편 로그인을 하면, 카카오에서 관리하는 내 이름과 이메일, 전화번호 등 개인정보를 해당 플랫폼에서 사용할 수 있는이유가 바로 OAuth를 이용하기 때문입니다.

 

그럼 이 서비스는 어떤 이유로 등장하게 된건지도 함께 알아보겠습니다.

등장 배경

우리가 만들게 될 서비스에서 만약 구글 캘린더의 일정 정보를 가져오거나, 카카오 친구 목록을 연동하여 작동하는 기능이 있다고 가정해보겠습니다. OAuth 등장 이전에 이 기능을 가장 편하게 구현하려면, 클라이언트가 유저에게 구글/카카오의 아이디와 비밀번호를 받아 사용자 대신 로그인을 하여 정보를 가져오도록 해야 했습니다.  

 

사람들이 규모가 작은 여러 서비스 이용을 위해 결제정보가 담긴 구글/카카오 등의 계정 정보를 넘겨야 한다면, 그 정보를 쉽게 내줄 수 있는 사람은 매우 드물거라고 생각합니다. 보안이 잘 되어있는지 판단할 수 없고, 믿을만 한 서비스 제공자인지도 모르는 상황에, 언제 어떻게 내 개인정보가 털릴지 모르기 때문입니다.

 

심지어 필자인 저를 비롯해 많은 사람들은 기억에 용이하게 하기 위해 여러 사이트의 비밀번호를 같거나 유사하게 만드는데, 이것들이 유출된다면 피해는 우리의 서비스에 국한되지 않을 것 입니다. 이 점은 구글, 카카오 등 대형 플랫폼에서도 큰 부담이면서, 자신들의 사용자 정보를 남에게 그대로 넘겨주는 꼴이니 좋아할 리가 없습니다.

 

또한, 비밀번호를 변경하지 않는 이상 제3의 클라이언트에게 부여된 권한을 회수할 수 없는 문제점도 있습니다.

 

이 문제점들을 해결하고자 등장한것이 바로 우리가 지금 다루고 있는 OAuth입니다.

 

어플리케이션 등록

OAuth 2.0을 사용하기 위해서는 먼저, 클라이언트를 Resource Server에 등록해야 합니다.  이 과정은 각 플랫폼의 디벨로퍼 사이트(KaKao Developers 등)에서 진행하면 됩니다. 이때 우리는 Redirect Url이라는걸 등록해야 하는데, 이것은 사용자가 OAuth 2.0서비스를 마치고 사용자를 리디렉션 시킬 위치 입니다. (예를 들어 카카오 간편 로그인을 마치고 돌아갈 메인 화면 지정 등)

 

Redirect Url을 지정하는 이유

OAuth 2.0 서비스는 인증이 성공한 사용자에 대해 사전에 등록된 Url의 목록들로만 리디렉션 시킵니다. 승인되지 않은 Url의 경우 Authorization Server에서 제공하는 Authorization Code가 중간에 탈취당할 수 있기 때문입니다.

 

또한 Redirect Url은 보안상의 이유로 Https만 허용되는데, localhost의 경우 예외적으로 Http가 허용됩니다. (로컬 환경에서의 개발 편의를 위해)

 

OAuth 2.0 동작 과정

  

OAuth 2.0 Sequence Diagram

위 그림은 OAuth 2.0의 동작과정을 한 눈에 볼 수 있도록 나타내고 있습니다. 아래에서 한번 살펴보도록 하겠습니다.

 

 OAuth 2.0 주체

Resource Owner

리소스의 소유자, 즉 위에서 지칭한 '사용자'에 해당합니다. 해당 서비스를 이용하면서 구글, 카카오 등의 플랫폼의 리소스(카카오 친구목록, 구글 캘린더 일정 정보 등)을 소유하고 있는 주체입니다.

 

Authorization & Resource Server

둘 다 사용자의 간편로그인을 진행할 서버(구글, 카카오가 가지고 있는 서버)입니다.  공식 문서에는 별개로 구분되어 있지만, 꼭 별도로 존재하지 않고 하나로 구성되어 있을 수 있습니다. (해당 플랫폼 회사 서버 구조에 따라 상이함)

 

Authorization Server는 사용자의 인증을 진행하고 Client에게 AccessToken을 발급하는 서버이며, Resource Server는 실제 사용하고자 하는 리소스(카카오 친구목록 등)을 소유한 서버 입니다.

 

Client

사용자의 요청에 따라 Resourcer Server에 있는 자원을 이용하고자 하는 서비스입니다. 현재 우리가 개발하려는 서비스에 해당합니다.

 

사용하는 파라미터

  • response_type : Code로 설정합니다. 인증이 성공할 경우 클라이언트는 Authorization Code를 받을 수 있습니다.
  • client_id : 애플리케이션 생성당시 발급받은 Client Id. 서비스의 식별자이며 노출되어도 상관 없습니다.
  • client_secret : 애플리케이션 생성당시 발급받은 서비스의 비밀번호. 절대 외부에 노출되어선 안됩니다.
  • scope : Client가 부여받은 Resource 접근 권한입니다. (이름, 이메일 등 세부 사항 설정 가능)
  • grant-type : 항상 Authorization Code로 설정해야 합니다.
  • code : 발급받은 Authorization Code

OAuth 2.0 스코프

기존에 OAuth 1.0에는 존재하지 않았지만 2.0이 되면서 추가된 기능 중 하나가 스코프 입니다. 이 스코프를 통해서 유저 리소스에 대한 클라이언트의 접근 범위를 제한할 수 있습니다. 스코프는 여러개로 구성될 수 있으며, 대소문자를 구분하는 문자열을 공백으로 구분하여 표현됩니다. 이때 문자열은 OAuth2.0 인증 서버에 의해 정의됩니다.

Kakao Developers 동의항목

 

위 사진과 같이, 우리의 서비스는 카카오 디벨로퍼스에서 유저에게 닉네임, 이름 등 요청 항목을 지정할 수 있습니다. 이렇게 선택된 동의 항목은 OAuth 2.0 제공자에게 스코프 문자열 형식으로 전달됩니다.

 

 

이후 사용자는 위 사진과 같이 스코프에 명시된 권한을 요청하는 화면을 만나게 됩니다. 이 과정을 거쳐 발급된 액세스 토큰은 스코프에 해당하는 권한을 제한적으로 획득하게 됩니다.

 

동작 과정

다시 본론으로 돌아와, 동작과정을 천천히 살펴보도록 하겠습니다. 

OAuth 2.0 Sequence Diagram

1 ~ 2. 로그인 요청

클라이언트 (우리의 서비스)는 리소스 소유자(유저)에게 간편 로그인 화면을 표시하게 됩니다. 이때 유저가 버튼을 누르게 되면, 리소스 오너를 리소스 서버의 인증 화면으로 이동시킵니다. 파라미터를 포함하여 쿼리 스트링으로 전달하게 되는데, 아래와 같은 형식으로 전달하게 됩니다.

https://kakao-authorization.com/auth2?response_type=code
&redirect_uri=https://my-service.com/callback
&client_id=100
&scope=create+delete

 

3 ~ 4. 로그인 페이지 제공 및 로그인

클라이언트가 빌드한 Authorization URL로 이동된 리소스 오너는 제공된 로그인 페이지에서 아이디와 비밀번호를 입력하여 인증을 진행합니다.

 

리소스 서버는 요청을 받으면, 리소스 오너가 기존에 로그인이 되어있는지 여부에 따라, 로그인이 되어있지 않다면 로그인을 요청하고, 리소스 오너가 로그인을 했다면 아래와 같은 검증 절차를 거칩니다.

 

  • URI는 클라이언트에서 '카카오 간편 로그인'과 같은 버튼에 넣어둔 URI
  • 클라이언트 아이디 값을 리소스 서버에서 가지고 있는지 확인
  • 리소스 서버가 저장하고 있는 클라이언트 아이디에 대한 Redirect URL이 같은지 확인

하나의 과정이라도 실패하게 되면 작업을 종료하고, 성공한다면 클라이언트에게 Scope에 해당하는 권한을 클라이언트에게 부여할 것인지 확인하는 메시지를 전송합니다. 

 

5 ~ 6. Authorization Code 발급, Redirect URI로 이동

인증 성공시 Authorization Server는 제공된 Redirect URI로 사용자를 리디렉션 시킵니다. 이 때, Authorization Code를 포함하여 리디렉션 시키게 됩니다.

Authorization Code는 Client가 Access Token을 얻기 위해 사용하는 임시 코드이며, 수명은 1분에서 10분 정도로 매우 짧습니다.

 

7 ~ 8. Authorization Code 전송, AccessToken 발급

Client는 인증 서버에 Authorization Code를 전달하고 Access Token을 받습니다. Client는 발급받은 유저의 AccessToken을 DB에 저장하고, 이후 리소스 서버에서 유저의 리소스에 접근할 때 사용합니다.

 

Access Token은 유출되어서도 안되고 제 3자가 가로챌 위험이 존재하기에, HTTPS 연결을 통해서만 사용할 수 있습니다. (Localhost는 예외)

 

클라이언트가 보낸 Authorization Code와 AccessToken은 token 엔드포인트에서 교환됩니다. token 엔드포인트에서 Access Token을 발급받기 위한 HTTP 요청은 아래와 같으며, application/x-www-form-urlencoded 형식에 맞춰 전달해야합니다.

POST /oauth/token HTTP/1.1
Host: kakao-authorization.com/auth2

grant_type=authorization_code
&code=***
&redirect_uri=https://my-service.com/callback
&client_id=100
&client_secret=****

 

이후 로그인이 성공하게 되면, 발급받은 Access Token을 바탕으로 리소스에 접근하여 원하는 정보를 가져올 수 있습니다.

(9 ~ 13 과정) 

 

Authorization Code를 사용하는 이유는 무엇일까?

왜 Client에게 곧바로 AceessToken을 발급하지 않고,  번거롭게 Authorization Code를 발급하는 과정이 있을까요?

 

위 과정에서 Redirect URL을 통해 Authorization Code가 아닌 Access Token을 직접 전달한다고 가정해봅시다. Redirect URL를 통해 데이터를 전달할 방법은 URL 자체에 데이터를 실어 전달하는 방법밖에 존재하지 않습니다. 그렇기에 Access Token과 같은 민감한 데이터를 곧장 실어서 보내야 하는데, 이는 곧 브라우저를 통해 데이터가 그대로 노출된다는 것을 의미합니다.

 

이런 사고를 방지하기 위해, Redirect URL을 프론트엔드 주소로 설정하여, Authorization code를 프론트에 전달합니다. 프론트는 백엔드에 이 Authorization Code를 전달하고, 백엔드는 받은 코드를 Authorization Server의 token 엔드포인트로 요청하여 Access Token을 발급받고, DB에 저장하는 과정을 거칩니다.

 

위 과정을 거쳐 Acceess Token은 애플리케이션과 OAuth 서비스의 백채널을 통해서만 전송되기에 공격자가 토큰을 가로챌 수 없다는 장점이 생깁니다.

 

프로젝트에서 백엔드와 프론트엔드 역할 분리

이번 프로젝트에서 어떻게 역할을 분리해야할지 생각해보았습니다. 크게 네가지로 나눌 수 있을 것 같습니다.

 

1. 백에서 모든 소셜 로그인 처리를 다 하는 방법

2. 프론트에서 모든 소셜 로그인 처리를 다 하는 방법

3. 프론트에서 서버에 Authorization Code를 전달하는 방법

4. 프론트에서 AccessToken까지 발급받은 뒤 , 서버에 AccessToken을 전달하는 방법

 

1번은 같은 도메인이 아니라면 Cors 및 Redirect 설정때문에 적용이 어려웠습니다. 로그인 및 회원가입을 성공했을 때 백에서 프론트 화면으로 돌아가도록 해야하는데, Redirect 처리가 어려웠습니다. 

 

2번의 경우,  프론트에서 next Auth나 Android SDK와 같은 모듈에서 모든 처리를 다 한 뒤,  받아온 회원 정보를 바탕으로 일반 회원가입처럼 처리하는 방법입니다. 로그아웃, 연결 끊기, 추후 메시지 보내기 등의 처리를 할 수 없다는 점, 비밀번호가 없기때문에 임의의 비밀번호로 처리해야하는데 이 임의의 비밀번호가 노출되면 많은 사용자의 정보가 일괄적으로 유출될 수 있다는 점의 단점만 존재하였습니다.

 

그래서 저는 3, 4 번 중 하나를 골라야 했습니다. 위 포스팅에서 처럼 3번의 경우가 어찌 보면 정석이라고 할 수 있지만 카카오 디벨로퍼스 포럼에 카카오가 답변한 내용을 보면, https 연결 상에서는 3번과 4번 두 방식의 보안 스펙이 큰 차이가 없어서, 유동적으로 결정해도 좋다는 답변이 있습니다. 그래서 저는 논의 끝에 4번 방식을 적용하기로 결정하였습니다. 

 

 

프론트는 사용자가 소셜 로그인 버튼을 누르면, 카카오 페이지로 리디렉트 시켜서 인증을 끝마치고, 백엔드는 전달받은 AccessToken으로 카카오 로그인 및 회원가입, 정보 조회 등의 작업을 수행하겠습니다.

 

 

 

이제 이론을 알아보았으니, 직접 구현해보도록 하겠습니다.

 

참고

 

OAuth 2.0 개념과 동작원리

2022년 07월 13일에 작성한 글을 보충하여 새로 포스팅한 글이다. OAuth 등장 배경 우리의 서비스가 사용자를 대신하여 구글의 캘린더에 일정을 추가하거나, 페이스북, 트위터에 글을 남기는 기능을

hudi.blog

 

[Spring] Spring Security 기본 개념 (JWT / OAuth2.0 / 동작 방식 / 구성 요소)

JWT (Jason Web Token) 유저 인증, 식별하기 위한 토큰 기반의 인증 구조 헤더 (Header) 타입 (type) : 항상 JWT 알고리즘 (alg) 페이로드 (Payload) : 사용자 정보 담김 서명 (Verify Signature) 동작 방식 1. 클라이언

datamoney.tistory.com

 

[OAuth] OAuth 2.0의 개념과 과정

안녕! 우아한테크코스 5기 [스탬프크러쉬]팀 깃짱이라고 합니다. 스탬프크러쉬 서비스의 소스 코드 바로가기 💋 인트로 우테코 프로젝트를 진행하면서 회원가입 및 로그인 기능을 구현해야 했

engineerinsight.tistory.com

 

OAuth.com - OAuth 2.0 Simplified

OAuth 2.0 is the modern standard for securing access to APIs. OAuth 2.0 Simplified is a guide to building an OAuth 2.0 server. Through high-level

www.oauth.com