From 3cb25049a845b86fa28c8c801d817b6f037bd7dd Mon Sep 17 00:00:00 2001 From: eeeeeaaan Date: Sun, 21 Sep 2025 20:12:58 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[FIX/#109]=20=EC=8A=A4=ED=85=9C=ED=94=84=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/PartnershipServiceImpl.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/assu/server/domain/partnership/service/PartnershipServiceImpl.java b/src/main/java/com/assu/server/domain/partnership/service/PartnershipServiceImpl.java index f70a5f86..3aa18f73 100644 --- a/src/main/java/com/assu/server/domain/partnership/service/PartnershipServiceImpl.java +++ b/src/main/java/com/assu/server/domain/partnership/service/PartnershipServiceImpl.java @@ -57,8 +57,13 @@ public class PartnershipServiceImpl implements PartnershipService { private final NotificationCommandService notificationService; + @Override + @Transactional public void recordPartnershipUsage(PartnershipRequestDTO.finalRequest dto, Member member){ + Student requestStudent = studentRepository.findById(member.getId()).orElseThrow( + () -> new GeneralException(ErrorStatus.NO_SUCH_STUDENT) // 혹은 적절한 예외 처리 + ); List usages = new ArrayList<>(); @@ -67,8 +72,9 @@ public void recordPartnershipUsage(PartnershipRequestDTO.finalRequest dto, Membe ); Long paperId = content.getPaper().getId(); // 1) 요청한 member 본인 - usages.add(PartnershipConverter.toPartnershipUsage(dto, member.getStudentProfile(), paperId)); - member.getStudentProfile().setStamp(); + usages.add(PartnershipConverter.toPartnershipUsage(dto, requestStudent, paperId)); + requestStudent.setStamp(); + System.out.println("update 된 stamp : "+requestStudent.getStamp()); List userIds = Optional.ofNullable(dto.getUserIds()).orElse(Collections.emptyList()); // 2) dto의 userIds에 있는 다른 사용자들 @@ -85,13 +91,15 @@ public void recordPartnershipUsage(PartnershipRequestDTO.finalRequest dto, Membe () -> new GeneralException(ErrorStatus.NO_SUCH_STORE) ); Partner partner = store.getPartner(); - if (partner != null) { - Long partnerId = partner.getId(); - notificationService.sendOrder(partnerId, 0L, dto.getTableNumber(), dto.getPartnershipContent()); - partnershipUsageRepository.saveAll(usages); - } else { - throw new GeneralException(ErrorStatus.NO_SUCH_PARTNER); - } + System.out.println("✨partnerId✨ = "+partner.getId()); + // if (partner != null) { + // Long partnerId = partner.getId(); + // System.out.println("알림 요청이 들어갑니다."); + // notificationService.sendOrder(partnerId, 0L, dto.getTableNumber(), dto.getPartnershipContent()); + // partnershipUsageRepository.saveAll(usages); + // } else { + // throw new GeneralException(ErrorStatus.NO_SUCH_PARTNER); + // } } From 5a5623fa14f4e7826c7b15840ffe39ed1c5e19a9 Mon Sep 17 00:00:00 2001 From: eeeeeaaan Date: Mon, 22 Sep 2025 17:18:05 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[FIX/#109]=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CertificationSessionManager.java | 104 +++++- .../config/CertifyWebSocketConfig.java | 48 +-- .../config/StompAuthChannelInterceptor.java | 57 ++- .../GroupCertificationController.java | 44 ++- .../dto/CertificationProgressResponseDTO.java | 9 - .../service/CertificationServiceImpl.java | 8 +- .../domain/chat/config/WebSocketConfig.java | 80 +++- src/main/resources/certify-test.html | 349 ++++++++++++++++-- 8 files changed, 571 insertions(+), 128 deletions(-) diff --git a/src/main/java/com/assu/server/domain/certification/component/CertificationSessionManager.java b/src/main/java/com/assu/server/domain/certification/component/CertificationSessionManager.java index 2b36a58f..91108335 100644 --- a/src/main/java/com/assu/server/domain/certification/component/CertificationSessionManager.java +++ b/src/main/java/com/assu/server/domain/certification/component/CertificationSessionManager.java @@ -4,33 +4,119 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Component; +import org.springframework.data.redis.core.StringRedisTemplate; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +// +// @Slf4j // ⭐️ SLF4j 로그 사용을 위해 추가 +// @Component +// public class CertificationSessionManager { +// private final Map> sessionUserMap = new ConcurrentHashMap<>(); +// +// public void openSession(Long sessionId) { +// sessionUserMap.put(sessionId, ConcurrentHashMap.newKeySet()); +// // ⭐️ 로그 추가 +// log.info("✅ New certification session opened. SessionID: {}", sessionId); +// } +// +// public void addUserToSession(Long sessionId, Long userId) { +// Set users = sessionUserMap.computeIfAbsent(sessionId, k -> { +// log.warn("Attempted to add user to a non-existent session. Creating new set for SessionID: {}", k); +// return ConcurrentHashMap.newKeySet(); +// }); +// +// boolean isAdded = users.add(userId); +// +// // ⭐️ 요청하신 멤버 추가 확인 로그 +// if (isAdded) { +// log.info("👤 User added to session. SessionID: {}, UserID: {}. Current participants: {}", +// sessionId, userId, users.size()); +// } else { +// log.info("👤 User already in session. SessionID: {}, UserID: {}. Current participants: {}", +// sessionId, userId, users.size()); +// } +// } +// +// public int getCurrentUserCount(Long sessionId) { +// return sessionUserMap.getOrDefault(sessionId, Set.of()).size(); +// } +// +// public boolean hasUser(Long sessionId, Long userId) { +// return sessionUserMap.getOrDefault(sessionId, Set.of()).contains(userId); +// } +// +// public List snapshotUserIds(Long sessionId) { +// return List.copyOf(sessionUserMap.getOrDefault(sessionId, Set.of())); +// } +// +// +// +// public void removeSession(Long sessionId) { +// sessionUserMap.remove(sessionId); +// // ⭐️ 로그 추가 +// log.info("❌ Certification session removed. SessionID: {}", sessionId); +// } +// } @Component +@RequiredArgsConstructor public class CertificationSessionManager { - private final Map> sessionUserMap = new ConcurrentHashMap<>(); + + // RedisTemplate을 주입받습니다. + private final StringRedisTemplate redisTemplate; + + // 세션 ID를 위한 KEY를 만드는 헬퍼 메서드 + private String getKey(Long sessionId) { + return "certification:session:" + sessionId; + } public void openSession(Long sessionId) { - sessionUserMap.put(sessionId, ConcurrentHashMap.newKeySet()); + String key = getKey(sessionId); + // 세션을 연다는 것은 키를 만드는 것과 같습니다. + // addUserToSession에서 자동으로 키가 생성되므로 이 메서드는 비워두거나, + // 만료 시간 설정 등 초기화 로직을 넣을 수 있습니다. + // 예: 10분 후 만료 + redisTemplate.expire(key, 10, TimeUnit.MINUTES); } public void addUserToSession(Long sessionId, Long userId) { - sessionUserMap.computeIfAbsent(sessionId, k -> ConcurrentHashMap.newKeySet()).add(userId); + String key = getKey(sessionId); + // Redis의 Set 자료구조에 userId를 추가합니다. + redisTemplate.opsForSet().add(key, String.valueOf(userId)); } public int getCurrentUserCount(Long sessionId) { - return sessionUserMap.getOrDefault(sessionId, Set.of()).size(); + String key = getKey(sessionId); + // Redis Set의 크기를 반환합니다. + Long size = redisTemplate.opsForSet().size(key); + return size != null ? size.intValue() : 0; } public boolean hasUser(Long sessionId, Long userId) { - return sessionUserMap.getOrDefault(sessionId, Set.of()).contains(userId); + String key = getKey(sessionId); + // Redis Set에 해당 멤버가 있는지 확인합니다. + return redisTemplate.opsForSet().isMember(key, String.valueOf(userId)); } + public List snapshotUserIds(Long sessionId) { - return List.copyOf(sessionUserMap.getOrDefault(sessionId, Set.of())); + String key = getKey(sessionId); + // Redis Set의 모든 멤버를 가져옵니다. + Set members = redisTemplate.opsForSet().members(key); + if (members == null) { + return List.of(); + } + return members.stream() + .map(Long::valueOf) + .collect(Collectors.toList()); } public void removeSession(Long sessionId) { - sessionUserMap.remove(sessionId); + String key = getKey(sessionId); + // 세션 키 자체를 삭제합니다. + redisTemplate.delete(key); } -} +} \ No newline at end of file diff --git a/src/main/java/com/assu/server/domain/certification/config/CertifyWebSocketConfig.java b/src/main/java/com/assu/server/domain/certification/config/CertifyWebSocketConfig.java index 85d7fc27..56da642a 100644 --- a/src/main/java/com/assu/server/domain/certification/config/CertifyWebSocketConfig.java +++ b/src/main/java/com/assu/server/domain/certification/config/CertifyWebSocketConfig.java @@ -11,27 +11,27 @@ import lombok.RequiredArgsConstructor; -@EnableWebSocketMessageBroker -@Configuration -@RequiredArgsConstructor -public class CertifyWebSocketConfig implements WebSocketMessageBrokerConfigurer { - - private final StompAuthChannelInterceptor stompAuthChannelInterceptor; - @Override - public void configureMessageBroker(MessageBrokerRegistry config) { - config.enableSimpleBroker("/certification"); // 인증현황을 받아보기 위한 구독 주소 - config.setApplicationDestinationPrefixes("/app"); // 클라이언트가 인증 요청을 보내는 주소 - } - - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - registry.addEndpoint("/ws").setAllowedOriginPatterns("*"); // 클라이언트 WebSocket 연결 주소 - // .setAllowedOriginPatterns("http://10.0.2.2:8080", "ws://10.0.2.2:8080");// CORS 허용 - } - - @Override - public void configureClientInboundChannel(ChannelRegistration registration) { - registration.interceptors(stompAuthChannelInterceptor); - } - -} +// @EnableWebSocketMessageBroker +// @Configuration +// @RequiredArgsConstructor +// public class CertifyWebSocketConfig implements WebSocketMessageBrokerConfigurer { +// +// private final StompAuthChannelInterceptor stompAuthChannelInterceptor; +// @Override +// public void configureMessageBroker(MessageBrokerRegistry config) { +// config.enableSimpleBroker("/certification"); // 인증현황을 받아보기 위한 구독 주소 +// config.setApplicationDestinationPrefixes("/app"); // 클라이언트가 인증 요청을 보내는 주소 +// } +// +// @Override +// public void registerStompEndpoints(StompEndpointRegistry registry) { +// registry.addEndpoint("/ws-certify").setAllowedOriginPatterns("*"); // 클라이언트 WebSocket 연결 주소 +// // .setAllowedOriginPatterns("http://10.0.2.2:8080", "ws://10.0.2.2:8080");// CORS 허용 +// } +// +// @Override +// public void configureClientInboundChannel(ChannelRegistration registration) { +// registration.interceptors(stompAuthChannelInterceptor); +// } +// +// } diff --git a/src/main/java/com/assu/server/domain/certification/config/StompAuthChannelInterceptor.java b/src/main/java/com/assu/server/domain/certification/config/StompAuthChannelInterceptor.java index b5da450e..20e33674 100644 --- a/src/main/java/com/assu/server/domain/certification/config/StompAuthChannelInterceptor.java +++ b/src/main/java/com/assu/server/domain/certification/config/StompAuthChannelInterceptor.java @@ -15,38 +15,61 @@ public class StompAuthChannelInterceptor implements ChannelInterceptor { private final JwtUtil jwtUtil; - @Override public Message preSend(Message message, MessageChannel channel) { StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); - log.info("StompCommand: {}", accessor.getCommand()); // StompCommand 로그 추가 if (StompCommand.CONNECT.equals(accessor.getCommand())) { - log.info("CONNECT command received."); - // 프론트에서 connect 시 Authorization 헤더 넣어야 함 String authHeader = accessor.getFirstNativeHeader("Authorization"); - log.info("Authorization Header: {}", authHeader); // Authorization 헤더 로그 추가 if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = jwtUtil.getTokenFromHeader(authHeader); - log.info("Extracted Token: {}", token); // 추출된 토큰 로그 추가 - - // JwtUtil 이용해서 Authentication 복원 Authentication authentication = jwtUtil.getAuthentication(token); - log.info("Authentication restored: {}", authentication); // 복원된 인증 정보 로그 추가 - // WebSocket 세션에 Authentication(UserPrincipal) 저장 + // ⭐️ 이 부분을 수정 accessor.setUser(authentication); - log.info("User principal set on accessor."); - } else { - log.warn("Authorization header is missing or not in Bearer format."); + + // ⭐️ 추가: 메시지 헤더에도 Authentication 정보 저장 + accessor.setHeader(StompHeaderAccessor.USER_HEADER, authentication); + + log.info("Authentication set: {}", authentication); } - } else if (StompCommand.SEND.equals(accessor.getCommand())) { - // SEND 명령어에 대한 로그 추가 (메시지 전송 시) - Object payload = message.getPayload(); - log.info("SEND command received. Destination: {}, Payload: {}", accessor.getDestination(), payload); } return message; } + + // @Override + // public Message preSend(Message message, MessageChannel channel) { + // StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); + // log.info("StompCommand: {}", accessor.getCommand()); // StompCommand 로그 추가 + // + // if (StompCommand.CONNECT.equals(accessor.getCommand())) { + // log.info("CONNECT command received."); + // // 프론트에서 connect 시 Authorization 헤더 넣어야 함 + // String authHeader = accessor.getFirstNativeHeader("Authorization"); + // log.info("Authorization Header: {}", authHeader); // Authorization 헤더 로그 추가 + // + // if (authHeader != null && authHeader.startsWith("Bearer ")) { + // String token = jwtUtil.getTokenFromHeader(authHeader); + // log.info("Extracted Token: {}", token); // 추출된 토큰 로그 추가 + // + // // JwtUtil 이용해서 Authentication 복원 + // Authentication authentication = jwtUtil.getAuthentication(token); + // log.info("Authentication restored: {}", authentication); // 복원된 인증 정보 로그 추가 + // + // // WebSocket 세션에 Authentication(UserPrincipal) 저장 + // accessor.setUser(authentication); + // log.info("User principal set on accessor."); + // } else { + // log.warn("Authorization header is missing or not in Bearer format."); + // } + // } else if (StompCommand.SEND.equals(accessor.getCommand())) { + // // SEND 명령어에 대한 로그 추가 (메시지 전송 시) + // Object payload = message.getPayload(); + // log.info("SEND command received. Destination: {}, Payload: {}", accessor.getDestination(), payload); + // } + // + // return message; + // } } \ No newline at end of file diff --git a/src/main/java/com/assu/server/domain/certification/controller/GroupCertificationController.java b/src/main/java/com/assu/server/domain/certification/controller/GroupCertificationController.java index ee919c59..3114836c 100644 --- a/src/main/java/com/assu/server/domain/certification/controller/GroupCertificationController.java +++ b/src/main/java/com/assu/server/domain/certification/controller/GroupCertificationController.java @@ -1,10 +1,16 @@ package com.assu.server.domain.certification.controller; +import java.security.Principal; + import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.stereotype.Component; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; import com.assu.server.domain.certification.dto.GroupSessionRequest; import com.assu.server.domain.certification.service.CertificationService; @@ -17,32 +23,32 @@ @Slf4j @Controller // STOMP 메시지 처리를 위한 컨트롤러 @RequiredArgsConstructor +@Component +@RequestMapping("/app") public class GroupCertificationController { private final CertificationService certificationService; @MessageMapping("/certify") - public void certifyGroup(@Payload GroupSessionRequest dto, SimpMessageHeaderAccessor headerAccessor) { - try { - log.info("### SUCCESS ### 인증 요청 메시지 수신 - adminId: {}, sessionId: {}", dto.getAdminId(), dto.getSessionId()); - - // Authentication에서 Member 정보 추출 - Authentication auth = (Authentication) headerAccessor.getUser(); - if (auth != null && auth.getPrincipal() instanceof PrincipalDetails) { - PrincipalDetails principalDetails = (PrincipalDetails) auth.getPrincipal(); - // 실제 비즈니스 로직 호출 - certificationService.handleCertification(dto, principalDetails.getMember()); - log.info("### SUCCESS ### 그룹 인증 처리 완료"); + public void certifyGroup(@Payload GroupSessionRequest dto, + Principal principal) { + if (principal instanceof UsernamePasswordAuthenticationToken) { + UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken)principal; + PrincipalDetails principalDetails = (PrincipalDetails)auth.getPrincipal(); + + try { + log.info("### SUCCESS ### 인증 요청 메시지 수신 - user: {}, adminId: {}, sessionId: {}", + principalDetails.getUsername(), dto.getAdminId(), dto.getSessionId()); + + // 헤더를 직접 다룰 필요 없이, 바로 principalDetails 객체를 사용 + if (principalDetails != null) { + certificationService.handleCertification(dto, principalDetails.getMember()); + log.info("### SUCCESS ### 그룹 인증 처리 완료"); + } + } catch (Exception e) { + log.error("### ERROR ### 인증 처리 실패", e); } - } catch (Exception e) { - log.error("### ERROR ### 인증 처리 실패", e); } } - // @MessageMapping("/certify") - // public void certifyGroup(SimpMessageHeaderAccessor headerAccessor) { - // log.info("### DEBUG ### 메서드 진입!"); - // log.info("### DEBUG ### User: {}", headerAccessor.getUser()); - // log.info("### DEBUG ### SessionId: {}", headerAccessor.getSessionId()); - // } } diff --git a/src/main/java/com/assu/server/domain/certification/dto/CertificationProgressResponseDTO.java b/src/main/java/com/assu/server/domain/certification/dto/CertificationProgressResponseDTO.java index 4221fe99..510a1b8c 100644 --- a/src/main/java/com/assu/server/domain/certification/dto/CertificationProgressResponseDTO.java +++ b/src/main/java/com/assu/server/domain/certification/dto/CertificationProgressResponseDTO.java @@ -37,13 +37,4 @@ public class CertificationProgressResponseDTO { private Integer count; private String message; private List userIds; - - // 생성자들 - public static CertificationProgressResponseDTO progress(int count) { - return new CertificationProgressResponseDTO("progress", count, null, null); - } - - public static CertificationProgressResponseDTO completed(String message, List userIds) { - return new CertificationProgressResponseDTO("completed", userIds.size(), message, userIds); - } } \ No newline at end of file diff --git a/src/main/java/com/assu/server/domain/certification/service/CertificationServiceImpl.java b/src/main/java/com/assu/server/domain/certification/service/CertificationServiceImpl.java index 01946fc6..79f65260 100644 --- a/src/main/java/com/assu/server/domain/certification/service/CertificationServiceImpl.java +++ b/src/main/java/com/assu/server/domain/certification/service/CertificationServiceImpl.java @@ -110,7 +110,8 @@ public void handleCertification(GroupSessionRequest dto, Member member) { boolean isDoubledUser= sessionManager.hasUser(sessionId, userId); if(isDoubledUser) { messagingTemplate.convertAndSend("/certification/progress/"+sessionId, - new CertificationProgressResponseDTO("progress", 0,"doubled member", null)); + new CertificationProgressResponseDTO("progress", null, + "doubled member", sessionManager.snapshotUserIds(sessionId))); throw new GeneralException(ErrorStatus.DOUBLE_CERTIFIED_USER); } @@ -127,11 +128,8 @@ public void handleCertification(GroupSessionRequest dto, Member member) { new CertificationProgressResponseDTO("completed", currentCertifiedNumber, "인증이 완료되었습니다.", sessionManager.snapshotUserIds(sessionId))); } else { messagingTemplate.convertAndSend("/certification/progress/" + sessionId, - new CertificationProgressResponseDTO("progress", currentCertifiedNumber, null, null)); + new CertificationProgressResponseDTO("progress", currentCertifiedNumber, null, sessionManager.snapshotUserIds(sessionId))); } - - - } @Override diff --git a/src/main/java/com/assu/server/domain/chat/config/WebSocketConfig.java b/src/main/java/com/assu/server/domain/chat/config/WebSocketConfig.java index ff1ba85f..1e2c06a1 100644 --- a/src/main/java/com/assu/server/domain/chat/config/WebSocketConfig.java +++ b/src/main/java/com/assu/server/domain/chat/config/WebSocketConfig.java @@ -1,16 +1,62 @@ +// package com.assu.server.domain.chat.config; +// +// import org.springframework.context.annotation.Configuration; +// import org.springframework.messaging.simp.config.ChannelRegistration; +// import org.springframework.messaging.simp.config.MessageBrokerRegistry; +// import org.springframework.web.socket.config.annotation.*; +// +// @Configuration +// @EnableWebSocketMessageBroker +// public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { +// +// @Override +// public void registerStompEndpoints(StompEndpointRegistry registry) { +// registry.addEndpoint("/ws") // 클라이언트 WebSocket 연결 지점 +// .setAllowedOriginPatterns( +// "*", +// "https://assu.shop", +// "http://localhost:63342", +// "http://localhost:5173", // Vite 기본 +// "http://localhost:3000", // CRA/Next 기본 +// "http://127.0.0.1:*", +// "http://192.168.*.*:*"); // 같은 LAN의 실제 기기 테스트용// fallback for old browsers +// // 같은 LAN의 실제 기기 테스트용 +// // fallback for old browsers +// +// // ✅ 모바일/안드로이드용 (네이티브 WebSocket) +// registry.addEndpoint("/ws") +// .setAllowedOriginPatterns("*"); // wss 사용 시 TLS 세팅 +// } +// +// @Override +// public void configureMessageBroker(MessageBrokerRegistry registry) { +// registry.setApplicationDestinationPrefixes("/pub"); // 클라이언트가 보내는 prefix +// registry.enableSimpleBroker("/sub"); // 서버가 보내는 prefix +// } +// } package com.assu.server.domain.chat.config; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; -import org.springframework.web.socket.config.annotation.*; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +import com.assu.server.domain.certification.config.StompAuthChannelInterceptor; + +import lombok.RequiredArgsConstructor; @Configuration @EnableWebSocketMessageBroker +@RequiredArgsConstructor public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + private final StompAuthChannelInterceptor stompAuthChannelInterceptor; + @Override public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws") // 클라이언트 WebSocket 연결 지점 .setAllowedOriginPatterns( "*", @@ -19,18 +65,32 @@ public void registerStompEndpoints(StompEndpointRegistry registry) { "http://localhost:5173", // Vite 기본 "http://localhost:3000", // CRA/Next 기본 "http://127.0.0.1:*", - "http://192.168.*.*:*"); // 같은 LAN의 실제 기기 테스트용// fallback for old browsers - // 같은 LAN의 실제 기기 테스트용 - // fallback for old browsers + "http://192.168.*.*:*"); + // 채팅용 엔드포인트 + + // 인증용 엔드포인트 + registry.addEndpoint("/ws-certify") + .setAllowedOriginPatterns("*"); + + registry.addEndpoint("/ws-certify/sock") + .setAllowedOriginPatterns("*") + .withSockJS(); // ⬅️ SockJS를 반드시 포함해야 합니다. - // ✅ 모바일/안드로이드용 (네이티브 WebSocket) - registry.addEndpoint("/ws") - .setAllowedOriginPatterns("*"); // wss 사용 시 TLS 세팅 } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { - registry.setApplicationDestinationPrefixes("/pub"); // 클라이언트가 보내는 prefix - registry.enableSimpleBroker("/sub"); // 서버가 보내는 prefix + // 채팅용 + registry.setApplicationDestinationPrefixes("/pub"); + registry.enableSimpleBroker("/sub"); + + // 인증용 추가 + registry.setApplicationDestinationPrefixes("/pub", "/app"); // 둘 다 추가 + registry.enableSimpleBroker("/sub", "/certification"); // 둘 다 추가 + } + + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + registration.interceptors(stompAuthChannelInterceptor); } -} +} \ No newline at end of file diff --git a/src/main/resources/certify-test.html b/src/main/resources/certify-test.html index fa33109d..90116f62 100644 --- a/src/main/resources/certify-test.html +++ b/src/main/resources/certify-test.html @@ -1,56 +1,335 @@ - - Certify Test - - + + + WebSocket 인증 테스트 - 인증자 + + + -

WebSocket Certify Test

+
+

🔐 WebSocket 인증 테스트 - 인증자

-
-
-
- - -
+
+

📋 서버 설정

+
+ + +
-

+        
+ + +
+
- \ No newline at end of file