Back

소셜 로그인(서드파티 로그인)

기존 spring security 의 form login 방식을 통해 생성된 쿠키로 사용자 인증을 하는, 이미 운영되고 있는 서비스에 추가로 별도의 웹 클라이언트를 만들었으며, 거기에 필요한 소셜로그인(카카오, 네이버, 구글)을 추가한 경험이 있다. 이 때 서버 쪽에서 필요한 요청들은 내가 했고, 프론트엔드 쪽은 외주 개발자가 했었다.

세 플랫폼 모두 외주 프론트엔드 개발자와 협의할 때 아무렇지 않게 각 플랫폼의 로그인 성공 이후 받아오는 어떤 특정한 값 자체를 DB 내 별도로 생성한 소셜 로그인 테이블에 사용자 구분이 가능한 데이터와 함께 관리하였으며, 각 플랫폼의 로그인 인증이 성공하면 응답으로 받는 그 특정한 값을 통해 사용자를 구분하고 로그인 처리를 해줬다.

그런데, 어쩌다 이 특정한 값이 같은 사용자로 로그인을 하더라도 요청마다 변할 수도 있다는 것을 들었다. 또한 당시에는 잘 안 봐서 몰랐는데, 정확한 공식문서를 확인해보니 사용자 인증 처리를 내부적으로 어떻게 구현해야 하는지에 대한 모범 사례라던가 예제와 관련한 내용이 없었다. 그리고 단순하게 그런 값을 한 번에 받아오는 것도 아니고 너무나도 복잡한 여러 가지 과정이 있었다. 놀라서 거의 일 년 만에 실제 운영 중인 곳에서 다시 테스트를 해봤는데, 로그인 처리가 잘 되는 것 같다.

내 기억이 어디부터 잘못된 것인지, 심지어 당시에 했던 방식도 뭔가 정확한 방식이 아닌 것 같기도 하고 제일 문제가, 이미 운영되고 있는 서비스에 로그인 문제가 있는 것이 아닐까 생각이 들면서, 별다른 연락이 없었으니 잘 동작하지 않을까 싶기도 하고 직접 제대로 확인을 해봐야겠다는 생각이 들었다.

업무를 진행했던 기록들이 남아있어 사진과 코드를 통해 확인하고, 나름대로 정리하여 작성한다.


카카오

당시에 구현된 방법은 다음과 같다.

javascript용 SDK를 사용했다. 로그인에 성공하면, 응답으로 access_token이라는 값을 받게 된다. 이후 백엔드에 별도로 요청을 하는데 요청하면서 파라미터에 access_token 이라는 값을 넘긴다.

SDK 없이, api만을 사용하여 구현하는 방법이 공식 문서에 있는데, 이에 대한 설명은 저 access_token을 받는 과정까지의 자체가 조금 복잡하다. 인가코드라는 것을 받기 위한 요청부터 시작한다. Redirect URI를 통해 인가코드를 백엔드에서 받고, 이후 백엔드에서는 그 인가코드를 이용하여 access_token을 받기 위한 요청을 따로 해야 하는 것이 정석이라고 나와 있다.

외주 개발자가 구현한 것은 간단하게 SDK에서 팝업 방식으로 로그인이라는 기능을 사용하기 위한 함수를 사용했다. 인가코드라는 것을 생각할 필요 없이 함수에 성공, 실패 여부에 따라 실행해야 할 콜백만 넘겨주고, 성공했을 때 실행하는 함수는 백엔드에 access_token을 담아 요청하는 것으로 구성이 되어있다.

이후 백엔드에서는 내부적으로 access_token을 통하여 사용자 정보를 받아오는 요청을 따로 하게 했다. 거기서 받아온 id라는 key로 이루어진, 숫자로 구성이 되어있는 값이 넘어온다.

이것을 사용하여 사용자 테이블의 특정 사용자와 구분이 가능하도록 즉, 1:1로 연결이 가능하게 하기 위하여 생성한 테이블에 존재하는 사용자인지 여부를 체크했다. 그렇게 이미 있으면 그냥 로그인, 없으면 플랫폼 관련 코드와 시퀀스로 늘어나는 값을 채번하고 이를 아이디로 사용하여 새로운 사용자를 포함한 유저에 필요한 각종 데이터를 생성하여 로그인 처리를 했다.

