교육/42Seoul

[실시간 채팅, 게임 서비스 ]transcendence - front와 user 식별자 공유하기 - userName은 안돼!

ebang 2023. 10. 18. 23:00
반응형

front에서 친구 목록과 함께 online/offline 상태를 보여주는 로직이 있었다.
친구가 소켓과 연결되는 순간 Back에서도 감지할 수 있기 때문에, 친구의 상태가 실시간으로 구현될 수 있었다.
프론트는 보여주는 게 userName밖에 없고, 친구 탭을 클릭하면 userName에 대해서 요청이 날라온다.
이럴 경우 문제점은, 친구가 자신의 userName을 변경한 경우, 다른 친구들은 업데이트된 목록을 갖고 있지 못하고, 예전 userName에 대해서 Back에 요청할 수 있다는 점이다. 이럴 경우 404 에러가 뜨게 된다.
 
문제 사황을 그림으로 표현하면 다음과 같다. 
 

이러한 경우가 친구 목록. 받은 친구 요청 목록, 내가 차단한 유저 목록 이 3개에 모두 해당한다.
이를 위한 해결책으로 다음과 같은 해결책들을 생각해보았다.
 
 

1. 프론트가 userId, userName 테이블(user table)을 가지고 있게 하자.

userName에 변동이 생겼을 때 해당 user table에서 업데이트만 해둔다면, 웬만한 목록들은 back에서 userId로 정보를 받아오되, 렌더링 전에 id에 해당하는 userName을 렌더링 해주는 방식이다.

  • 프론트에게 이 테이블을 갖고 있게 하는건 보안상 위험하다고 생각해서 패쓰했다.

2. Back에서 Front로 socket event로 업데이트 해주자.

userName 변경 시, 해당 유저를 친구로 갖고 있거나, 차단한 유저로 갖고 있거나, 해당 유저에게 친구 요청을 받은 유저리스트를 순회하면서 그 목록을 업데이트 해준다.
<친구이름 - 친구 상태> 를 보여주는 부분은 socket event 로 받고 있었기 떄문에, 해당 부분을 socket 이벤트로 다시 한번 보내주면 잘 업데이트 될 것이라고 생각했다.

  • 문제점:
    • userController - UserService 가 userName 변경에 대한 로직을 처리하기 위해 socketUserservice를 의존성 주입하고 나면, socketUserService에서도 userService를 의존성 주입하고 있기 때문에 순환 참조 에러가 발생하게 된다.
    • 차단 목록, 친구 요청 목록은 restapi 요청 후에 업데이트 되는 로직이기 때문에 실시간으로 다시 업데이트 해주는 것이 불가능했다.

3. UserName외에 front와 user식별을 위한 정보를 추가하자. 

  1. 이건 팀원의 의견이었는데, 처음에는 동의하지 않았다. slackId는 42Seoul의 유저라면 모든 사람들이 공유하는 의미가 큰 식별자인데 이걸 사용하는 것이 옳은가? 에 대한 의문이 있었다. 
  2. 그러나 다시 생각해보면, useId 를 사용한다고 해도 그 요청에 대한 백의 응답이 오는 것 또한 막을 수 없다. parameter에 들어가는 userId, 혹은 slackId를 이용해서 내가 가진 권한으로 할 수 있는 요청은 다 할 수 있는 건 어쩔 수 없는 것이다. 다만 개인적인 생각으로는 userId가 slackId 보다 나은 점은, 사용자가 실제로 요청을 보내보고 싶어서 시도해본다고 할 때, userId는 임의의 값으로 테스트하는 것이기 때문에 시도하는 사람에게 아무런 의미가 없지만, slackId는 실제로 오프라인으로 아는 유저이기 때문에 (인트라넷에서 조회가능) 의미가 클 수 있다는 점이다. 
  3. 아무튼, 차라리 해당 유저에 대한 인증과 인가를 강화하는 게 맞지, 당장의 식별자를 고친다고 해서 해결할 수 있는 문제가 아니라는 것을 깨달았다.
    그래서 이 방식으로 구현하기로 결정했다.

