이슈
SWM
과정에서 서비스를 개발 중 소셜 로그인 구현이 필요했다. 해당 프로젝트는 모바일 앱 서비스로 flutter
로 작성된 프론트 엔드와 스프링 부트
로 작성된 백엔드 서버를 두고 있었다. 본 프로젝트에서는 네이버와 카카오 소셜 로그인을 지원하기로 했고, 구글링을 통해 자료를 찾아본 결과 네이버의 경우에는 개인 개발자가 제작한 네아로 공식 SDK 기반의 소셜 로그인 라이브러리가 있었고, 카카오의 경우에는 아예 공식적으로 개발된 Flutter용 카카오 API 연동 라이브러리를 지원하고 있었다. 그런데 이렇게 SDK
를 사용할 경우 네이버나 카카오와 같은 플랫폼에서 제공해주는 API KEY를 프론트 단에서 관리한다는 점과, 회원 정보를 불러오는 API
요청 또한 프론트에서 수행되므로 자체 백엔드 서버 중심의 회원 관리가 이루어지지 않았다.
배경 지식
세션 로그인 vs 토큰 로그인
로그인을 통한 인증에는 그 방식에 따라 크게 2가지가 있는데, 세션 로그인과 토큰 로그인이 그것이다. 둘의 작동 방식은 다음과 같다.
세션 로그인
사용자가 클라이언트(브라우저)에서 서버로 로그인 요청을 하면 서버에서 세션을 생성하고 세션 ID를 클라이언트로 보낸 후 클라이언트는 쿠키를 통해 세션을 유지하는 방식이다. 하지만 네이티브 앱
에는 세션이 없기 때문에 이와 같은 방식의 로그인을 구현할 수 없다. 만약 하이브리드 앱
이라면 방법이 아주 없는 것만은 아니다. 웹뷰
를 사용해서 100% 웹 앱의 형태를 지녔다면 웹뷰의 세션을 통해 이러한 로그인 방식을 구현할 수 있다. 하지만 세션은 IP 단위로 유지되기 때문에 와이파이 사용 등 IP 변경이 잦은 모바일 기기에서는 세션이 자주 풀리고, 웹뷰 내부에는 세션이 있더라도 REST API 호출 시에는 그 세션 정보가 없다. 따라서 네이티브
와 하이브리드
상관없이 모바일에서는 세션 로그인
이 권장되지 않거나 불가능하다. 그렇다면 토큰 로그인
방식을 살펴보자.
토큰 로그인
토큰 로그인
의 경우에는 방식이 조금 더 간단하다. 클라이언트에서 서버로 로그인 요청을 하면 회원 정보를 확인한 후 AccessToken
을 발급한다. 추후 클라이언트는 서버와 통신 시에 이 토큰을 같이 담아 요청하기만 하면 된다. 하지만 이와 같은 방식에는 보안 이슈가 있는데, 바로 AccessToken
을 탈취당하면 대처법이 없다는 것이다. 이를 해결한 방법으로는 AccessToken
에 만료 기간을 두고 RefreshToken
을 통해 이를 지속적으로 갱신해주는 것이 있다.
토큰 로그인 with Refresh Token
이러한 방식을 사용하게 되면 AccessToken
을 탈취 당하더라도, 기간이 만료됨에 따라 재사용이 불가능하고 서버에서 사용자 인증을 통해 발급받은 RefreshToken
을 통해서만 갱신할 수 있기 때문에 안전하다. 물론 이 RefreshToken
또한 지속적으로 갱신해서 클라이언트에게 발급한다. 하지만 클라이언트와 서버 쪽 모두 개발 과정이 더 복잡해진다는 점이 있다.
이와 같은 토큰 로그인
방식은 Web
이나 네이티브 앱
상관없이 모두 REST API
를 통해 이용할 수 있는 방법이다. 따라서 모바일 기기에서의 로그인은 토큰 로그인 방식을 사용하며, 현재 Web
에서도 토크 로그인이 세션 로그인에 비해 갖는 특징 때문에 토큰 로그인을 사용하는 추세라고 한다.
두 가지 로그인 방식에 대한 더 자세한 차이점은 여기에서 확인할 수 있다.
OAuth2.0
OAuth는 외부서비스의 인증 및 권한부여를 관리하는 범용적인 프로토콜이며, 현재는 2.0 버전이 사용되고 있다. 그리고 거의 대부분의 소셜 로그인은 모두 OAuth2.0 방식을 사용하고 있다. 그리고 이는 내부적으로 앞서 설명한 AccessToken
과 RefreshToken
을 이용한 토큰 인증 방식에 기반한다.
OAuth 시퀀스 다이어그램
위 사진에서는 RefreshToken
이 빠져있는데, AccessToken
발급 시에 같이 발급된다고 보면 된다. 보다시피 OAuth2.0
방식의 구성 요소에는 두 가지 서버가 있다.
- 인증 서버(Authorization Server)
- Client로부터 로그인 요청을 받는 주체
- Client로 로그인 페이지 제공
- Client로 Authorization Code(인증 코드) 발급
- Client로 인증 코드를 통해 AccessToken 및 RefreshToken 발급
- 리소스 서버(Resource Server)
- Client로부터 API 요청을 받는 주체
- Client의 AccessToken 검증을 통해 API 요청 승인
- Client로 API 서비스 제공
여기서 인증 서버와 리소스 서버는 소셜 로그인에 이용하려는 플랫폼에 따라 달라지게 된다. 예를 들어 구글 로그인을 구현한다면 구글에서 제공하는 인증 서버와 리소스 서버에 요청하게 된다. 물론 개발자 입장에서 방식은 모두 동일하므로 플랫폼에 따라 개발 방식이 거의 유사하다.
OAuth에 대한 더 자세한 정보는 여기에서 참고할 수 있다.
Native SDK vs REST API
다시 글 맨 앞에서 언급했던 이슈로 돌아와보자. 만약 Native SDK
사용하면 어떻게 될까? 이 방식을 사용하면 위에서 제시했던 OAuth2.0 시퀀스 다이어그램에서 서비스 Client
가 네이티브 앱
자체가 되버린다. 즉, 로그인 과정과 회원 관리에서 우리가 자체 개발한 백엔드 서버가 낄 자리가 없어지고, 회원을 인증하고 정보를 가져오는 주체가 백엔드가 아닌 프론트엔드가 되버린다. 그래서 이러한 경우에는 OAuth2.0
인증을 백엔드에서 처리하고 유저 정보를 DB
에서 따로 관리해야 한다. 필자가 했던 프로젝트에서는 다음과 같은 방식으로 해결하였다.
서버 중심의 소셜 로그인
백엔드
에서 프론트
로부터 받을 로그인 요청 API
를 따로 개발하고, 프론트에서 요청이 들어오면 백엔드에서 소셜 로그인
을 진행한다. 그 다음 백엔드에서 리소스 서버로부터 유저의 정보를 받아오고, 이를 DB
에 저장 및 갱신한다. 이후에 이 유저 정보로 서버에서 자체적인 JWT
를 생성하고 (여기서는 AccessToken
과 RefreshToken
) 이를 프론트에 발급한다.
이와 같은 방식이 Native SDK
에 비해 갖는 차이점은 다음과 같다.
- 프론트에서 백엔드로 로그인 API 요청만 보내면 백엔드 서버에서
OAuth2.0
을 통한 소셜 로그인 및 회원 관리를 수행하고 백엔드에서 자체적으로 발급한토큰
을 프론트가 전달받는다. Flutter
앱 자체에서 로그인을 하지 않고 모바일 웹에서 로그인한 후 결과를Redirect URI
를 통해 앱으로 리턴받는다.
한마디로, 서버 입장에서 클라이언트를 앱이 아닌 웹으로 판단하겠다는 것이다. 따라서 소셜 로그인을 구현하려는 플랫폼에서의 개발자 설정에서도 서비스 플랫폼을 Android
나 iOS
가 아닌 Mobile Web
으로 설정해주어야 한다.
Redirect URI
그렇다면 이렇게 모바일 웹 브라우저에서 수행한 로그인 결과를 어떻게 앱으로 전달할 수 있을까? 이 때 바로 Redirect URI
가 사용된다.
Redirect URI
란 말 그대로 앱이 갖는 고유한 주소이며, 웹페이지에서 특정 앱으로 데이터를 redirect
하고자 할 때 사용된다.
따라서 모바일 앱 서비스 개발 중 REST API
방식을 통해 OAuth
소셜 로그인을 구현한다면 Redirect URI
는 필수적인 존재이다.
모바일 웹에서 로그인을 진행하므로 로그인에 대한 결괏값도 모바일 웹을 통해 전달되기 때문이다. 따라서 서버로부터 최종적인 AccessToken
과 RefreshToken
을 이 Redirect URI
를 통해 전달받게 된다.
그렇다면 flutter
에서 이를 어떻게 구현할까? 다행히 이것을 도와주는 간편한 라이브러리가 존재한다.
flutter web auth
본 라이브러리를 이용하면 손쉽게 백엔드 서버로부터 받은 로그인 페이지로 로그인을 수행하고, 이를 통해 발급받은 token
을 redirect URI
통해 받아올 수 있다. Redirect URI
는 고유한 값을 가지는 것이 안전하기 때문에 보통 앱의 패키지명
으로 지정한다. 간단한 코드는 아래와 같다.
1 |
|
결국 프론트 상에서는 위 코드만으로 소셜 로그인이 끝난다.
나머지는 앞서 제시한 서버 중심의 소셜 로그인 시퀀스 다이어그램처럼 백엔드
에서 개발한 로그인 API
에 달렸다.
따라서 SDK
를 사용할 때와는 달리 프론트
에서 소셜 플랫폼 API KEY를 관리하거나, 플랫폼 리소스 서버에 직접 API
요청을 보내는 일이 없기 때문에 프론트가 완전히 소셜 로그인과 분리된다.
이를 통해 프론트
에서는 플랫폼에 상관없이 동일한 과정으로 소셜 로그인을 요청할 수 있고 백엔드
에서는 프론트와 독립적으로 로그인 과정과 회원 정보를 관리할 수 있다.