[일정관리JPA]주요 기능(인증,인가,예외처리)

2025. 4. 4. 15:39·프로젝트

https://computerreport.tistory.com/88

 

[일정관리JPA] API 명세 및 ERD작성

이전에는 JDBC 를 활용하여 따로 코드를 작성했지만 JPA 를 사용하면 더 간편하게 데이터베이스를 생성 가능하기에 이번 프로젝트에 활용해보았다. 개발 일정2025.03.26 ~ 2025.04.04프로젝트명일정 관

computerreport.tistory.com

 


 

API 와 ERD 작성을 완료하고 디렉토리 및 파일 분리를 하였다.

todoProject
├── src/
│   ├── TodoJpaApplication.java          # 애플리케이션의 진입점
│   │
│   ├── common                             # 공통 상수 및 유틸리티 클래스
│   │   └── Const.java
│   │
│   ├── config                             # 설정 관련 클래스
│   │   ├── PasswordEncoder.java           # 비밀번호 인코딩 관련 설정
│   │   └── WebConfig.java                 # 웹 관련 설정
│   │
│   ├── controller                         # REST API 엔드포인트
│   │   ├── LoginController.java           # 로그인 관련 API
│   │   ├── TodoController.java            # Todo 관련 API
│   │   └── UserController.java            # 사용자 관련 API
│   │
│   ├── dto                                # 데이터 전송 객체 (DTO)
│   │   ├── ErrorResponseDto.java          # 오류 응답 DTO
│   │   ├── LoginRequestDto.java           # 로그인 요청 DTO
│   │   ├── LoginResponseDto.java          # 로그인 응답 DTO
│   │   ├── TodoRequestDto.java            # Todo 요청 DTO
│   │   ├── TodoResponseDto.java           # Todo 응답 DTO
│   │   ├── UserRequestDto.java            # 사용자 요청 DTO
│   │   └── UserResponseDto.java           # 사용자 응답 DTO
│   │
│   ├── entity                             # 엔티티 클래스
│   │   ├── BaseEntity.java                # 기본 엔티티 클래스
│   │   ├── Todo.java                      # Todo 엔티티 클래스
│   │   └── User.java                      # 사용자 엔티티 클래스
│   │
│   ├── Exception                          # 사용자 정의 예외 클래스
│   │   ├── PasswordEqualsCheckException.java # 비밀번호 불일치 예외
│   │   └── TodoNotFindException.java      # Todo 항목 미발견 예외
│   │
│   ├── filter                             # 필터 클래스
│   │   └── LoginFilter.java               # 로그인 필터 클래스
│   │
│   ├── handler                            # 예외 처리 클래스
│   │   └── AllExceptionHandler.java       # 모든 예외를 처리하는 핸들러
│   │
│   ├── repository                         # 데이터베이스 작업을 처리하는 리포지토리
│   │   ├── TodoRepository.java            # Todo 리포지토리 인터페이스
│   │   └── UserRepository.java            # 사용자 리포지토리 인터페이스
│   │
│   └── service                            # 비즈니스 로직을 처리하는 서비스
│       ├── LoginService.java              # 로그인 서비스
│       ├── TodoService.java               # Todo 서비스
│       └── UserService.java               # 사용자 서비스
│
└── resources
    ├── application.properties              # 애플리케이션 설정 파일
    ├── static                              # 정적 자원 (CSS, JS, 이미지 등)
    └── templates                           # 템플릿 파일 (HTML 등)

test
└── java
    └── TodoJpaApplicationTests.java        # 애플리케이션 테스트 클래스

 

 

일정,유저의 CPUD 는 이전 프로젝트에서 다뤄봤기 때문에 이번엔 로그인 기능과 예외처리 기능을 중점으로 작성해볼려고 한다.

 

✅ 로그인

  • Cookie / Session 을 활용 
  • 필터를 활용하여 인증 처리(회원가입,로그인 요청은 인증제외)
  • 이메일과 비밀번호를 활용한 기능 구현

 

♐ LoginFilter

더보기
public class LoginFilter implements Filter {

    private static final String[] WHITE_LIST = {"/users/signup", "/logout", "/session-login", "/check"};

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();

        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // 화이트리스트에 없는 요청에 대해 로그인 체크
        if (!isWhiteList(requestURI)) {
            HttpSession session = httpRequest.getSession(false);
            if (session == null || session.getAttribute(Const.LOGIN_USER) == null) {
                httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                httpResponse.setContentType("application/json");
                httpResponse.setCharacterEncoding("UTF-8");
                httpResponse.getWriter().write("message : 로그인 해주세요");
                return;
            }
        }

