Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Expand All @@ -64,5 +67,4 @@ private String toErrorPayload(ExceptionCode code) {
}



}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@
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
@RequiredArgsConstructor
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. 인가(정지 계정)
);
}

//레디스 메시지 브로커 사용
Expand Down
Original file line number Diff line number Diff line change
@@ -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; // 정상 계정
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,13 @@ public TokenDto execute(SignInReq signInReq) {
// 2. 정지기간 만료되었는지 확인
sanctionService.validateSuspendedEndDate(user);

// 3. 제재/차단 상태 확인
sanctionService.validateLoginAccess(user);

// 4. 경고 확인
// 3. 경고 확인
Optional<SanctionRes> 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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<String, Object> 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) {
Expand All @@ -102,10 +78,6 @@ public Optional<SanctionRes> findAndMarkUnreadWarning(User user) {
});
}

public Optional<UserSanction> findLatestUnread(User user) {
return sanctionRepository.findTopByUserAndIsReadIsFalseOrderByCreatedAtDesc(user);
}

public Optional<UserSanction> findLatestUnread(User user, SanctionType type) {
return sanctionRepository.findTopByUserAndSanctionTypeAndIsReadIsFalseOrderByCreatedAtDesc(user, type);
}
Expand Down
Loading