TIL - 20250806

juni·2025년 8월 6일

TIL

목록 보기
86/316

0806 스프링 시큐리티 기반 회원가입/로그인 구현


✅ 회원가입 기능 구현 (Controller, Service, DTO)

  • 회원가입 프로세스의 기본 골격을 구현했습니다. Spring MVC의 핵심 컴포넌트인 Controller, Service, DTO(Data Transfer Object)를 사용하여 역할을 분리하고 기능을 구현했습니다.

  • AuthController: 클라이언트의 HTTP 요청(@PostMapping("/signup"))을 받아들이는 진입점입니다. @RequestBody로 받은 JSON 데이터를 SignUpRequest DTO로 변환하고, @Valid를 통해 유효성 검사를 수행한 뒤 UserService에 처리를 위임합니다.

  • UserService: 실제 비즈니스 로직(회원 정보 저장, 중복 확인 등)을 처리하는 핵심 계층입니다.

  • SignUpRequest (DTO): 클라이언트로부터 받은 회원가입 데이터를 담는 객체입니다. @NotBlank, @Email 등의 유효성 검증 어노테이션을 적용하여, Controller 단에서 데이터의 유효성을 1차적으로 보장합니다.


✅ Spring Security 적용 및 비밀번호 암호화

  • 사용자의 민감 정보인 비밀번호를 안전하게 저장하기 위해 Spring Security를 도입하고 비밀번호 암호화를 적용했습니다. 평문 비밀번호를 DB에 저장하는 것은 심각한 보안 취약점입니다.

PasswordEncoder

  • 개념: 비밀번호를 단방향으로 해시(Hash)하여 암호화하는 Spring Security의 인터페이스입니다. 해시된 값은 복호화가 불가능하며, 대신 사용자가 입력한 비밀번호를 동일한 방식으로 해시하여 DB에 저장된 값과 비교(matches())하는 방식으로 인증을 수행합니다.
  • 구현: BCryptPasswordEncoderBean으로 등록하여 프로젝트 전반에서 주입받아 사용하도록 설정했습니다. BCrypt는 현재 산업 표준으로 널리 사용되는 강력한 해시 알고리즘입니다.
// PasswordEncoderConfig.java - Bean 등록
@Configuration
public class PasswordEncoderConfig {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

// UserService.java - 회원가입 시 암호화 적용
User user = User.builder()
        .username(request.getUsername())
        .password(passwordEncoder.encode(request.getPassword())) // 비밀번호를 암호화하여 저장
        .email(request.getEmail())
        .build();
userRepository.save(user);

✅ 로그인 기능 구현 및 API 추가

  • 회원가입 기능에 이어 로그인 기능을 구현했습니다. 사용자가 입력한 식별자(아이디 또는 이메일)와 비밀번호를 검증하여 인증을 처리합니다.

➕ 로그인 처리 로직

  1. LoginRequest (DTO): 로그인에 필요한 사용자 이름/이메일과 비밀번호를 담는 객체입니다.
  2. UserServiceauthenticate 메서드:
    • 전달받은 사용자 이름 또는 이메일로 DB에서 사용자를 조회합니다.
    • 사용자가 존재하지 않으면 예외를 발생시킵니다.
    • 사용자가 존재하면, PasswordEncoder.matches() 메서드를 사용하여 클라이언트가 보낸 평문 비밀번호DB에 저장된 암호화된 비밀번호를 비교합니다.
    • 검증에 성공하면 사용자 정보를 반환하고, 실패하면 비밀번호 불일치 예외를 발생시킵니다.
  3. AuthControllerlogin 메서드: LoginRequest를 받아 UserService에 인증을 위임하고, 성공 시 ApiResponse에 담아 응답합니다.

✅ 일관된 API 응답 형식 설계: ApiResponse

  • 클라이언트에게 일관되고 예측 가능한 응답을 제공하기 위해 공통 응답 래퍼(Wrapper) 클래스ApiResponse<T>를 도입했습니다. 이를 통해 성공/실패 여부, 메시지, 데이터를 표준화된 구조로 전달할 수 있습니다.
필드설명예시
code응답 상태를 나타내는 코드 (e.g., 200)200
message응답에 대한 설명 메시지"회원가입이 성공적으로 완료되었습니다."
data실제 응답 데이터 (Generic Type T)UserResponse 객체 또는 null
  • 이 구조를 통해 모든 API 응답이 동일한 형식을 갖게 되어 프론트엔드에서의 데이터 처리가 매우 용이해집니다.

✅ 전역 예외 처리: GlobalExceptionHandler

  • 애플리케이션 전역에서 발생하는 예외를 한 곳에서 중앙 관리하기 위해 @RestControllerAdvice 어노테이션을 사용한 GlobalExceptionHandler를 구현했습니다. 이를 통해 Controller마다 중복된 try-catch 블록을 작성할 필요가 없어지고 코드의 가독성과 유지보수성이 향상됩니다.

➕ 커스텀 예외와 에러 코드

  • BusinessException: 비즈니스 로직 상의 예외 상황(e.g., 사용자명 중복, 이메일 중복)을 나타내기 위한 커스텀 예외 클래스입니다.
  • ErrorCode (Enum): 애플리케이션에서 발생할 수 있는 모든 에러 상황을 HttpStatus, 코드, 메시지와 함께 열거형으로 정의합니다. 이를 통해 에러 관리가 체계적으로 이루어집니다.
  • ErrorResponse (DTO): 클라이언트에게 반환할 에러 응답의 형식을 정의한 DTO입니다.

➕ 유효성 검증(@Valid) 예외 처리

  • @Valid 어노테이션을 통과하지 못하면 MethodArgumentNotValidException이 발생합니다. GlobalExceptionHandler에서 이 예외를 처리하여, 어떤 필드가 어떤 규칙을 위반했는지 상세한 정보를 담은 ValidationError 객체를 ErrorResponse에 포함시켜 응답합니다.
  • @JsonInclude(JsonInclude.Include.NON_NULL): 응답 DTO를 JSON으로 변환할 때, 값이 null인 필드는 결과에서 제외시키는 어노테이션입니다. 이를 통해 불필요한 정보 없이 깔끔한 응답을 보낼 수 있습니다.
// GlobalExceptionHandler.java 예시
@RestControllerAdvice
public class GlobalExceptionHandler {

    // 비즈니스 예외 처리
    @ExceptionHandler(BusinessException.class)
    protected ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
        // ... ErrorCode를 기반으로 ErrorResponse 생성 및 반환
    }

    // 유효성 검증 예외 처리
    @ExceptionHandler(MethodArgumentNotValidException.class)
    protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        // ... BindingResult에서 필드별 에러 정보를 추출하여 ErrorResponse 생성 및 반환
    }
}

📌 요약

  • 회원가입/로그인의 기본 로직을 Controller-Service-DTO 패턴으로 구현했습니다.
  • Spring SecurityPasswordEncoder를 도입하여 사용자 비밀번호를 안전하게 암호화했습니다.
  • ApiResponse 클래스를 통해 모든 API 응답을 표준화하여 일관성을 확보했습니다.
  • @RestControllerAdvice를 사용한 GlobalExceptionHandler를 구현하여 예외 처리를 중앙 집중화했습니다.
  • BusinessExceptionErrorCode Enum을 활용하여 비즈니스 예외를 체계적으로 관리하고, @Valid 예외 처리로 사용자에게 친절한 에러 메시지를 제공할 수 있게 되었습니다.

0개의 댓글