개발 기록/도서관 관리 시스템

[Library Management System] 25.09.25 (54일) | (리팩토링) 에러/성공 코드와 메시지 재정의하고 회원가입, 로그인, 로그아웃

dev.jelee 2025. 9. 25. 23:05

[ 작업한 내용 ]

< 공용: 회원가입 >

# JoinReqDTO

- 회원가입 요청 DTO 정의

- 사용자 아이디, 비밀번호, 이메일 (username, password, email)

@Getter
public class JoinReqDTO {
  private String username;
  private String password;
  private String email;
}

# JoinResDTO

- 회원가입 응답 DTO 정의

- 사용자 고유번호, 사용자 아이디 (id, username)

- User 엔티티를 파라미터로 받는 생성자 함수 정의

@Getter
public class JoinResDTO {
  private Long id;
  private String username;

  public JoinResDTO(User user) {
    this.id = user.getId();
    this.username = user.getUsername();
  }
}

# AuthController

- POST /api/v1/auth/signup 으로 요청합니다.

- 파라미터로 JoinReqDTO를 받으며, JoinReqDTO의 구성은 username, password, email입니다.

- Service계층으로 request(username, password, email)을 전달합니다.

- 반환 받은 결과는 JoinResDTO 형태로 저장합니다.

- 클라이언트 측으로 성공 메시지와 결과를 ApiResponse.success()로 감싸서 응답합니다.

// 공용: 회원가입
@PostMapping("/signup")
public ResponseEntity<?> singUp(@RequestBody JoinReqDTO request) {
  
  // 서비스로직
  JoinResDTO resonseDTO = authService.signUp(request);

  // 성공메시지
  String message = messageProvider.getMessage(UserSuccessCode.USER_CREATED.getMessage());
  
  // 응답
  return ResponseEntity
            .status(UserSuccessCode.USER_CREATED.getHttpStatus())
            .body(ApiResponse.success(
              UserSuccessCode.USER_CREATED, 
              message, 
              resonseDTO));
}

# AuthService

- userRepository.existsByUsername(request.getUsername())으로 아이디 중복 체크를 합니다. 아이디가 중복이 되면 

- userRepository.existsByEmail(request.getEmail())로 이메일 중복 체크를 합니다.

- 요청받은 DTO를 가지고 User 엔티티를 생성하고 userRepository.save(user)을 사용하여 DB에 User 객체를 저장한다.

- Controller 계층으로 user를 기반으로 JoinResDTO 객체를 생성하여 반환한다.

/*
  * 공용: 회원가입
  */
@Transactional
public JoinResDTO signUp(JoinReqDTO request) {

  // 아이디 중복 체크
  if (userRepository.existsByUsername(request.getUsername())) {
    throw new BaseException(UserErrorCode.USER_USERNAME_DUPLICATED);
  }
  
  // 이메일 중복 체크
  if (userRepository.existsByEmail(request.getEmail())) {
    throw new BaseException(UserErrorCode.USER_EMAIL_DUPLICATED);
  }

  // User 엔티티 생성
  User user = User.builder()
            .username(request.getUsername())
            .password(passwordEncoder.encode(request.getPassword()))
            .email(request.getEmail())
            .role(Role.ROLE_USER)
            .joinDate(LocalDateTime.now())
            .build();
  
  // user 저장
  userRepository.save(user);
  
  // 반환
  return new JoinResDTO(user);
}

# UserRepository

- 아이디 중복 체크를 위해 boolean exsistsByUsername(String username); 메서드 생성.

- 이메일 중복 체크를 위해 boolean exsistsByEmail(String email); 메서드 생성

  // 아이디 중복 체크
  boolean existsByUsername(String username);

  // 이메일 중복 체크
  boolean existsByEmail(String email);

< 공용: 로그인 >

# LoginReqDTO

- 로그인 요청 DTO 정의

- 사용자 아이디, 비밀번호 (username, password)