내가 생각했던 이상하다는 그 코드가 이 id값인데, 숫자로만 이루어진 값이라 그렇게 생각한 것 같다. 그리고 변할 수 있다는 값은 이 id값이 아니고, SDK없이 api만을 사용하는 방식에서 access_token을 받기 위해 사용하는 인가코드라는 값을 뜻하는 것 같다.

그렇게 함께 넘어오는 닉네임과 이메일도 있는데 이런 특정 사용자임을 처리하는, 즉 기본키로 관리할 값을 이메일로 사용해도 될 것 같지만, 카카오톡 사용자의 이메일이 고객센터 요청을 통해 변경이 될 수 있다는 것을 생각해보니, 저 id라는 값으로 관리하는 것이 맞는 것 같다. 그때도 id가 카카오톡 내부에서 관리하는, 로그인한 계정의 고유의 값으로 생각하여 이것으로 사용했던 것 같다.

작성 이후 검색도 해봤는데 다른 블로그 예제를 참고도 해봤는데, 맞는 것 같다.

잠시 생각을 했었을 때 카카오톡 내부에서 친구추가 가능한 아이디를 사용하여 특정 사용자임을 판단 가능한 기본키로 사용이 가능할 수 있다고 생각했었다. 이는 카카오톡에서 사용자가 마치 닉네임처럼 바꿀 수 있다는 사실을 내가 까먹어서 그렇게 생각한 것 같다. 그리고 사용자 정보를 받아오는 요청을 했을 때 카카오톡으로 아이디가 응답으로 오지도 않는다. 개발자가 착각하여 이를 사용해 사용자 인증을 해버리면, 나중에 카카오톡 아이디가 바뀌었을 때 문제가 생길 수 있어서 그런 것으로 생각된다.


네이버

네이버도 네이버에서 제공해주는 SDK를 사용했다.

로그인 성공 시 받아오는 토큰을 백엔드에 넘겨준다.

이후 백엔드는 받은 토큰값을 이용하여 OAuth2 인증을 위한 별도의 요청을 더 실행하여 새로운 두 번째 토큰값을 받아온다. 이 OAuth2 인증을 위한 요청은 네이버 인증서버에서 CORS 허용을 막아두어 브라우저에서 요청을 못 하도록 하였는데, 클라이언트에서 이 두 번째로 받아오는 토큰은 노출되지 않도록 하기 위해 이렇게 처리한 것 같다. 아무튼 그렇게 백엔드 내부에서 받아오게 된다.

두 번째로 받아온 토큰을 통해 사용자 정보를 받아오는 요청을 하게 된다.

카카오처럼 네이버 역시 id값을 받아오는데, 응답받은 json 데이터에서 DATA 객체 내부 token 객체의 id라는 key로 이루어져 있으며, 이는 숫자와 영어로 이루어진 난수처럼 보이게 되어있다.

카카오에서 했던 것 처럼 네이버에서는 이 id값을 사용했다.


구글

구글 로그인은 이미 컴포넌트화된 라이브러리를 사용했다.

내부 동작이 어떻게 동작하는지 모르겠으나, 개발자도구로 봤을 때 브라우저상에서 구글 계정에 대한 자체 검증과 관련한 요청 한번, 이후 유저 정보를 받기 위한 요청 한번을 한다.

다른 플랫폼은 유저 정보를 받을 수 있는 토큰값을 클라이언트에서 확인할 수 없도록 하는데, 구글은 왜 유저 정보를 받기 위한 토큰을 백엔드에서 요청하지 않는지는 모르겠다.

라이브러리 자체가 위험한 방식으로 되어있는 것인지, 아무튼 그렇게 구현이 되어있다.

당시 캡쳐했던 사진인데, 무슨 정보들인지 몰라서 다 모자이크 처리를 했다.

그렇게 프론트에서 모두 처리되어 결국 받아온 사용자 정보 중 googldID라는 key로 받아오는 값이 있는데, 이는 숫자로만 이루어져 있다. 그리고 그 id를 사용했다.


특이사항

당시 작성했던 특이사항이 있다.

카카오의 경우 로그인 서비스를 요청하면 받는 애플리케이션 고유의 키값이 같아도 도메인이 다를 경우에 자체 서버인증을 할 때 생길 수 있는 문제가 있다.

