728x90

HTTP 기반의 REST API와 달리, WebSocket은 초기 핸드셰이크(Handshake) 시에만 인증 정보를 확인하며, 이후에는 이 연결 상태를 지속적으로 유지해야 합니다. NestJS에서는 Guard와 Pipe를 사용하여 HTTP와 유사하게 인증을 처리할 수 있습니다.
1. 클라이언트 측에서 토큰 전달
클라이언트는 연결을 시도할 때 JWT 토큰을 Socket.io의 auth 옵션을 통해 서버로 전달합니다.
// 클라이언트 (예: Vue, React, Plain JS)
const token = 'YOUR_VALID_JWT_TOKEN';
const socket = io('http://localhost:8080/chat', {
auth: {
token: token, // 서버의 Gateway로 전달됨
}
});
728x90
2. Gateway에서 연결 시 인증 처리
NestJS Gateway의 handleConnection 메서드는 연결이 수립되기 직전에 호출되므로, 이 곳에서 토큰을 검증하고 사용자 정보를 소켓 객체에 저장하는 것이 베스트 프랙티스입니다.
// src/auth/ws-auth.guard.ts (인증 로직을 분리한 Guard 예시)
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';
import * as jwt from 'jsonwebtoken';
// 실제 프로젝트에서는 JwtService를 주입받아 사용해야 합니다.
@Injectable()
export class WsAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> {
const client: Socket = context.switchToWs().getClient<Socket>();
const token = client.handshake.auth.token;
if (!token) {
throw new WsException('인증 토큰이 누락되었습니다.');
}
try {
// 1. 토큰 검증 (실제 SECRET_KEY 사용)
const payload = jwt.verify(token, 'YOUR_SECRET_KEY');
// 2. 인증 성공 시 사용자 정보를 소켓 객체에 저장
// 이렇게 저장된 정보는 @SubscribeMessage 핸들러에서 사용 가능합니다.
client['user'] = payload;
return true;
} catch (e) {
// 3. 토큰이 유효하지 않으면 WsException 발생 -> 연결 거부
throw new WsException('유효하지 않거나 만료된 토큰입니다.');
}
}
}
728x90
3. 메시지 핸들러에서 사용자 정보 사용
handleConnection에서 인증을 완료하고 사용자 정보를 소켓(client['user'])에 저장했으므로, 이제 @SubscribeMessage 메서드에서 이 정보를 안전하게 사용할 수 있습니다.
// src/chat/chat.gateway.ts (Gateway 내부)
// ...
import { UseGuards } from '@nestjs/common';
import { WsAuthGuard } from './ws-auth.guard';
@WebSocketGateway(...)
export class AuthenticatedChatGateway implements OnGatewayConnection, ... {
// ... handleConnection은 WsAuthGuard의 canActivate가 대신 처리합니다.
// 1. Gateway 전체에 Guard 적용 (모든 메시지 이벤트에 인증 필요)
@UseGuards(WsAuthGuard)
handleConnection(client: Socket) {
// Guard가 true를 반환했으므로, 이미 인증된 상태입니다.
const user = client['user'];
this.logger.log(`인증된 사용자(${user.userId}) 연결: ${client.id}`);
}
// 2. 특정 메시지 이벤트에 Guard 적용 (선택적)
@UseGuards(WsAuthGuard)
@SubscribeMessage('sendMessage')
handleMessage(
@MessageBody() data: { message: string },
@ConnectedSocket() client: Socket,
) {
// 소켓 객체에서 저장된 사용자 정보 추출
const user = client['user'];
// 사용자 인증 정보를 기반으로 메시지 처리
this.logger.log(`[${user.username}] 메시지 수신: ${data.message}`);
// ... 메시지 브로드캐스팅 로직
}
}
🔑 요약 베스트 프랙티스
- 토큰 전달: 클라이언트는 반드시 socket.io의 auth 객체를 통해 JWT를 전달합니다.
- 인증 분리: 인증 로직은 Gateway 메서드 내부에 직접 작성하기보다, WsAuthGuard 클래스로 분리하여 재사용성과 유지보수성을 높입니다.
- 예외 처리: 인증 실패 시 일반적인 client.disconnect() 대신 WsException을 사용하여 클라이언트에게 명확한 오류 코드를 전달해야 합니다.
- 정보 저장: 인증에 성공하면, 사용자 페이로드(payload)를 소켓 객체(client['user'])에 저장하여 다른 @SubscribeMessage 핸들러에서 쉽게 접근할 수 있도록 합니다.
728x90
'Nest.js를 배워보자 > 11. NestJS WebSocket 실전 — 실시간 시스템 만들기' 카테고리의 다른 글
| 🖥️ NestJS 및 NuxtJS를 이용한 실시간 서버 상태 구현 (0) | 2025.12.03 |
|---|---|
| 실시간 채팅 예제와 인증된 WebSocket 연결 (0) | 2025.12.03 |
| NestJS WebSocket Gateway 기본 설정 (0) | 2025.12.03 |