Spring Boot 와 JPA 를 활용하여 배달 어플을 만드는 부르릉(Burrrrng) 이라는 프로젝트를 진행했다.
유저 삭제를 설계하는 과정에서 다음과 같은 문제가 발생했다.
문제상황
유저를 삭제하기 위해 보안상 비밀번호를 확인 후 삭제를 진행하고자 했으나,
비밀번호 확인 기능을 구현하는 과정에서 다음과 같은 문제점이 발생했다.
- DELETE method에서 body 사용은 적절치 않다.
- @PathVariable 혹은 query로 비밀번호를 전달하게 되면 url에 바로 비밀번호가 노출되어 보안상 좋지 않다.
해결 방안
SESSION과 COOKIE를 사용하여 구현하는 방식으로 해결을 했다.
비밀번호 확인 결과를 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에서 비밀번호와 사용자 일치
- User 삭제 요청

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

쿠키에 비밀번호 일치 여부 값이 들어있는 sessionId가 들어있는 것을 확인할 수 있다.
- 해당 쿠키를 포함해 삭제 요청 보내기

정상적으로 삭제된 것을 확인할 수있다.
- DB의 deleted_at 값 확인
User 테이블

Store 테이블

Menu 테이블

User, Store, Menu에 deleted_at 값이 저장된 것을 확인할 수 있다.
softDelete와 영속성 전이를 이용한 연관 데이터 삭제까지 완료되었다.
다른 방법 제시
- JWT를 활용
JWT를 활용하여 비밀번호 확인 상태를 저장한다.