개발/Spring

[Spring] 최대 사용 기기 관리 구현

hojak99 2021. 4. 17. 19:06

앱에서 최대 3개의 기기에서 로그인을 하여 서비스를 이용할 수 있도록 해달라는 요구사항이 생겼을 때
어떻게 구현을 하면 좋을까 해서 검색을 좀 해보았는데 나오는 글이 없었다. 내가 못 찾은 걸지도 ㅎㅎ

자세한 요구사항은 작성하지 않지만 간략하게 이야기하면 다음과 같다.

 

요구사항

  • 서로 다른 기기라는 것을 구분해야 함
  • 웹, 앱 내에 마이페이지에서 등록된 기기를 개별 삭제, 전체 삭제를 할 수 있어야 함
  • 기기 별로 기기 이름과 마지막 접속 시간이 나타나야 함
  • 이미 3대의 기기가 등록 돼 있는 상태에서 추가로 기기가 등록된다면
    해당 기기에서는 사용자에게 등록된 기기를 삭제하고 로그인을 할 수 있거나 로그인을 하지 않고 앱을 나갈 수 있음. 

그렇다면 요청이 들어올 때마다 서버에서는 요청을 어느 기기에서 요청한 것인지 구분할 수 있어야 한다.
또한, 서버에서는 현재 JWT 를 이용하여 모든 요청을 검증하고 있기 때문에 토큰을 이용해서 처리를 해야 한다.

그렇다면 각각의 요구사항을 어떻게 충족시킬 건지 생각을 해보도록 한다.

 

요구사항 확인

  • 서로 다른 기기라는 것을 구분해야 함
    • 앱 개발자분에게 여쭤보니 device 별 고유한 uuid 가 존재한다고 한다. 해당 값을 이용하도록 한다.
  • 웹, 앱 내에 마이페이지에서 등록된 기기를 개별 삭제, 전체 삭제를 할 수 있어야 함
    • DB 내에 기기를 식별할 수 있도록 데이터를 쌓아야 한다. 삭제 시 요청 막아야 한다.
  • 기기 별로 기기 이름과 마지막 접속 시간이 나타나야 함
    • 앱 개발자분에게 여쭤보니 기기 이름을 줄 수 있다고 하심.
      마지막 접속 시간을 기록하는 것은 서버에서 유효한 요청 들어올 때마다 기록을 하거나, 앱 종료 시점에서 클라이언트에서 api 호출해서 업데이트 하는 식으로 할 수 있을 것 같다.
      우선 마지막 접속 시간을 기록하는 것이 정말 중요한 기능 및 데이터는 아니기 때문에 빡세게 검증할 필요는 없고 매 요청마다 update 쿼리를 날리는 것은 비효율적이므로 클라이언트에서 앱 이탈 시에 api 를 호출하는 것으로 정함.
  • 이미 3대의 기기가 등록 돼 있는 상태에서 ,,, (중략) ,,, 
    • 최대 3대의 기기가 등록 돼 있는 상태에서 로그인을 했을 때, 해당 사용자가 기기를 제거할 수 있어야 하므로 서버에서는 해당 사용자를 식별 할 수 있는 상태여야 한다.


 

내가 생각했던 부분

  1. 당연하게도 최대 3대의 기기에서만 서비스를 이용할 수 있기 때문에 모든 요청에 대해서 등록된 기기에서 온 요청인지 검사를 해야 한다.

  2. 매 요청마다 DB 에서 사용자에게 등록된 기기를 조회한다면 매우 낭비이기 때문에 어딘가에 캐싱 해 놓아야 한다. 
  3. 사용자 별로 토큰을 최대 3개만 등록 돼 있지는 않을 것이다. 
    최대 3대 이상 등록 돼 있는 경우에서 새로운 기기에서 접속 시 서버에서 식별 할 수 있도록 임시 토큰을 지급할 것이다.

 

구현

위에 생각했던 것들을 이용해서 구현을 해 보도록 한다.
자세한 구현 사항은 작성하지 않고 간략적으로 표현 해 보도록 하겠다.