도메인이 다른 경우 같은 카카오 계정 정보를 요청하여도 받아온 id값이 달랐으며, 이후 해당 계정의 카카오톡 애플리케이션에서 연결된 서비스 관리 목록에 보면, 같은 이름으로 등록된 내 애플리케이션이 도메인의 갯수만큼 표시가 되었다.

즉, 카카오는 도메인에 따라 로그인한 사용자의 id값이 다르니, 유의해야 한다. 나머지 플랫폼은 따로 관련하여 작성된 사항은 없지만, 테스트는 했는지 잘 모르겠다.

이런 문제가 있다고 알아차린 계기는 다음과 같다.
로컬 개발환경과 테스트 개발 서버는 같은 개발 DB를 바라보고 있었다.
그 상태로 테스트 개발 서버의 환경에서도 카카오 로그인 테스트를 진행하고 로컬 개발 환경에서도 카카오 로그인 테스트를 했었던 것 같다.
왔다 갔다 이런저런 테스트도 하고 개발을 하다가 어느 순간 카카오를 통한 로그인을 진행했을 때 내 카카오톡 계정은 이미 애플리케이션에서 요구하는 정보제공 동의를 이미 끝냈음에도 또 동의를 요구하는 과정이 생겼었다. 이후 소셜 로그인 관련 테이블을 조회하니 자체적으로 관리하는 하나의 사용자 아이디에 두 개의 카카오 인증 id가 부여되었다.

그때는 왜 이런 현상이 생기는지 많은 테스트를 했었고, 카카오 사용자 조회를 통해 받아오는 id값이 카카오 계정을 특정할 수 없는 값이라고 생각하기도 했었던 기억이 난다.
이것을 제대로 알아차리기까지 많은 시간이 필요했었다.


구현했던 것을 다시 보면서, 당시에는 기능 구현에만 급급하였고 시간에 쫓겨 정신없이 하다 보니 뭔가 이상하게 처리한 것도 있다.

타 플랫폼의 사용자 인증을 완료하여 자체적으로 관리하는 서버의 사용자 인증 상태를 만드는 것이 정해진 단 하나의 방법, 일반적인 로그인 요청을 사용하는 방법밖에 없었다고 생각을 했었는지, 좋지 않은 방식으로 처리를 했다.

당시 했던 것이 spring security 의 form login을 사용하면서 일반적인 로그인에 사용하는 API를 그대로 요청하고, 세션/쿠키 방식이라 응답이 오면 브라우저 쿠키에 인증에 필요한 값이 들어오고, 1000ms 정도 후에 홈 화면으로 redirect하여 인증이 완료된 화면으로 보여주게 하는 방법을 사용했었다.

문제는 저 방법을 사용하려면, 평문의 비밀번호를 알아야 로그인 요청을 하는데, 그것을 할 수 없었다. 복호화를 통해 비밀번호를 평문으로 바꾸는 범죄는 하지 않았다. 애초에 암호화 알고리즘 자체가 단방향이라 할 수도 없었다. 결국 구현했던 방식은 많이 더러운데, 작성하기에는 창피하다.


자체 서버의 사용자 인증 방식을 바꾼다고 했을 때를 생각해봤다.

각 플랫폼별 로그인을 하는 과정에서 OAuth2 방식으로 사용자 인증을 처리하여 JWT를 사용할 뿐이고, 자체 서버에서의 인증 처리의 방식은 정하기 나름인 것 같다.

타 플랫폼에서 그냥 로그인을 했다는 것 자체만 사용할 것인지, 아니면 받은 토큰을 통해 내부 서비스를 추가적으로 사용할 것인지에 따라 서비스를 사용할 수 있는 토큰을 클라이언트에 저장하고 있을지, 로그인 인증이 끝나는 순간 필요 없으니 관리하지 않을지도 나뉘며,
받아온 JWT을 그대로 인증에 사용할지, 내부의 별도의 인증 처리를 통해 사용할지 이런 부분에서도 여러 케이스가 나눠지기 때문에도 정해진 방식이 따로 없는 것 같다.

social login + 자체 인증을 동시 지원하는 경우 security 처리는?

관련하여 자바지기 박재성님 글을 확인하니, 소셜로그인 사용자와 자체적으로 관리하는 사용자를 JWT를 사용할 수 있도록 통합하는 구조를 설명하는 글이 있기도 하다.


정리를 해놓아야 나중에 찾아보기 쉬운 것 같다.
언젠가는 또 만지게 될 것인데, 빠르게 찾아 사용하도록 해야겠다.