        chain.doFilter(request, response);
    }

    /**
     * 요청 URI가 화이트리스트에 포함되어 있는지 확인합니다.
     *
     * @param requestURI 요청 URI
     * @return 화이트리스트에 포함되어 있으면 true, 그렇지 않으면 false
     */
    private boolean isWhiteList(String requestURI) {
        return PatternMatchUtils.simpleMatch(WHITE_LIST, requestURI);
    }
}

 

♐ WebConfig

더보기
@Configuration
public class WebConfig implements WebMvcConfigurer {

    /**
     * 로그인 필터를 등록하는 빈을 생성합니다.
     *
     * @return 필터 등록 빈
     */
    @Bean
    public FilterRegistrationBean<Filter> loginFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LoginFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.addUrlPatterns("/*");

        return filterRegistrationBean;
    }


}

 

♐ LoginRequest

더보기
@Data
public class LoginRequestDto {

    /**
     * 이메일.
     */
    @NotBlank(message = "이메일 입력은 필수 입니다.")
    @Email(message = "이메일 형식으로 입력하세요")
    private String email;

    /**
     * 비밀번호.
     */
    @Size(min = 8, message = "비밀번호는 최소 8글자 이상이여야 합니다")
    private String password;
}

 

 

♐ LoginController

더보기
@Validated
@RestController
@RequiredArgsConstructor
public class LoginController {

    private final LoginService loginService;
    private final UserService userService;

    /**
     * 사용자의 로그인 요청을 처리합니다.
     *
     * @param dto 로그인 요청 데이터
     * @param request HTTP 요청 객체
     * @return 로그인 응답 데이터와 함께 200 OK 상태 코드 또는 404 Not Found
     */

    @PostMapping("/session-login")
    public ResponseEntity<LoginResponseDto> login(@Valid @RequestBody LoginRequestDto dto, HttpServletRequest request) {
        LoginResponseDto loginResponseDto = loginService.login(dto.getEmail(), dto.getPassword());

        if (loginResponseDto.getId() == null) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }

        HttpSession session = request.getSession();
        UserResponseDto loginUser = userService.findById(loginResponseDto.getId());
        session.setAttribute(Const.LOGIN_USER, loginUser);

        return new ResponseEntity<>(loginResponseDto, HttpStatus.OK);
    }

    /**
     * 사용자의 로그아웃 요청을 처리합니다.
     *
     * @param request HTTP 요청 객체
     * @return 200 OK 상태 코드
     */

    @PostMapping("/session-logout")
    public ResponseEntity<Void> logout(HttpServletRequest request) {
        HttpSession session = request.getSession(false);

        if (session != null) {
            session.invalidate();
        }
        return new ResponseEntity<>(HttpStatus.OK);
    }

    /**
     * 이메일의 존재 여부를 확인하는 API입니다.
     *
     * @param email 확인할 이메일
     * @return 이메일의 존재 여부 결과와 함께 200 OK 상태 코드
     */

    @GetMapping("/check")
    public ResponseEntity<String> check(
            @NotBlank(message = "이메일 입력은 필수 입니다.")
            @Email
            @RequestParam String email) {
        String result = loginService.getEmail(email);

        return new ResponseEntity<>(result, HttpStatus.OK);
    }
}

 

 

♐ LoginService

더보기
@Service
@RequiredArgsConstructor
public class LoginService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    /**
     * 사용자를 로그인합니다.
     *
     * @param email 사용자 이메일
     * @param password 사용자 비밀번호
     * @return 로그인 응답 DTO
     * @throws PasswordEqualsCheckException 비밀번호가 일치하지 않을 때 발생
     */
    @Transactional
    public LoginResponseDto login(String email, String password) {
        User user = userRepository.findByEmailOrElseThrow(email);

        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new PasswordEqualsCheckException();
        }

        return new LoginResponseDto(user.getId());
    }

    /**
     * 주어진 이메일의 사용 가능 여부를 확인합니다.
     *
     * @param email 확인할 이메일
     * @return 사용 가능 여부 메시지
     */
    @Transactional
    public String getEmail(String email) {
        if (userRepository.hasSameEmail(email)) {
            return "이미 존재하는 이메일 입니다";
        } else {
            return "사용 가능한 이름 입니다";
        }
    }
}

 

 

✅ 예외처리 

  •  Validation 을 활용
  • 정해진 예외 뿐아니라 프로젝트를 분석한 후 커스텀 예외처리

 