먼저 redis 를 이용한다.
(서버에서 토큰을 이용하고 있는데 토큰을 이용하는 목적 중 하나인 stateless 의 장점이 사라지지만 앱, 웹에서 자동로그인을 구현하기 위해 사용했고, 개인적으로 이용해보고 싶었기도 했다.

전체적인 플로우는 다음과 같다.  (토큰 유효성은 아래 내용에서 제외하고 설명하도록 한다.)

  1. redis 에는 다음과 같이 저장하도록 한다.
    key: 사용자ID 로 단순 string
    value: 토큰, 기기 uuid, 생성, 만료시간을 등록된 기기별로 list 로 이루어져 있다.

  2. 로그인 시마다 redis 에 사용자 ID 에 대한 value 값을 가져와 해당 조건으로 데이터가 총 3개 이상인지 체크 한다.
    - 정상 token 으로 이루어져 있으며.
    - 만료 시간이 남아 있는.

  3. 3개 미만이라면 DB 에 기기이름, 기기 uuid 를 insert
    redis 에 사용자 ID 를 키로 가지고 있는 list 에 위 해당 데이터와 token, 만료시간을 저장. 

  4. 3개 이상이라면 DB 에 저장하지 않음
    대신 redis 에 사용자 ID 를 키로 가지고 있는 list 에 기기 uuid, token, 만료시간을 저장. 이 때 임시 토큰이다. 

  5. 로그인, 로그아웃 등과 같은 요청을 제외한 나머지 요청에서
    기기 uuid, token 을 가지고 redis 에 존재하는지 검증한다. 존재하지 않으면 유효하지 않은 요청이라고 판단.
    또한, 슬라이딩 세션 전략으로 특정 만료 시간 전에 토큰을 교체 해 주도록 한다.
    그러나 여러 요청이 동시에 들어오는 상황에서 토큰이 특정 만료시간 전이라면 서버에서 토큰을 교체를 하는데,,, 기존 토큰을 들고오는 요청들이 막히는 것을 고려하여 새로운 토큰은 redis 에 업데이트가 아닌 새로 넣어놓고,, 기존 데이터의 만료시간을 현재시간+N 으로 설정하여 동시에 들어오는 요청에 대해 고려해준다.

  6. 기기 삭제 시 redis 및 DB 에서 제거해준다.

  7. 새로운 기기 접속 시 기기 삭제 후 redis 에 등록된 임시 토큰을 정식 토큰으로 변경해주며, DB 에도 새로운 기기 데이터를 넣어 놓는다.

 

나는 이런 요구사항을 어떻게 구현할지에 대해서 모두 draw.io 에서 플로우차트를 이용해서 설계를 하고 그대로 코드로 옮기는 식으로 개발을 하고 있다.

여기에도 해당 플로우차트를 올리면 더 쉽게 설명을 할 수 있을 것 같은데 공개하기 좀 그래서 저렇게 간략하게 표현을 했다.

글로 표현하니 생략된 부분이 많아졌는데 그래도 누군가가 구현할 때 도움이 될 듯 하여 남겨본다.

추가로 나는 토큰의 만료시간과,,, redis 의 만료시간을 하나의 만료시간이라고 생각하지 않고 구분을 해주었다.
토큰의 단점 중 하나로 만료 시간이 남아있고 유효한 토큰이라면 서버에서 redis 든 db 든 체크하지 않으면 모두 유효한 요청이라고 판단한다.

그런데 redis 에 저장해놓을 때 key 가 아닌 value 에는 redis 만료시간을 지정하지 못하기 때문에 value 데이터 json 내부에 따로 만료시간을 넣어주었다.
그래서 토큰에 대한 만료시간이 남아있고, 토큰이 유효하더라도 redis 에 없으면 유효하지 않은 요청이라고 판단 하도록 했다.
기기 삭제 및 토큰 변경이 일어나더라도 redis 에 반영을 해주니 기존 토큰은 유효하나,,, 서버에서는 유효하지 않은 토큰이라고 판단하도록,

반응형