Skip to content

채팅 서버 ‘익명’ 기능 유지하기

Hoeeeeeh edited this page Dec 5, 2024 · 1 revision

채팅 서버와 익명

채팅 서버를 비롯한 대부분의 liboo 서비스는 익명으로 사용할 수 있도록 지원하고 있다.

그렇지만 채팅 서버에서는 유저를 식별할 수 있어야 어떤 유저가 어떤 채팅을 보냈는지를 화면에 띄울 수 있다.

따라서 유저마다 userId 를 하나씩 발급해서 localStorage 에 저장하고 이를 활용해서 어떤 유저가 서버에 채팅을 보냈는지를 식별하고 있다.

그렇다면 userId 를 굳이 localStorage 에 저장해야할 이유가 있었을까?

userId 는 로그인이 없는 liboo 에서 유저를 식별할 수 있는, 유일한 기준이었다.

WebSocket 의 socketid 로 유저를 식별하게 되면, socketid는 탭마다 달라지기 때문에 사용자가 탭을 열면 다른 사용자가 되어버린다.

따라서 다른 탭에서도 userId 가 유지되어야 했고 탭끼리 데이터를 공유할 수 있는 방법 중에 가장 간단한 localStorage 를 활용했던 것이다.

하지만 localStorage 에 저장되는 userId 는 언제든지 탈취가 가능하고, 언제든지 악의적으로 수정해서 서버와 통신할 수 있다.

따라서 여러 탭에서 하나의 채팅방에 입장해도 같은 유저임을 판별할 수는 있겠지만, 반대로 다른 사람의 userId 만 탈취하면 그 사람을 사칭할 수도 있다.

다른 탭마다 다른 소켓 id 를 가지기 때문에 localStorage 에 공유되는 데이터인 userId 를 넣었더니 이번엔 보안적인 측면에서 문제가 발생한다.

그래서 현재는 아래와 같은 방식을 택했는데,

여러 탭에서 같은 유저로 채팅하는 것을 애초부터 막는다.

추가로, 서버에서 userid , socketid 를 key-value 로 하는 데이터 저장

클라이언트는 서버에 userid 를 처음 보냈을 때, 서버 측에서는 userid 에 해당하는 socketid 를 따로 저장해두고 다음번부터 socketid 와 userid 에 대한 비교를 계속 하면 된다.

그리고 만약 클라이언트와 disconnected 가 된다면, userid 에 해당하는 값을 삭제하면 된다.

우리는 현재 이 방법을 통해서 userid 를 위조하더라도 socketid 와의 비교 과정을 통해서 유저의 유효성을 판별하고 있었는데 문제가 하나 있다.

바로, 연결이 끊겼음에도 disconnected 가 되지 않는 경우도 있다 라는 것이다.

클라이언트 페이지에서는 유저가 탭을 나가거나 하는 등의 이벤트를 감지해서 서버로 disconnected 이벤트를 발행할텐데, 너무 빠르게 나갔다가 들어갔다 등을 반복해서 그런지 서버에 disconnected 가 정상적으로 들어오지 않는 경우가 있었다.

실제로 socket 이 닫혔는데도 userid - socketid 에 대한 데이터가 삭제되지 않고 남아있어서 추가적으로 클라이언트-서버간의 heartbeat 체크가 필요해진다. 만약 heartbeat 체크가 없으면 좀비 userid-socketid 데이터가 살아있기 때문에 해당 userid 는 앞으로 영영 채팅에 참가할 수가 없다. (socketid 는 매번 달라지므로 서버에 있는 좀비 socketid 가 살아있어서)

게다가 이 방식대로라면 모든 탭마다 소켓을 만들기도 만들어야하고, userid - socketid 데이터를 저장해야한다.

추가로 userId 가 탈취된 상태라면, 탈취범이 진짜 유저보다 먼저 다른 방에 접속하면 탈취범이 진짜 유저가 되어버린다.

그래서 어떻게 하면 좋을까..

일단 모든 탭에서 공유하는 userid 를 object storage 에 넣은 순간부터 문제가 된다. 보안적인 측면에서 이슈가 생기지 않을 수가 없다.

image

그래서 근본적인 원인인 userid 를 삭제하는 방법을 찾아보려고 한다.

userId 는 다른 탭마다 가지는 다른 socketid 때문에, 공통의 Id 를 위해서 만들어진 식별자이다. 그래서,

탭을 무조건 하나로 고정한다 → socketid 가 무조건 하나다 → userId 를 쓸 필요가 없다.

이런 방법이 하나가 있고,

탭은 여러개로 열 수 있지만 → socketid 를 어떻게든 하나만 생성되도록 한다 → userId 를 쓸 필요가 없다.

이런 방법이 있다.

1번이 당연히 쉽고 빠르다. 대신 사용자가 만약 탭을 2개 열고, 같은 방에 접속한다면 채팅이 하나만 되고 하나는 되지 않을 것이다.

2번 방법은 당연히 ux 측면에서 좋아진다. 하지만 socketid 를 어떻게든 하나만 생성되도록 한다 의 방법을 찾아야한다.

기본적으로 브라우저는 탭마다 독립적인 환경, 즉 다른 프로세스에서 실행되기 때문에 프로세스가 다같이 사용할 수 있는 무언가가 필요하다..

https://www.youtube.com/watch?v=SVt1-Opp3Wo

마침 토스에서 다중 탭, 단일 소켓의 해결 방안을 발표해서 이것을 참고하면 될 것 같다!

클라이언트 측에서 애초부터 단일 socketid 로 통신을 시도한다면 서버에서는 userid 고 뭐고 socketid 만을 가지고 단일 유저로 판단할 수 있다.

프론트 → 서버

서버 → 프론트 isMe : true false

프론트 소켓 1개로 [socket.id](http://socket.id) 계속 동일할거고 → 서버

LiBoo

공통

민지

영길

준서

지수

창현

데일리 스크럼

회의록

발표

일기장

Clone this wiki locally