프로젝트/트러블 슈팅

Spring 팀 프로젝트 Burrrrng 트러블 슈팅

으녕오리 2024. 12. 7. 00:10

Github 링크

Spring Boot 와 JPA 를 활용하여 배달 어플을 만드는 부르릉(Burrrrng) 이라는 프로젝트를 진행했다.
유저 삭제를 설계하는 과정에서 다음과 같은 문제가 발생했다.

문제상황

유저를 삭제하기 위해 보안상 비밀번호를 확인 후 삭제를 진행하고자 했으나,
비밀번호 확인 기능을 구현하는 과정에서 다음과 같은 문제점이 발생했다.

  • DELETE method에서 body 사용은 적절치 않다.
  • @PathVariable 혹은 query로 비밀번호를 전달하게 되면 url에 바로 비밀번호가 노출되어 보안상 좋지 않다.

해결 방안

SESSIONCOOKIE를 사용하여 구현하는 방식으로 해결을 했다.
비밀번호 확인 결과를 SESSION의 Const.PASSWORD_CHECK로 저장했다.

UserController

@PostMapping("/{id}/password-check")
    public ResponseEntity<CommonNoContentResDto> checkPassword(
            @PathVariable Long id,
            @Valid @RequestBody PasswordCheckRequestDto passwordCheckRequestDto,
            @SessionAttribute(name = Const.LOGIN_USER) User loginUser,
            HttpServletRequest request
    ) {

        userService.checkPassword(id, passwordCheckRequestDto.getPassword(), loginUser);

        HttpSession session = request.getSession();
        session.setAttribute(Const.PASSWORD_CHECK, Boolean.TRUE);

        return ResponseEntity.status(HttpStatus.OK).body(new CommonNoContentResDto("비밀번호 일치 확인"));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<CommonNoContentResDto> deleteUser(
            @PathVariable Long id,
            @SessionAttribute(name = Const.LOGIN_USER) User loginUser,
            @SessionAttribute(name = Const.PASSWORD_CHECK, required = false) Boolean isChecked,
            HttpServletRequest request
    ) {

        if (isChecked == null) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "비밀번호를 확인해주세요.");
        }

        if (!loginUser.getId().equals(id)) {
            throw new ResponseStatusException(HttpStatus.FORBIDDEN, "본인만 삭제할 수 있습니다.");
        }

        userService.deleteUser(id, loginUser);

        logout(request);

        return ResponseEntity.status(HttpStatus.OK).body(new CommonNoContentResDto("정상적으로 삭제되었습니다."));
    }

UserService

public void checkPassword(Long id, String inputPassword, User loginUser) {

        if (!id.equals(loginUser.getId())) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "본인만 확인 가능합니다.");
        }

        User user = userRepository.findByIdOrElseThrow(id);

        PasswordEncoder passwordEncoder = new PasswordEncoder();

        if (!passwordEncoder.matches(inputPassword, user.getPassword())) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다.");
        }
    }

    @Transactional
    public void deleteUser(Long id, User loginUser) {

        if (!id.equals(loginUser.getId())) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "본인만 삭제 가능합니다.");
        }

        User user = userRepository.findByIdOrElseThrow(id);

        if (user.getDeletedAt() != null) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "이미 탈퇴된 사용자입니다.");
        }

        userRepository.delete(user);
    }

UserController 에서 요청 받기
UserService 에서 비밀번호 일치 여부, loginUser 와 id 일치 여부 확인
UserController 에서 session 에 저장 getSession, setAttribute
return ResponseEntity

결과

UserService에서 비밀번호와 사용자 일치

  1. User 삭제 요청

비밀번호 확인이 된 session 정보가 없어서 errorr가 발생하였다.

  1. 비밀번호 확인 요청

쿠키에 비밀번호 일치 여부 값이 들어있는 sessionId가 들어있는 것을 확인할 수 있다.

  1. 해당 쿠키를 포함해 삭제 요청 보내기

정상적으로 삭제된 것을 확인할 수있다.

  1. DB의 deleted_at 값 확인

User 테이블

Store 테이블

Menu 테이블

User, Store, Menu에 deleted_at 값이 저장된 것을 확인할 수 있다.
softDelete와 영속성 전이를 이용한 연관 데이터 삭제까지 완료되었다.

다른 방법 제시

  • JWT를 활용
    JWT를 활용하여 비밀번호 확인 상태를 저장한다.