각 차단 목록 리스트, 친구 목록, 친구 요청 목록을 보내줄 때 slackId도 함께 보내주는 것으로 구현하기로 하였다. 
(하지만 userName과 slackId를 동시에 보여주고 slackId로 요청하는 건, 식별자의 의미가 맞지 않아서 이부분은 아쉽다. 프론트에서 보여주지 않으면 좋았을 것 같다. )
 
결과: 

 
추가적으로 알아본 사항은 다음과 같다.

  • uuid:
    • universally unique identifier, 소프트웨어 구축에 쓰이는 식별자 표준. 중복이 되지 않는 유일한 값을 구성하려고 할 때 주로 사용된다.
    • 라이브러리를 이용해서 쉽게 구현이 가능하다. 다만 프론트가 다른 유저의 uuid 테이블을 갖는 건 userId 테이블을 갖는 것과 같은 의미이므로 이걸 사용한다고 해도 문제가 해결되는 것은 없다.

 
서비스의 발생가능한 문제점에 대해 어디까지 생각해보아야 할까?
처음 프론트와 userName으로 통신하기로 결정한 날, userName 변경으로 인해 문제가 발생할 수도 있다는 생각을 안한 것은 아니다.
그 당시 문제 삼지 않았던 이유는 유저가 userName을 변경한다고 하더라도 그때는 로직상 socket 연결이 끊어진 상황일 것이므로 다시 연결할 땐 업데이트가 될 것이기 때문에 문제가 없을 것이라고 생각했다.
이때 한가지 놓쳤던 부분은 ‘이름을 바꾼 유저’가 아니라 ‘유저의 친구’, ‘유저를 차단해놓은 유저’, ‘유저에게 친구요청을 받은 유저’ 등등 다른 유저에 대한 고민이 부족했다는 것이다. 특히 Back에서는 이런 상황에 대비해 userId로 저장하는 로직으로 구현했음에도 불구하고 front가 userName에 대해 요청하면 의미가 없다는 걸 생각하지 못했다.
 
userId를 프론트가 어떻게 저장할 것이냐에 대한 논의가 있었는데 userId가 필요하다고 생각했으나 브라우저 노출은 안좋다고 생각했던 그 당시 Backend의 입장에서, ‘보안’만 신경쓰는 것이 아니라 데이터 정합성에 대한 고려도 충분히 했으면 좋았을 것 같다.
 
원래는 다 따져보는 성격인데 너무 깐깐해보일까봐 front는 편하게 userName으로 넘기세요~ Back이 userId로 관리하니까 문제 없어요~ 라고 생각한 부분이 있었는데, 이제는 더 맘 편히 깐깐해져보자! 아니 더 깐깐해져야 한다. 
 
이런 상황과 관련해서 좋은 글을 찾아서 읽게 되었다.
 
long 대신 Integer 을 쓰는 것도 고려하지 않을 때 실제로 문제가 생긴다는 것도 신기했고, 단순 오버프로우의 문제가 아니라 연계 시스템 전반적인 장애를 일으킬 수 있다는 점이 인상깊었다.
 

끊임없는 장애 대응 속에서 ‘휴먼 에러’ 막기, 입력에 대한 검증, 그리고 백에서 모든 계산과 검증을 할 것을 알리는 글인데, 상당히 와닿았다.
내가 너무 깊은 부분까지 고민하는 게 아니라, 그래도 가치있는 고민이었구나, 라고 깨닫게 해준 글이다.
 
보게 된 글

소감:
백을 전적으로 맡아서 한 프로젝트는 이번이 거의 처음이라 그런지 힘도 많이 들어갔던 것 같다. 힘은 빼고 시야는 넓혀서 보도록 노력해야겠다. 그리고 일처리에 관한 책도 읽어볼 것이다. (소프트 스킬에 대한 고민도 필요)
 

반응형