실시간 웹 애플리케이션을 개발하다 보면, Socket.IO 연결에서 JWT 토큰 관리가 까다로운 경우가 많습니다.
특히 토큰이 만료되었을 때 기존 연결을 어떻게 처리할지, 새로운 연결 시도를 어떻게 막을지 고민이 되죠.
핸드셰이크 단계에서부터 JWT를 검증해서 유효하지 않은 토큰으로는 아예 연결 자체가 성립되지 않도록 하는 방법을 공유합니다.
운영과 개발 서버에 이 방식을 적용한 경험을 바탕으로 작성했습니다.
해결책: io.use()로 핸드셰이크 단계에서 차단
Socket.IO의 io.use()는 네임스페이스 미들웨어로, 모든 소켓 연결 시도마다 가장 먼저 실행됩니다. 여기서 토큰 검증을 하고, 실패하면 next(new Error(...))로 연결 자체를 거부하는 것이 핵심입니다.
이렇게 하면:
- 연결이 성립하기도 전에 컷!!
- connection 리스너가 아예 실행되지 않음
- 서버 리소스 점유, 채널 조인, 방 진입 등의 후속 로직이 전혀 실행되지 않음
- 실패 사유는 클라이언트의 connect_error 이벤트로 전달
import axios from 'axios';
import { Server } from 'socket.io';
const io = new Server(httpServer, {
// CORS, path 등 환경에 맞게 설정
});
io.use(async (socket, next) => {
// ⚠️ 토큰은 query가 아닌 auth로 받는 것이 안전하고 표준적입니다
const { user_id, tenant_code, token } = socket.handshake.auth || {};
// 디버깅용 메타데이터 저장
socket.data.meta = { user_id, tenant_code };
if (!token || !user_id || !tenant_code) {
return next(new Error('Missing credentials'));
}
try {
const res = await axios.post(
`${process.env.API_HOST}/v3/token/validation`,
{ user_id, tenant_code, token },
{ timeout: 3000 } // 타임아웃 필수!
);
if (res.status !== 200) {
return next(new Error('Token validation failed'));
}
// 검증 통과
return next();
} catch (err) {
// 외부 API 오류나 타임아웃도 "연결 거부"로 일관 처리
return next(new Error('Auth service unreachable'));
}
});
// 여기까지 도달했다면 "검증을 통과한 연결"만 들어옵니다
io.on('connection', (socket) => {
const { user_id, tenant_code } = socket.data.meta || {};
console.info('[Socket] Connected:', { user_id, tenant_code, id: socket.id });
// 이후 비즈니스 로직...
});
중요한 포인트들
- next(): 검증 통과, 연결 허용
- next(new Error('...')): 즉시 거부, connection 이벤트 실행 안됨
- throw하지 말고 return next(err)로 명확히 종료
- 인증 실패와 검증 서비스 장애를 같은 인터페이스로 클라이언트에 전달하면, 클라이언트에서 재시도/로그인 갱신 로직을 일관되게 처리할 수 있습니다
'TIL' 카테고리의 다른 글
| Nginx 설정 팁 (0) | 2025.09.11 |
|---|---|
| Socket Admin UI 페이지 보는법 (0) | 2025.09.11 |
| Docker 로그와 PM2, Logger, 색상 출력 – 실무에서 겪은 이야기 (3) | 2025.08.15 |
| postgres DB 이원화 (3) | 2025.08.10 |
| Ingress Controller rewrite 안될때 (2) | 2025.07.30 |