@Getter
public class LoginReqDTO {
  private String username;
  private String password;
}

# LoginResDTO

- 로그인 응답 DTO 정의

- 사용자 아이디, 토큰 (username, token)

- User 엔티티와 String token을 파라미터로 받는 생성자 함수 정의

@Getter
public class LoginResDTO {
  private String username;
  private String token;

  public LoginResDTO(User user, String token) {
    this.username = user.getUsername();
    this.token = token;
  }
}

# AuthController

- POST /api/v1/auth/signin 으로 요청합니다.

- 파라미터로 LoginReqDTO를 받으며, LoginReqDTO의 구성은 username, password 입니다.

- Service계층으로 request(username, password)을 전달합니다.

- 반환 받은 결과는 LoginResDTO 형태로 저장합니다.

- ResponseCookie형태로 responseDTO.getTocken()에서 생성한 토큰과 HttpCookie에 저장할 설정을 정의합니다. 그리고 난 후 HttpServletResponse에 생성한 쿠키를 .addHeader()를 사용해 헤더에 담아 클라이언트 측으로 보냅니다.

- 클라이언트 측으로 성공 메시지와 결과를 ApiResponse.success()로 감싸서 응답합니다.

@PostMapping("/signin")
public ResponseEntity<?> signIn(
  @RequestBody LoginReqDTO request, 
  HttpServletResponse response) {
    
    // 서비스로직
    LoginResDTO responseDTO = authService.signIn(request);
    // String token = authService.signIn(request);

    // Jwt를 HttpOnly 쿠키에 저장
    ResponseCookie cookie = ResponseCookie.from("JWT", responseDTO.getToken())
                .httpOnly(true)
                .secure(true)
                .path("/")
                .maxAge(24 * 60 * 60)
                .sameSite("Strict")
                .build();

    response.addHeader("Set-Cookie", cookie.toString());

    // 성공메시지
    String message = messageProvider.getMessage(AuthSuccessCode.AUTH_LOGIN_SUCCESS.getMessage());

    // 응답
    return ResponseEntity
              .status(AuthSuccessCode.AUTH_LOGIN_SUCCESS.getHttpStatus())
              .body(ApiResponse.success(
                AuthSuccessCode.AUTH_LOGIN_SUCCESS,
                message, 
                responseDTO.getUsername()));
}

# AuthService

- userRepository.findByUsername(request.getUsername())으로 아이디가 유효한지 확인후 User 객체를 생성합니다. 아이디가 없다면 UserErrorCode.USER_NOT_FOUND 예외를 던집니다.

- 생성한 User 객체의 비밀번호와 요청받은 비밀번호를 !passwordEncoder.matches()를 사용하여 일치한지 체크하고 동일하지 않다면 UserErrorCode.INVALID_PASSWORD 예외를 던집니다.

- 사용자의 상태가 ACTIVE 인지 체크를 합니다. 만약에 상태가 ACTIVE가 아니라면 현재 사용자의 상태에 관련된 예외를 던집니다.

- user.setLastLoginDate(LocalDateTime.now())를 사용하여 마지막 로그인 시간을 저장한 후 userRepository.save(user)를 통해 DB에 마지막 로그인 시간을 저장합니다.

- jwtTokenProvider.generateToken(user)을 사용하여 로그인한 사용자의 정보를 토대로 토큰을 생성하여 String token으로 저장합니다.

- Controller 계층으로 user, token을 기반으로 LoginResDTO 객체를 생성하여 반환합니다.

