[OAuth2] 소셜 로그인 구현 방식에 대한 시행 착오
간만에 소셜로그인을 구현하게 되었는데 이번엔 스프링 부트 Oauth2 라이브러리를 쓰지않고 구현해보려고 했다.
나처럼 웹개발에 익숙하지 않은 사람들에겐 라이브러리가 마냥 좋은 것 같진 않다.
내부적으로 어떻게 굴러가는지 알기 어려울 때가 많고 자칫하면 가장 중요한 개념을 놓치기 쉬운 것 같다.
그냥 소셜 로그인하는데 쓰이는 거야~ 라고만 알고 라이브러리로 쉽게 구현한들 프로토콜에 대해 이해하지 못하면
제대로 된 개발자라고 될 수 있을까.
Oauth2는 고객(Resource Owner)이 내 어플리케이션(Client)에 직접 그들의 소셜 아이디와 비밀번호를 주지 않아도 내 어플리케이션이 그들을 대신하여 소셜 서비스서버(Resource Sever)가 제공하는 고객의 자원(개인정보)에 대해 접근 권한을 위임 받는 인증 프로토콜이다.
이전에는 블로그에 올렸던 건 앞단은 JSP와 구현했어서 사실상 서버에서 모든 걸 처리하고 페이지를 주는 식이었는데
지금은 프론트(Vue)와 rest api 서버(Spring boot)로 나눠두었기 때문에 구현에 대해서 고민이 좀 많았다.
1. 프론트에서 어디까지 처리할 것인가?
2. 서버는 어디서부터 어디까지 처리할 것인가?
- 1번안 (자바스크립트 sdk) : 프론트에서 인가 코드, 엑세스 토큰, 사용자 정보까지 전부 다 받기 / 서버에서 사용자 정보 받고 로그인&가입 처리하기
- 2번안 (자바스크립트sdk+restApi방식) : 프론트에서 인가 코드, 엑세스 토큰까지 받기 / 서버는 사용자 정보 요청 후 로그인&가입 처리
- 3번안 (restApi 방식) : 프론트에서 인가 코드 요청까지만 하기 / 서버는 인가코드 redirect로 받고 엑세스 토큰, 사용자 정보 요청 후 로그인&가입처리
일단, 1번안에서 4번안까지 전부 직접 구현해보았다.
1번안의 경우 이건 프론트에서 다 처리하고 서버는 오는 요청만 받고 처리하니 서버가 너무 수동적이게 되버려 택하지 않았다. (보안상 괜찮을까?) 하지만 서비스 가입할 때 리소스 서버에서 제공하는 정보 외에 추가 필수 정보, 휴대폰 인증 등이 필요한 경우는 이런 식으로 써야할 거 같다.
2번안의 경우 이렇게 자바스크립트 sdk 방식과 rest api 방식을 같이 구현해도 괜찮을까? 싶었다.
서버에서 redirect uri로 인가 코드를 받는 것 자체도 인증의 일부가 아닐까 싶어 일단 바로 택하지 않았다.
3번을 먼저 구현해보았다. 프론트는 서버에 axios 요청하고 바로 응답받은 로그인url (요청 주소+서비스키+리다이렉트uri)를 팝업창을 띄워서 처리하도록 했다.
방법은 달라도 이전에 구현해보았기 때문에 어렵지 않게 연동이 되었으나 문제점이 하나 생겼는데, 팝업창 처리가 어려웠다.
로그인 후 리소스 서버는 첨부한 redirect uri(서버)로 리다이렉트하는데 팝업창을 거치기 때문에 최종 응답(내 서비스의 accessToken & refreshToken 담긴 Json)이 팝업창에 출력이 되어버린다.
클라이언트 서버 리소스 서버
-----------> 팝 -------------------------------------> 응답 생성 생략
업 <------------------------------------- 응답 리턴
<----??--- 창
프론트에서는 서버에 직접 요청을 보낸 것이 아니라서 이 json을 받을 방법을 찾을 수가 없었다.
저 로그인 url로 프론트에서 직접 axios get 요청을 보내보기도 했는데 CORS 에러가 뜬다.
(카카오 데브톡 답변을 빌리면 리다이렉트 될 것이기 때문에 axios로 요청할 수 없다고 한다. )
출력된 팝업창의 html을 읽더라도 창을 띄운 시점 후에 읽는 메서드의 호출 시점이나 창을 닫는 시점을 지정할 수도 없고 정말 하루종일 찾아보았는데 해결이 안됐다.
(물론 내가 못 찾은 게 맞을 거다. 누군가에겐 저걸 왜 못 해? 겠지. 열심히 공부해야겠다.)
나중에 문의해보니 보통 이렇게 rest api를 사용한 경우 로그인 처리가 끝나면 서버에서 메인페이지 화면으로 리다이렉트 시킨다고 한다.
그런데 토큰을 프론트로 보내야하는데 리다이렉트를 해버리면 토큰 전달을 할 수가 없는 문제가 발생한다.
리다이렉트에도 토큰을 보내려고 많이 찾아보았는데,
RedirectAttribute의 addAttribute 경우는 쿼리스트링으로 담아서 보내지는 게 되는데 jwt 토큰은 탈취 당하면 방법이 없기 때문에 노출은 보안상 택하지 않았고,
addFlashAttribute의 경우는 세션을 사용하여 1회용으로 저장하기 때문에 url 노출은 없으나 Vue에서 이를 받을 수 있는 방법을 찾을 수 없닸다. 아마도 이렇게 세션을 이용해서 보낸다는 것은 화면도 서버에서 리턴하는 JSP환경에서 사용 가능한 것일 것 같다.
애초에 Redirect 자체가 get 방식이기 때문에 노출없이는 힘들 것 같다.
고민 끝에 카카오 데브톡에 자바스크립트 sdk 방식과 rest api 방식을 섞어도 되는지 문의를 해보았는데,
"사용방식은 서비스하고자 하시는 방식에 따라 맞는 방식으로 선택하셔서 사용하시면 됩니다.
저는 개인적으로 웹서비스에서는 둘 다 사용하는 것을 선호 하는데요
프론트에서는 카카오톡 앱의 설치 유무에 따라 앱 또는 웹으로 알아서 로그인 시켜줬으면 해서 JavaScript SDK의 Kakao.Auth.authorize를 사용하고 있습니다."
이제 2번안의 sdk와 restApi 방식을 같이 써도 되는가에 대한 의구심이 사라졌기 때문에 자바스크립트 SDK를 사용하여 프론트에서 처리를 해보았다.
자바스크립트 sdk의 Kakao.Auth.login 메서드 하나만 사용하면 한방에 팝업창 띄우기, 인가코드, 엑세스 토큰 받기까지 전부 받아온다.
이제 엑세스 토큰을 서버에 요청 보낸 후 서버에서는 WebClient로 토큰을 첨부하여 사용자 정보를 받아온다.
DB 조회 후 가입되어있으면 Jwt 토큰 발급, 가입 안되어있으면 가입 처리 후 Jwt 토큰 발급해준다.
그럼 프론트에서 최초의 요청에 대한 응답으로 토큰을 받을 수 있다 :)