22
33import com .back .global .security .CustomUserDetails ;
44import com .back .global .security .JwtTokenProvider ;
5+ import com .back .global .websocket .service .WebSocketSessionManager ;
56import lombok .RequiredArgsConstructor ;
67import lombok .extern .slf4j .Slf4j ;
78import org .springframework .context .annotation .Configuration ;
2829public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
2930
3031 private final JwtTokenProvider jwtTokenProvider ;
32+ private final WebSocketSessionManager sessionManager ;
3133
3234 /**
3335 * 메시지 브로커 설정
@@ -55,11 +57,11 @@ public void registerStompEndpoints(StompEndpointRegistry registry) {
5557
5658 /**
5759 * WebSocket 메시지 채널 설정
58- * JWT 인증 인터셉터 등록
60+ * JWT 인증 인터셉터 및 세션 관리 로직 등록
5961 */
6062 @ Override
6163 public void configureClientInboundChannel (ChannelRegistration registration ) {
62- // JWT 인증 인터셉터 등록
64+ // JWT 인증 + 세션 관리 인터셉터 등록
6365 registration .interceptors (new ChannelInterceptor () {
6466 @ Override
6567 public Message <?> preSend (Message <?> message , MessageChannel channel ) {
@@ -74,9 +76,19 @@ public Message<?> preSend(Message<?> message, MessageChannel channel) {
7476 authenticateUser (accessor );
7577 }
7678
77- // SEND 시점에서도 인증 확인 (추가 보안)
79+ // SEND 시점에서 인증 확인 및 활동 시간 업데이트
7880 else if (StompCommand .SEND .equals (accessor .getCommand ())) {
79- validateAuthentication (accessor );
81+ validateAuthenticationAndUpdateActivity (accessor );
82+ }
83+
84+ // SUBSCRIBE 시점에서 방 입장 처리
85+ else if (StompCommand .SUBSCRIBE .equals (accessor .getCommand ())) {
86+ handleRoomSubscription (accessor );
87+ }
88+
89+ // UNSUBSCRIBE 시점에서 방 퇴장 처리
90+ else if (StompCommand .UNSUBSCRIBE .equals (accessor .getCommand ())) {
91+ handleRoomUnsubscription (accessor );
8092 }
8193 }
8294
@@ -111,24 +123,109 @@ private void authenticateUser(StompHeaderAccessor accessor) {
111123 // 세션에 사용자 정보 저장
112124 accessor .setUser (authentication );
113125
126+ log .info ("WebSocket 인증 성공 - 사용자: {} (ID: {}), 세션: {}" ,
127+ userDetails .getUsername (), userDetails .getUserId (), accessor .getSessionId ());
128+
114129 } catch (Exception e ) {
130+ log .error ("WebSocket 인증 실패: {}" , e .getMessage ());
115131 throw new RuntimeException ("WebSocket 인증에 실패했습니다: " + e .getMessage ());
116132 }
117133 }
118134
119135 /**
120- * 메시지 전송 시 인증 상태 확인
136+ * 메시지 전송 시 인증 상태 확인 및 활동 시간 업데이트
121137 */
122- private void validateAuthentication (StompHeaderAccessor accessor ) {
138+ private void validateAuthenticationAndUpdateActivity (StompHeaderAccessor accessor ) {
123139 if (accessor .getUser () == null ) {
124140 throw new RuntimeException ("인증이 필요합니다" );
125141 }
126142
127- // 인증된 사용자 정보 로깅
143+ // 인증된 사용자 정보 추출 및 활동 시간 업데이트
128144 Authentication auth = (Authentication ) accessor .getUser ();
129145 if (auth .getPrincipal () instanceof CustomUserDetails userDetails ) {
146+ Long userId = userDetails .getUserId ();
147+
148+ // 사용자 활동 시간 업데이트
149+ sessionManager .updateLastActivity (userId );
150+
130151 log .debug ("인증된 사용자 메시지 전송 - 사용자: {} (ID: {}), 목적지: {}" ,
131- userDetails .getUsername (), userDetails .getUserId (), accessor .getDestination ());
152+ userDetails .getUsername (), userId , accessor .getDestination ());
153+ }
154+ }
155+
156+ /**
157+ * 방 채팅 구독 시 방 입장 처리
158+ */
159+ private void handleRoomSubscription (StompHeaderAccessor accessor ) {
160+ try {
161+ String destination = accessor .getDestination ();
162+
163+ // 방 채팅 구독인지 확인: /topic/rooms/{roomId}/chat
164+ if (destination != null && destination .matches ("/topic/rooms/\\ d+/chat" )) {
165+ Long roomId = extractRoomIdFromDestination (destination );
166+
167+ if (roomId != null && accessor .getUser () != null ) {
168+ Authentication auth = (Authentication ) accessor .getUser ();
169+ if (auth .getPrincipal () instanceof CustomUserDetails userDetails ) {
170+ Long userId = userDetails .getUserId ();
171+
172+ // 방 입장 처리
173+ sessionManager .joinRoom (userId , roomId );
174+
175+ log .info ("방 입장 처리 완료 - 사용자: {} (ID: {}), 방: {}" ,
176+ userDetails .getUsername (), userId , roomId );
177+ }
178+ }
179+ }
180+ } catch (Exception e ) {
181+ log .error ("방 구독 처리 중 오류 발생: {}" , e .getMessage (), e );
182+ }
183+ }
184+
185+ /**
186+ * 방 채팅 구독 해제 시 방 퇴장 처리
187+ */
188+ private void handleRoomUnsubscription (StompHeaderAccessor accessor ) {
189+ try {
190+ String destination = accessor .getDestination ();
191+
192+ // 방 채팅 구독 해제인지 확인: /topic/rooms/{roomId}/chat
193+ if (destination != null && destination .matches ("/topic/rooms/\\ d+/chat" )) {
194+ Long roomId = extractRoomIdFromDestination (destination );
195+
196+ if (roomId != null && accessor .getUser () != null ) {
197+ Authentication auth = (Authentication ) accessor .getUser ();
198+ if (auth .getPrincipal () instanceof CustomUserDetails userDetails ) {
199+ Long userId = userDetails .getUserId ();
200+
201+ // 방 퇴장 처리
202+ sessionManager .leaveRoom (userId , roomId );
203+
204+ log .info ("방 퇴장 처리 완료 - 사용자: {} (ID: {}), 방: {}" ,
205+ userDetails .getUsername (), userId , roomId );
206+ }
207+ }
208+ }
209+ } catch (Exception e ) {
210+ log .error ("방 구독 해제 처리 중 오류 발생: {}" , e .getMessage (), e );
211+ }
212+ }
213+
214+ /**
215+ * destination에서 방 ID 추출
216+ * ex) "/topic/rooms/123/chat" -> 123
217+ */
218+ private Long extractRoomIdFromDestination (String destination ) {
219+ try {
220+ if (destination == null ) return null ;
221+
222+ String [] parts = destination .split ("/" );
223+ if (parts .length >= 4 && "rooms" .equals (parts [2 ])) {
224+ return Long .parseLong (parts [3 ]);
225+ }
226+ } catch (NumberFormatException e ) {
227+ log .warn ("방 ID 추출 실패 - destination: {}, 에러: {}" , destination , e .getMessage ());
132228 }
229+ return null ;
133230 }
134- }
231+ }
0 commit comments