@Transactional
public LoginResDTO signIn(LoginReqDTO request) {

  // 사용자 유효 검사
  User user = userRepository.findByUsername(request.getUsername())
    .orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND));
  
  // request와 DB의 password가 동일한지 체크
  if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
    throw new BaseException(UserErrorCode.INVALID_PASSWORD);
  }

  // 사용자의 상태가 ACTIVE인지 체크
  if (user.getStatus() == UserStatus.INACTIVE) {
    throw new BaseException(UserErrorCode.USER_STATUS_INACTIVE);
  } else if (user.getStatus() == UserStatus.SUSPENDED) {
    throw new BaseException(UserErrorCode.USER_STATUS_SUSPENDED);
  } else if (user.getStatus() == UserStatus.DELETED) {
    throw new BaseException(UserErrorCode.USER_STATUS_DELETED);
  }

  // 마지막 로그인 시간 저장
  user.setLastLoginDate(LocalDateTime.now());
  userRepository.save(user);

  // 토큰 생성
  String token = jwtTokenProvider.generateToken(user);

  // 반환
  return new LoginResDTO(user, token);
}

# UserRepository

- username으로 사용자 전체 엔티티를 조회하는 Optional<User> findByUsername(String username) 메서드 생성

// username으로 사용자 전체 엔티티 조회
Optional<User> findByUsername(String username);

< 공용: 로그아웃 >

# LogoutResDTO

- 로그아웃 응답 DTO 정의

- 사용자 고유번호, 사용자 아이디 (id, username)

- User 엔티티를 파라미터로 받는 생성자 함수 정의

@Getter
public class LogoutResDTO {
  private Long id;
  private String username;

  public LogoutResDTO(User user) {
    this.id = user.getId();
    this.username = user.getUsername();
  }
}

# AuthController

- POST /api/v1/auth/logout 으로 요청합니다.

- 파라미터로 @AuthenticationPrincipal을 사용해 가져온 사용자 정보를 User 객체로 저장합니다. 그리고 HttpServletResponse타입의 빈 객체 response을 생성합니다.

- 제거용 JWT 쿠키를 생성하여 HttpServletResponse타입의 객체에 .addHeader()로 저장합니다.

- Service계층으로 user.getId를 전달합니다.

- 반환받은 결과는 LogoutResDTO 형태로 저장합니다.

- 성공메시지와 반환받은 결과를 ApiResponse.success()로 감싸서 클라이언트 측으로 반환합니다.

@PostMapping("/logout")
public ResponseEntity<?> logout(
  HttpServletResponse response,
  @AuthenticationPrincipal User user) {
  
  // Jwt 제거
  ResponseCookie deleteCookie = ResponseCookie.from("JWT", "")
              .httpOnly(true)
              .secure(true)
              .path("/")
              .maxAge(0) // 쿠키 즉시 만료
              .sameSite("Strict")
              .build();
  
  response.addHeader("Set-Cookie", deleteCookie.toString());

  // 서비스로직
  LogoutResDTO responseDTO = authService.logout(user.getId());

  // 성공메시지
  String message = messageProvider.getMessage(AuthSuccessCode.AUTH_LOGOUT_SUCCESS.getMessage());

  // 응답
  return ResponseEntity
            .status(AuthSuccessCode.AUTH_LOGOUT_SUCCESS.getHttpStatus())
            .body(ApiResponse.success(
              AuthSuccessCode.AUTH_LOGOUT_SUCCESS, 
              message, 
              responseDTO));
}

# AuthService

- userRepository.findById(userId)로 사용자를 조회하여 User 객체로 생성합니다. 만약에 없는 사용자라면 UserErrorCode.USER_NOT_FOUND 예외를 던집니다.

- Controller 계층으로 user를 토대로 LogoutResDTO 객체를 생성하여 반환합니다.

/*
  * 공용: 로그아웃
  */
public LogoutResDTO logout(Long userId) {
  
  // userId로 사용자 정보 조회
  User user = userRepository.findById(userId)
      .orElseThrow(() -> new BaseException(UserErrorCode.USER_NOT_FOUND));

  // 반환    
  return new LogoutResDTO(user);
}

commit
회원가입 성공 화면(좌), 로그인 성공 화면(가운데), 로그아웃 성공 화면(우)