diff --git a/gotcha-socket/src/main/java/socket_server/common/auth/JwtChannelInterceptor.java b/gotcha-socket/src/main/java/socket_server/common/auth/JwtChannelInterceptor.java index 83f40f42..eb6fe43d 100644 --- a/gotcha-socket/src/main/java/socket_server/common/auth/JwtChannelInterceptor.java +++ b/gotcha-socket/src/main/java/socket_server/common/auth/JwtChannelInterceptor.java @@ -48,6 +48,9 @@ public Message preSend(Message message, MessageChannel channel) { } catch (AuthenticationServiceException e) { throw new MessagingException(toErrorPayload(JwtExceptionCode.ACCESS_TOKEN_NOT_FOUND)); } catch (Throwable e) { + System.err.println("=== [DEBUG][JwtChannelInterceptor] 예외 발생 ==="); + e.printStackTrace(); + throw new MessagingException(toErrorPayload(GlobalExceptionCode.INTERNAL_SERVER_ERROR)); } } @@ -64,5 +67,4 @@ private String toErrorPayload(ExceptionCode code) { } - } diff --git a/gotcha-socket/src/main/java/socket_server/common/auth/UserStatusInterceptor.java b/gotcha-socket/src/main/java/socket_server/common/auth/UserStatusInterceptor.java new file mode 100644 index 00000000..2506bc30 --- /dev/null +++ b/gotcha-socket/src/main/java/socket_server/common/auth/UserStatusInterceptor.java @@ -0,0 +1,58 @@ +package socket_server.common.auth; + +import gotcha_common.exception.exceptionCode.ExceptionCode; +import gotcha_common.exception.exceptionCode.GlobalExceptionCode; +import gotcha_domain.auth.SecurityUserDetails; +import gotcha_domain.user.User; +import lombok.RequiredArgsConstructor; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessagingException; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.messaging.support.MessageHeaderAccessor; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import socket_server.common.exception.socket.SocketUserStatusExceptionCode; + +@Component +@RequiredArgsConstructor +public class UserStatusInterceptor implements ChannelInterceptor { + + @Override + public Message preSend(Message message, MessageChannel channel) { + StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); + + try { + if (accessor != null && StompCommand.CONNECT.equals(accessor.getCommand())) { + Authentication auth = (Authentication) accessor.getUser(); + SecurityUserDetails details = (SecurityUserDetails) auth.getPrincipal(); + User user = details.getUser(); + + SocketUserStatusExceptionCode code = + SocketUserStatusExceptionCode.fromStatus(user.getUserStatus()); + + if (code != null) { + throw new MessagingException(toErrorPayload(code)); + } + + } + + return message; + } catch (Throwable e) { + if (e instanceof MessagingException) { + throw (MessagingException) e; + } + System.err.println("=== [DEBUG][UserStatusInterceptor] 예외 발생 ==="); + e.printStackTrace(); + + throw new MessagingException(toErrorPayload(GlobalExceptionCode.INTERNAL_SERVER_ERROR)); + } + } + + private String toErrorPayload(ExceptionCode code) { + return String.format("{\"errorCode\":\"%s\", \"status\":%d, \"message\":\"%s\"}", code.getCode(), + code.getStatus().value(), code.getMessage()); + } +} diff --git a/gotcha-socket/src/main/java/socket_server/common/config/WebSocketConfig.java b/gotcha-socket/src/main/java/socket_server/common/config/WebSocketConfig.java index 7d542790..8a9e8545 100644 --- a/gotcha-socket/src/main/java/socket_server/common/config/WebSocketConfig.java +++ b/gotcha-socket/src/main/java/socket_server/common/config/WebSocketConfig.java @@ -8,6 +8,7 @@ import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; import socket_server.common.auth.JwtChannelInterceptor; +import socket_server.common.auth.UserStatusInterceptor; @Configuration @EnableWebSocketMessageBroker @@ -15,10 +16,14 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { private static final String ENDPOINT = "/ws-connect"; private final JwtChannelInterceptor jwtChannelInterceptor; + private final UserStatusInterceptor userStatusInterceptor; @Override public void configureClientInboundChannel(ChannelRegistration registration) { - registration.interceptors(jwtChannelInterceptor); + registration.interceptors( + jwtChannelInterceptor, // 1. 인증(jwt) + userStatusInterceptor // 2. 인가(정지 계정) + ); } //레디스 메시지 브로커 사용 diff --git a/gotcha-socket/src/main/java/socket_server/common/exception/socket/SocketUserStatusExceptionCode.java b/gotcha-socket/src/main/java/socket_server/common/exception/socket/SocketUserStatusExceptionCode.java new file mode 100644 index 00000000..791e1469 --- /dev/null +++ b/gotcha-socket/src/main/java/socket_server/common/exception/socket/SocketUserStatusExceptionCode.java @@ -0,0 +1,39 @@ +package socket_server.common.exception.socket; + +import gotcha_common.exception.exceptionCode.ExceptionCode; +import gotcha_domain.user.UserStatus; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; + +@AllArgsConstructor +public enum SocketUserStatusExceptionCode implements ExceptionCode { + USER_SUSPENDED(HttpStatus.FORBIDDEN, "SOCKET-403-001", "계정이 일시 정지되었습니다."), + USER_BANNED(HttpStatus.FORBIDDEN, "SOCKET-403-002", "계정이 영구 정지되었습니다."); + + private final HttpStatus status; + private final String code; + private final String message; + + @Override + public HttpStatus getStatus() { + return status; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } + + public static SocketUserStatusExceptionCode fromStatus(UserStatus status) { + return switch (status) { + case SUSPENDED -> USER_SUSPENDED; + case BANNED -> USER_BANNED; + default -> null; // 정상 계정 + }; + } +} diff --git a/gotcha/src/main/java/Gotcha/domain/auth/service/SignInUseCase.java b/gotcha/src/main/java/Gotcha/domain/auth/service/SignInUseCase.java index bf54dc35..4675c1f9 100644 --- a/gotcha/src/main/java/Gotcha/domain/auth/service/SignInUseCase.java +++ b/gotcha/src/main/java/Gotcha/domain/auth/service/SignInUseCase.java @@ -27,16 +27,13 @@ public TokenDto execute(SignInReq signInReq) { // 2. 정지기간 만료되었는지 확인 sanctionService.validateSuspendedEndDate(user); - // 3. 제재/차단 상태 확인 - sanctionService.validateLoginAccess(user); - - // 4. 경고 확인 + // 3. 경고 확인 Optional warningOpt = sanctionService.findAndMarkUnreadWarning(user); - // 5. 토큰 생성 + // 4. 토큰 생성 TokenDto tokenDto = jwtHelper.createToken(user, signInReq.autoSignIn()); - // 6. 경고 메시지가 있으면 토큰에 추가하여 반환 + // 5. 경고 메시지가 있으면 토큰에 추가하여 반환 return warningOpt .map(warning -> TokenDto.of(tokenDto.accessToken(), tokenDto.refreshToken(), tokenDto.accessTokenExpiredAt(), tokenDto.autoSignIn(), warning)) .orElse(tokenDto); diff --git a/gotcha/src/main/java/Gotcha/domain/sanction/service/SanctionService.java b/gotcha/src/main/java/Gotcha/domain/sanction/service/SanctionService.java index 4dd9d48a..2903c630 100644 --- a/gotcha/src/main/java/Gotcha/domain/sanction/service/SanctionService.java +++ b/gotcha/src/main/java/Gotcha/domain/sanction/service/SanctionService.java @@ -1,7 +1,5 @@ package Gotcha.domain.sanction.service; -import Gotcha.domain.auth.exception.AuthExceptionCode; -import Gotcha.domain.auth.exception.UserAccountStatusException; import Gotcha.domain.report.service.UserReportService; import Gotcha.domain.sanction.dto.SanctionReq; import Gotcha.domain.sanction.dto.SanctionRes; @@ -14,13 +12,10 @@ import gotcha_domain.user.User; import gotcha_domain.user.UserStatus; import gotcha_user.service.UserService; -import jakarta.persistence.TableGenerator; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; @Service @@ -67,25 +62,6 @@ public SanctionRes sanctionUser(SanctionReq sanctionReq, String adminId) { return SanctionRes.fromEntity(userSanction); } - @Transactional - public void validateLoginAccess(User user) { - if (user.getUserStatus() == UserStatus.SUSPENDED || user.getUserStatus() == UserStatus.BANNED) { - AuthExceptionCode code = user.getUserStatus() == UserStatus.SUSPENDED ? - AuthExceptionCode.ACCOUNT_SUSPENDED : AuthExceptionCode.ACCOUNT_BANNED; - - UserSanction sanction = findLatestUnread(user) - .orElseThrow(()->new CustomException(AuthExceptionCode.SANCTION_NOT_FOUND)); - sanction.markAsRead(); - - Map details = new HashMap<>(); - details.put("reason", sanction.getReason()); - if (sanction.getExpireDuration() != null) { - details.put("expireDuration", sanction.getExpireDuration()); - } - throw new UserAccountStatusException(code, details); - } - } - @Transactional public void validateSuspendedEndDate(User user) { if(user.getUserStatus()==UserStatus.SUSPENDED) { @@ -102,10 +78,6 @@ public Optional findAndMarkUnreadWarning(User user) { }); } - public Optional findLatestUnread(User user) { - return sanctionRepository.findTopByUserAndIsReadIsFalseOrderByCreatedAtDesc(user); - } - public Optional findLatestUnread(User user, SanctionType type) { return sanctionRepository.findTopByUserAndSanctionTypeAndIsReadIsFalseOrderByCreatedAtDesc(user, type); }