안녕하세요. 이번 포스팅에서는 Oauth2를 활용한 카카오 로그인 과정에서 고민했던 문제에 대해서 공유하고자 합니다. 카카오 로그인 관련된 포스팅은 2편에 이어서 할 예정이며 내용은 아래와 같습니다!
- 외부 API를 사용하는 클래스를 어디서 관리해야 하는가(모듈)? - 이번 포스팅
- 외부 API를 어떻게 테스트 할까? - 다음 포스팅
Interface와 모듈 분리를 사용하여 구현하였습니다. 관련 코드는 여기서 확인하실 수 있습니다. 👈
카카오 로그인 프로세스
많은 분이 OAUTH2를 사용한 소셜 로그인을 많이 구현해 보셨을거에요! 그래서 이번 포스팅은 구현보다는 Oauth2 전체 프로세스에 대해서만 간략하게 언급하겠습니다. 잘 아시는 분은 바로 아래로 넘어가시면 될 것 같아요!
카카오 로그인이 이루어지는 과정은 아래와 같습니다. 각 과정에서 주의해야 할 점들을 간단하게 요약했습니다. 아래의 프로세스가 아시는 분에게는 쉽겠지만, 처음 해보는 분들께는 어려울 수 있습니다.(저에게 어려웠기 때문에..)
중요한 포인트만 요약하자면 인증된 사용자라는 것이 확인된 결과로 카카오 서버는 CODE를 반환합니다. 이 코드를 바탕으로 해당 유저에 접근할 수 있는 토큰을 요청하고 응답으로 받은 토큰으로 사용자 정보를 조회하거나, 다른 요청을 추가로 보낼 수 있습니다.
- 사용자는 카카오 로그인을 버튼을 클릭 한다.
- 서버나 클라이언트에서 카카오 로그인 페이지로 리다이랙트한다.
- 사용자는 카카오 로그인 페이지에서 로그인을 하고 확인을 누른다.
- 카카오 서버는 해당 사용자를 확인하여 인증된 사용자인 경우 우리 서버로 요청을 보낸다.
- 카카오 서버는 이 때 우리가 등록한 URL을 사용한다.
- 인증된 사용자라는 것을 증명하기 위해서 URL에 Query Param으로 Code라는 것을 함께 보낸다.
- 우리 서버는 카카오에 등록한 정보를 함께 카카오 서버로 요청한다. - 이에 카카오 서버는 토큰을 응답한다.
- 카카오 로그인에서 주의할 점은 ClientID, 4번에서 받은 Code 등의 정보를 QueryParam으로 보내야 한다.
- 다른 소셜 로그인은 일반적으로 Body를 사용하는데, 카카오는 QueryParam으로 보내야 한다!
- 카카오에서 받은 Token을 다시 카카오에게 보내며 사용자 정보를 요청한다.
- 응답 받은 카카오 유저 정보를 바탕으로 우리의 사용자를 생성한다.
추가로 한 일
위의 과정을 완료하면 카카오 로그인은 완료가 됩니다. 여기에 추가적으로 저는 카카오에서 제공하는 토큰을 저의 서비스 토큰으로 변환하는 작업을 추가하였습니다. 이러한 작업을 수행한 이유는 다음과 같습니다.
- 이미 카카오 토큰에 종속적이지만, 카카오 토큰을 그대로 사용하는 것은 저의 서비스가 카카오에 너무 종속적인 형태가 된다고 생각했습니다.
- 토큰이란 저의 서비스에 대한 인증 및 인가를 하기 위한 도구입니다. 즉 제가 직접 컨트롤 할 수 있어야하며 서비스에 대한 토큰 전략을 결정할 수 있어야 합니다.
외부 API 관리
카카오 로그인을 하기 위해서 추가한 코드들은 어떤 패키지에 위치하는 것이 바람직할지 고민해 보았습니다.
회원과 로그인의 관계에 대해서 고민 해본다면 이에 대한 답을 찾을 수 있을 것 같습니다.
Q. 질문
- 소셜 로그인은 회원 패키지에서 담당해야 하는 역할인가?
A.답변
- 관심사의 분리 관점에서 회원과 로그인 모듈은 분리하는 것이 바람직하다고 생각합니다. 회원 패키지는 회원과 관련된 기능만을 수행해야 합니다. 로직의 규모가 작은 경우 로그인 관련 로직이 회원 도메인에 들어올 수 있지만, 장기적인 관점에서 회원과 로그인의 관심사를 분리함으로써 회원은 조금 더 순수하게 회원 관련된 로직에만 집중할 수 있습니다. 둘의 관심사를 분리하는 것이 어려운 일이라면 굳이 지금 할 필요는 없다고 생각합니다. 다만 분리하는 것과 분리하지 않는 것의 차이가 미비하기 때문에 로직이 적은 지금도 분리하는 것이 낫다고 생각합니다.
- 변경이 자주 일어나는 곳이 덜 일어나는 곳에 의존해야 한다. 소셜 로그인은 카카오 로그인뿐 아니라 네이버, 페이스북 등 다양하게 있습니다. 네이버 로그인이 새로 추가되는 경우 회원 패키지의 코드를 변경해야 할 확률이 높아집니다. 하지만 LoginService가 MemberService에 의존하는 경우 새로운 로그인 방법이 추가되어도 변경의 확률이 거의 희박합니다.(로그인 및 회원가입에서 사용하는 MemberService API는 create & find 정도이기 때문에 거의 바뀔 일이 없는 API입니다.) 따라서 변경이 상대적으로 자주 일어나는 곳에서 적게 일어나는 곳을 의존하는 것이 맞다고 생각합니다.
- 물론 KakaoLoginService , NaverLoginService를 추상화한 LoginService에 의존한다면 새로운 로그인 방법이 추가되어도 회원 패키지를 변경할 일은 적을 것입니다. 하지만 그럼에도 불구하고 외부 API 스펙이 바뀐다면 추상화된 인터페이스도 변경의 여지가 있습니다. 즉 변경될 확률이 조금 더 높다고 생각합니다.
- 위와 같은 이유로 저는 Login과 Member패키지를 분리하고 Login 패키지에서 Member 패키지를 의존하는 형태로 구상하였습니다.
사실 Oauth2라는 규약과 함께 외부 모듈이 드라마틱하게 변경될 일은 없을 것 같습니다. 또한 사소한 변경은 LoginService의 구현체만 변경하면 되기 때문에 서로 어떤 형태로 의존하더라도 크게 문제는 없을거라 생각합니다. 다만 어떤게 조금 더 합리적일까라는 관점에서 저의 생각이니, 혹시 이상하거나 다르게 생각하신다면 알려주시면 감사하겠습니다!!
잘봤읍니다