블로그를 정리하던 중 과거의 아카이브된 글에서 해당 글을 발견하였다. 그래서 이번에 구현 방식까지 한번에 정리해두려고 한다.
왜 세션/쿠키 방식으로 구현을 하지 않았는지에 대해 의문을 가질 수도 있다.
기존에 이미 JWT로 사용자 인증이 구현되어있는 상황에서 인증 방식의 수정없이 유저의 동시 접속을 막는 것이 요구사항이라 JWT 인증 기반 위에 웹소켓으로 동시 접속 방지를 구현하게되었다.
동시 접속 방지 로직 플로우은 아래와 같다.
유저가 로그인할 때, 유저 PK를 기반으로 Connection을 열고, 액세스 토큰을 Connection에 emit한다.
해당 유저가 로그아웃 할때까지 Connection을 유지한다.
다른 클라이언트(시크릿 탭 포함)에서 해당 유저로 로그인 시
기존 클라이언트로 'logout' 메세지를 보내고, 기존 Connection을 끊는다.
새로 접속된 클라이언트로 1번 단계를 진행시킨다.
서버쪽에서는 중복 접속 인증 용도의 GateWay를 열어두고, 프론트에서는 useEffect훅으로 socket 연결/해제 및 관련 팝업 처리를 진행하였다.
NestJs에서는 기본적으로 웹소켓 Gateway를 위한 WebSocketGateway라는 데코레이터를 @nestjs/websockets 패키지에서 지원한다. WebSocketGateway 데코레이터의 namespace 옵션에 값을 지정하여 Gateway 엔드포인트 url을 지정할 수 있다. SubscribeMessage 데코레이터는 파라미터로 받는 String 값을 웹소켓에서 구독하는 메세지명으로 할당한다.
로그인 / 로그아웃시 사용하는 AuthGateway는 2가지 SubscribeMessage를 가지고 있다.
'login'의 경우, handleLogin 메소드 파라미터로 Socket 타입 client와 json 타입 data를 받는데 data의 키는 userId, accessToken 2가지를 받는다. 웹소켓을 통해 'login' 메세지가 넘어오면 받은 userId로 소켓에 채널을 생성하고 해당 채널로 accessToken을 전송한다.
'logout'의 경우에도 handleLogin과 마찬가지로 Socket 타입인 client와 json 타입 data를 받는데 userId만을 받는다. 웹소켓을 통해 'logout' 메세지가 넘어오면 data에 담겨있는 userId로 생성된 채널을 웹소켓에서 삭제한다.
프론트엔드쪽 로직을 살펴보면 Login 버튼 onClick 이벤트에 서버의 사용자 로그인 api를 호출하여 리턴된 accessToken과 인증 토큰을 cookie에 저장하는 함수를 걸어두고, React Router단에서 useEffect 훅을 통해 만약 쿠키에 accessToken 값이 있다면 페이지가 이동할 때마다 지정된 웹소켓 GateWay 주소로 에 login 메세지와 현재 사용자의 Id, accessToken을 보내도록 구현하였다.
만약 login 메세지를 통해 프론트로 다시 전송받은 accessToken과 cookie에 담겨있는 accessToken 값이 다르다면, 소켓 연결을 초기화하고, 모달창을 띄워 '다른 클라이언트에서 로그인이 감지되었습니다.'와 같은 안내후, 확인 버튼 클릭시 쿠키에 담겨있는 인증 토큰과 accessToken을 clear하고, 메인페이지로 이동시키게 중복 로그인 방지를 구현하였다.
[NestJS] 동시 접속 방지 구현
블로그를 정리하던 중 과거의 아카이브된 글에서 해당 글을 발견하였다. 그래서 이번에 구현 방식까지 한번에 정리해두려고 한다.
기존에 이미 JWT로 사용자 인증이 구현되어있는 상황에서 인증 방식의 수정없이 유저의 동시 접속을 막는 것이 요구사항이라 JWT 인증 기반 위에 웹소켓으로 동시 접속 방지를 구현하게되었다.
동시 접속 방지 로직 플로우은 아래와 같다.
서버쪽에서는 중복 접속 인증 용도의 GateWay를 열어두고, 프론트에서는 useEffect훅으로 socket 연결/해제 및 관련 팝업 처리를 진행하였다.
NestJs에서는 기본적으로 웹소켓 Gateway를 위한 WebSocketGateway라는 데코레이터를 @nestjs/websockets 패키지에서 지원한다. WebSocketGateway 데코레이터의 namespace 옵션에 값을 지정하여 Gateway 엔드포인트 url을 지정할 수 있다. SubscribeMessage 데코레이터는 파라미터로 받는 String 값을 웹소켓에서 구독하는 메세지명으로 할당한다.
로그인 / 로그아웃시 사용하는 AuthGateway는 2가지 SubscribeMessage를 가지고 있다.
'login'의 경우, handleLogin 메소드 파라미터로 Socket 타입 client와 json 타입 data를 받는데 data의 키는 userId, accessToken 2가지를 받는다. 웹소켓을 통해 'login' 메세지가 넘어오면 받은 userId로 소켓에 채널을 생성하고 해당 채널로 accessToken을 전송한다.
'logout'의 경우에도 handleLogin과 마찬가지로 Socket 타입인 client와 json 타입 data를 받는데 userId만을 받는다. 웹소켓을 통해 'logout' 메세지가 넘어오면 data에 담겨있는 userId로 생성된 채널을 웹소켓에서 삭제한다.
프론트엔드쪽 로직을 살펴보면 Login 버튼 onClick 이벤트에 서버의 사용자 로그인 api를 호출하여 리턴된 accessToken과 인증 토큰을 cookie에 저장하는 함수를 걸어두고, React Router단에서 useEffect 훅을 통해 만약 쿠키에 accessToken 값이 있다면 페이지가 이동할 때마다 지정된 웹소켓 GateWay 주소로 에 login 메세지와 현재 사용자의 Id, accessToken을 보내도록 구현하였다.
만약 login 메세지를 통해 프론트로 다시 전송받은 accessToken과 cookie에 담겨있는 accessToken 값이 다르다면, 소켓 연결을 초기화하고, 모달창을 띄워 '다른 클라이언트에서 로그인이 감지되었습니다.'와 같은 안내후, 확인 버튼 클릭시 쿠키에 담겨있는 인증 토큰과 accessToken을 clear하고, 메인페이지로 이동시키게 중복 로그인 방지를 구현하였다.
'개발 > NestJS' 카테고리의 다른 글