간단한 프로젝트라 정해진 예외에서 대부분 처리가 가능해서 아래의 예제만 분석되었다.

 

♐ AllExceptionHandler

더보기
package org.example.todojpa.handler;

import jakarta.validation.ConstraintViolationException;
import org.example.todojpa.Exception.EmailFoundException;
import org.example.todojpa.Exception.PasswordEqualsCheckException;
import org.example.todojpa.Exception.NotFoundException;
import org.example.todojpa.dto.ErrorResponseDto;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.sql.SQLIntegrityConstraintViolationException;
import java.util.stream.Collectors;

@ControllerAdvice
public class AllExceptionHandler {

    /**
     * 유효성 검사 예외를 처리합니다.
     *
     * @param ex 처리할 예외
     * @return 오류 응답
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponseDto> handleValidationExceptions(MethodArgumentNotValidException ex) {
        String messages = ex.getBindingResult().getFieldErrors()
                .stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining(", "));

        return new ResponseEntity<>(new ErrorResponseDto("C001", messages, "BAD_REQUEST"), HttpStatus.BAD_REQUEST);
    }

    /**
     * 제약 조건 위반 예외를 처리합니다.
     *
     * @param ex 처리할 예외
     * @return 오류 응답
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<ErrorResponseDto> handleConstraintViolationException(ConstraintViolationException ex) {
        return new ResponseEntity<>(new ErrorResponseDto("C002", ex.getMessage(), "BAD_REQUEST"), HttpStatus.BAD_REQUEST);
    }

    /**
     * 지원하지 않는 HTTP 메서드 예외를 처리합니다.
     *
     * @param ex 처리할 예외
     * @return 오류 응답
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResponseEntity<ErrorResponseDto> handleDefaultHandlerExceptionResolver(HttpRequestMethodNotSupportedException ex) {
        return new ResponseEntity<>(new ErrorResponseDto("C003", "전송 형식에 오류가 있습니다", "METHOD_NOT_ALLOWED"), HttpStatus.METHOD_NOT_ALLOWED);
    }

    /**
     * 비밀번호 불일치 예외를 처리합니다.
     *
     * @param ex 처리할 예외
     * @return 오류 응답
     */
    @ExceptionHandler(PasswordEqualsCheckException.class)
    public ResponseEntity<ErrorResponseDto> handlePasswordEqualsCheckException(PasswordEqualsCheckException ex) {
        return new ResponseEntity<>(new ErrorResponseDto("C004", ex.getMessage(), "BAD_PASSWORD"), HttpStatus.NOT_ACCEPTABLE);
    }

    /**
     * SQL 무결성 제약 위반 예외를 처리합니다.
     *
     * @param ex 처리할 예외
     * @return 오류 응답
     */
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public ResponseEntity<ErrorResponseDto> handleSQLIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException ex) {
        return new ResponseEntity<>(new ErrorResponseDto("C005", "이미 해당 이메일이 존재합니다", "BAD_REQUEST"), HttpStatus.BAD_REQUEST);
    }

    /**
     * 객체를 찾을 수 없는 예외를 처리합니다.
     *
     * @param ex 처리할 예외
     * @return 오류 응답
     */
    @ExceptionHandler(NotFoundException.class)
    public ResponseEntity<ErrorResponseDto> handleTodoNotFindException(NotFoundException ex) {
        return new ResponseEntity<>(new ErrorResponseDto("C006", ex.getMessage(), "NOT_FOUND"), HttpStatus.NOT_FOUND);
    }

    /**
     * 이메일을 찾을 수 없는 예외를 처리합니다.
     *
     * @param ex 처리할 예외
     * @return 오류 응답
     */
    @ExceptionHandler(EmailFoundException.class)
    public ResponseEntity<ErrorResponseDto> handleEmailFoundException(EmailFoundException ex) {
        return new ResponseEntity<>(new ErrorResponseDto("C007", ex.getMessage(), "FOUND"), HttpStatus.IM_USED);
    }
}

 

 

♐ TodoRequestDto

더보기
@Data
public class TodoRequestDto {

    /**
     * 제목.
     */
    @Size(min = 2, message = "두글자 이상 입력하세요")
    private final String title;

    /**
     * 내용.
     */
    @NotBlank(message = "내용을 입력하세요")
    private final String contents;
}

 

 

♐ UserRequestDto

더보기
@Data
public class UserRequestDto {

    /**
     * 사용자 이름.
     */
    @NotBlank(message = "이름을 입력하세요")
    private final String userName;

