-
Notifications
You must be signed in to change notification settings - Fork 7
내편 OAuth 적용기
3차 스프린트 구현사항으로 내편의 기존 로그인을 없애고 카카오 인증을 통한 로그인으로 변경하기로 했다. 그래서 OAuth를 도입했다. 이후에 다른 플랫폼의 인증수단도 추가하며 점점 사용자 친화적인 서비스로 발전하려 한다.
OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의
정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인
수단으로서 사용되는, 접근 위임을 위한 개방형 표준이다.
- 위키백과
풀어서 설명하자면 어떤 플랫폼에서 인증을 부여하기 위해 사용하는 Http 통신상에서 동작하는 프로토콜이다.
우리 서비스에 접근한 유저가 특정 다른 플랫폼의 유저인지 확인하려면 어떻게 하면 될까? 1차원 적으로 유저에게 그 플랫폼 id와 password를 받아 직접 로그인이 되는지 확인해보면 된다. 하지만 유저가 본인의 id와 password를 우리 서비스에 제공하는 것을 꺼려하지 않을까? id와 password를 알고 있으면 그 플랫폼에 대해서 그 유저와 완전히 동일한 권한을 가지게 된다. 또한 만약 password가 변경된다면 그걸 기반으로 유저의 인증을 이용하던 애플리케이션은 정상적으로 동작하지 못할 수도 있다.
뭐 이래저래 id와 password를 공유하는 방법은 여러 모로 불편함이 많아보인다. OAuth 2.0은 유저가 인증을 대신하는 플랫폼에 직접 로그인 정보를 입력하여 인증절차를 거치고 인증 결과를 서비스에 제공하여 서비스에서 권한을 가지는 토큰을 받을 수 있게 하는 방식으로 동작하여 위의 불편함이 해소되었다.
OAuth의 동작 플로우를 설명하려면 용어정리가 필요하다.
- 서비스를 이용하는 사용자를 Resource Owner
- 우리가 개발하려는 서비스를 Client
- Resource Owner의 인증 정보를 제공하는 플랫폼의 인증 서버를 Authorization Server
- 인증 정보를 제공하는 플랫폼에서 사용자의 인증 토큰으로 자원을 요청했을 때 자원을 제공하는 서버를 Resource Server
라고 칭하겠다.
- 일단 Client가 Resource Owner에 어플리케이션을 등록하고 client id와 client secret을 발급받는다. 이후 인가코드를 전달받을 Redirect Uri도 등록한다.
- Resource Owner에게 client id와 redirect Uri를 파라미터로 담은 해당 플랫폼의 로그인 화면을 보여주며 해당 플랫폼에 로그인시킨다.
- Authorization Server에서는 redirect Uri로 authorization code를 파라미터로 전달해서 리다이렉트한다.
- authorization code가 있으면 그제서야 사용자의 인증 정보를 담은 Access Token을 요청할 수 있다.
- 내편에서는 authorization code를 받는 부분까지는 FE에서 이후엔 사용하는 파라미터들을 BE로 보내서 BE에서 외부 플랫폼과 통신했다. 이유는 밑에 따로 정리.
- Resource Owner의 Access Token을 얻었다면 Resource Server에 Resource를 요청할 수 있다.
- 내편에서는 사용자의 닉네임, 프로필 사진, 이메일, 해당 플랫폼 식별자(id) 총 4가지 정보를 요청해서 사용한다.
처음엔 FE에서 사용자 정보를 요청하는 부분까지 전부 통신한 후에 BE에 사용자 정보를 API로 넘겨줘서 DB에 저장시켰었다. 하지만, 이 방법에는 문제점이 있었다.
이유는... 우리는 회원가입 API와 로그인 API를 합쳐놨었다. 왜냐면 사용자가 카카오나 네이버같은 서비스에서 로그인하고 해서 로그인 했을 때 사실 그건 회원가입 API였고 로그인 API다시 해야하니까 또 로그인하라고 하는 것이 너무 UX측면에서 별로라고 느꼈다. 그래서 다른 플랫폼에서 로그인하면 OAuth 과정을 거쳐서 이미 내편 DB에 있는 유저라면 그냥 내편 토큰반환, 아니라면 회원가입 후 내편 토큰 반환하도록 API를 하나만 만들었다.
자, 위 상황에서 만약 악의적인 목적으로 Postman같은 Http Client를 사용해서 사용자 정보를 넣는 로그인 API에 임의의 값을 넣고 로그인 요청을 한다면..? 타 플랫폼에서 인증되지도 않은 유저의 정보를 DB에 저장하고 로그인 토큰을 반환하게 된다. 이는 심각한 문제였다. 결국 DB에 저장을 하는 BE 서버에서 직접 타 플랫폼에 Access Token과 사용자 정보를 요청해야 신뢰할 수 있는 사용자 정보를 얻고 저장할 수 있다고 판단했다.
로그인에 대한 부분을 OAuth를 사용했기 때문에 테스트 코드 작성에 애를 먹었다. 특히 내편은 로그인하지 않으면 거의 모든 기능을 이용할 수 없기 때문에 거의 모든 인수테스트가 깨졌다. 로그인 부분을 어떻게 테스트 통과 시켜야 했을까?
결국 인수테스트에서 타 플랫폼에 Http요청을 해서 사용자 정보를 얻는 클래스를 Mocking하기로 했다. 마침 타 플랫폼에 요청을 담당하는 클래스가 있었고 Spring Bean으로 사용중이었다! 딱 이 클래스만 MockBean으로 사용하면 안정적으로 테스트 코드를 짤 수 있었다. 인수테스트에서 Mocking하는건 사실 별로 좋은 방법이 아니지만 외부 통신 API를 테스트에서 써야한다면 정상작동한다고 가정하고 Mocking하는게 테스트를 짜는 유일한 방법이었다.