    /**
     * 이메일.
     */
    @NotBlank(message = "이메일을 입력하세요")
    @Email(message = "이메일 형식으로 입력하세요")
    private final String email;

    /**
     * 비밀번호.
     */
    @Size(min = 8, message = "비밀번호는 최소 8글자 이상이여야 합니다")
    private final String password;

}

 

 

♐ TodoController

더보기
/**
 * 새로운 Todo 항목을 저장하는 API입니다.
 *
 * @param todoRequestDto Todo 요청 데이터
 * @param request HTTP 요청 객체
 * @return 생성된 Todo의 응답 데이터와 함께 201 Created 상태 코드
 */
@PostMapping
public ResponseEntity<TodoResponseDto> save(@Valid @RequestBody TodoRequestDto todoRequestDto, HttpServletRequest request) {

    HttpSession session = request.getSession(false);
    UserResponseDto loginUser = (UserResponseDto) session.getAttribute(Const.LOGIN_USER);
    TodoResponseDto save = todoService.save(loginUser.getEmail(), todoRequestDto.getTitle(), todoRequestDto.getContents());

    return new ResponseEntity<>(save, HttpStatus.CREATED);
}

 

/**
 * Todo 항목을 수정하는 API입니다.
 *
 * @param id Todo 항목의 ID
 * @param todoRequestDto 수정할 Todo 데이터
 * @return 수정된 Todo의 응답 데이터와 함께 200 OK 상태 코드
 */
@PatchMapping("/{id}")
public ResponseEntity<TodoResponseDto> updateTodo(@Valid @PathVariable Long id, @RequestBody TodoRequestDto todoRequestDto) {

    TodoResponseDto updateTodo = todoService.updateTodo(id, todoRequestDto.getTitle(), todoRequestDto.getContents());

    return new ResponseEntity<>(updateTodo, HttpStatus.OK);
}

 

 

♐ UserController

더보기
/**
 * 새로운 사용자를 등록하는 API입니다.
 *
 * @param userRequestDto 사용자 요청 데이터
 * @return 생성된 사용자의 응답 데이터와 함께 201 Created 상태 코드
 */
@PostMapping("/signup")
public ResponseEntity<UserResponseDto> save(@Valid @RequestBody UserRequestDto userRequestDto) {

    UserResponseDto userResponseDto = userService.save(userRequestDto.getUserName(), userRequestDto.getEmail(), userRequestDto.getPassword());

    return new ResponseEntity<>(userResponseDto, HttpStatus.CREATED);
}

 

/**
 * 사용자 정보를 수정하는 API입니다.
 *
 * @param id 사용자 ID
 * @param userRequestDto 수정할 사용자 데이터
 * @return 수정된 사용자의 응답 데이터와 함께 200 OK 상태 코드
 */
@PatchMapping("/{id}")
public ResponseEntity<UserResponseDto> updateUser(@Valid @PathVariable Long id, @RequestBody UserRequestDto userRequestDto) {

    UserResponseDto userResponseDto = userService.updateUser(id, userRequestDto.getUserName(), userRequestDto.getEmail());

    return new ResponseEntity<>(userResponseDto, HttpStatus.OK);

}

 

 

Login Filter 예외처리시 오류가 발생하여 트러블 슈팅을 작성하였다.

https://computerreport.tistory.com/86

 

[Spring]Filter 예외처리

intro프로젝트를 진행하며 발생한 문제 상황과 해결 과정들을 상세히 기록하고 추후에 같은 문제가 발생 했을때 빠르게 문제 해결하기 위해 트러블 슈팅을 정리할려고 한다.기록하는 습관을 기

computerreport.tistory.com

 

 

'프로젝트' 카테고리의 다른 글

[일정관리JPA] API 명세 및 ERD작성  (0) 2025.04.04
'프로젝트' 카테고리의 다른 글
  • [일정관리JPA] API 명세 및 ERD작성
코딩로봇
코딩로봇
금융 IT 개발자
  • 코딩로봇
    쟈니의 일지
    코딩로봇
  • 전체
    오늘
    어제
    • 분류 전체보기 (151)
      • JavaScript (8)
      • SQL (11)
      • 코딩테스트 (30)
        • Java (15)
        • SQL (13)
      • Java (10)
      • 프로젝트 (30)
        • 트러블슈팅 (10)
        • 프로젝트 회고 (18)
      • git,Github (2)
      • TIL (38)
      • Spring (20)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    스파르타 코딩 #부트캠프 #첫ot
    java #arraylist #list #배열
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
코딩로봇
[일정관리JPA]주요 기능(인증,인가,예외처리)
상단으로

티스토리툴바