📝느낀 점
이번 회원 탈퇴 기능 구현 및 논리적 삭제 처리 과정에서 발생한 문제와 그 해결 과정을 통해 RESTful API 설계의 중요성과 Spring Security 활용의 중요성을 배웠습니다. 특히 논리적 삭제의 복잡성을 이해하고, 데이터베이스의 유니크 제약 조건을 유지하면서 논리적 삭제를 처리하는 방법을 익히는 좋은 기회가 되었습니다. 또한, 클라이언트와 서버 간의 데이터 전송 방식에서 발생할 수 있는 문제를 예측하고, 이를 해결할 수 있는 방안을 마련하는 것이 중요하다는 것을 깨달았습니다. RESTful API 설계에서는 각 HTTP 메서드의 용도를 정확히 이해해서 적용해야 하며, 비밀번호와 같은 민감한 정보를 안전하게 처리하는 방법을 고민하면서, 비동기 처리와 @RequestBody의 활용 등 다양한 접근 방식을 고려하게 되었습니다. 또한, Spring Data JPA와 @SQLDelete 및 @Where 어노테이션을 활용해 논리적 삭제를 구현하면서 데이터베이스의 유니크 제약 조건을 유지할 수 있었습니다. 앞으로도 이러한 경험을 바탕으로 더욱 안전한 웹 애플리케이션을 개발하겠습니다.
어떻게 했기에 문제 상황을 마주하게 되었는지
회원 탈퇴 기능을 구현하는 과정에서 RESTful API 설계 원칙을 따르고자
@DeleteMapping을 사용하여 회원 탈퇴 요청을 처리했습니다.
클라이언트 측에서 비밀번호를 포함한 데이터를 DELETE 메소드로
서버에 전송하려 했으나, 이로 인해 문제가 발생했습니다.
DELETE 메소드로 전송된 비밀번호가 서버에서 제대로 인식되지 않았고, 데이터 전송 방식의 문제로 발생했습니다.
동시에, 논리적 삭제를 위해 데이터베이스에서 deleted 컬럼을 사용했지만,
email 컬럼의 유니크 제약 조건으로 인해 논리적으로 삭제된 회원의 이메일과
동일한 이메일로 새로운 회원을 가입시키려 할 때 예외가 발생했습니다.
이런 상황은 클라이언트와 서버 간의 데이터 전송 방식에서 발생한 문제와
데이터베이스의 제약 조건 처리에서의 문제로 나타났습니다.
이게 왜 문제인지
RESTful API 설계 원칙에서 DELETE 메소드는 주로 리소스 삭제를 요청할 때 사용되며,
일반적으로 URL 경로에 필요한 데이터를 포함시킵니다.
그러나 비밀번호와 같은 민감한 데이터를 URL에 포함시키는 것은 보안상 큰 위험을 초래할 수 있다고 생각했습니다.
URL에 포함된 비밀번호는 브라우저의 히스토리에 남을 수 있으며, 로그 파일에 기록될 가능성도 있어 안전하지 않습니다.
또한, 일부 서버 구현에서는 DELETE 요청의 본문을 처리하지 않거나,
쿼리 파라미터로 데이터를 받는 데 어려움을 겪을 수 있습니다.
이는 DELETE 메소드의 특성상 본문 데이터를 허용하지 않는 경우가 많아 발생하는 문제입니다.
또한, 논리적으로 삭제된 레코드도 데이터베이스의 유니크 제약 조건에
의해 중복 체크에서 제외되지 않아 예외가 발생할 수 있습니다.
이는 새로운 회원을 가입시키는 과정에서 예상치 못한 데이터베이스 충돌을 발생 시킬 수 있었습니다.
문제를 어떻게 감지했는지
클라이언트 측에서 DELETE 요청을 보낼 때 서버가 비밀번호를 제대로 인식하지 못하고,
요청이 생각한대로 처리되지 않는 것을 확인했습니다.
클라이언트가 서버에 비밀번호를 포함한 DELETE 요청을 보내지만,
서버는 이 요청을 올바르게 처리하지 못하여 비밀번호를 인식하지 못하는 문제가 발생했습니다.
또한, 비밀번호를 URL에 포함시키는 방식이 보안상 적절하지 않음을 알게 되었습니다.
이 문제는 보안상의 위험을 초래할 뿐만 아니라, RESTful API 설계 원칙에도 부합하지 않는 방식이었습니다.
회원 가입 시 "Duplicate entry" 예외 메시지를 통해 논리적으로 삭제된
레코드가 중복 체크에 포함되고 있음을 알게 되었습니다.
이는 데이터베이스 로그와 예외 메시지를 통해 확인할 수 있었으며,
삭제된 레코드가 여전히 유니크 제약 조건에 영향을 줄 수도 있는 증거였다고 생각했습니다.
이를 통해 논리적 삭제가 유니크 제약 조건을 제대로 처리하지 못하는 문제를 발견하게 되었습니다.
어떻게 해결했는지
먼저, 서버 측에서 @DeleteMapping과 @RequestParam을 함께 사용하는 것이 적절하지 않음을 확인하고,
비밀번호와 같은 민감한 데이터를 URL에 포함시키지 않도록 수정했습니다.
클라이언트에서는 DELETE 요청을 보낼 때 비밀번호를 요청 본문에 포함하여 전송하도록 변경했습니다.
@DeleteMapping
public ResponseEntity<MemberDeletedResponse> softDeleteMember(@Validated @RequestBody MemberDeleteRequest deleteRequest) {
MemberDeleteRequestDTO memberDeleteRequestDTO = MemberDeleteRequest.toMemberDeleteRequestCommand(deleteRequest);
boolean softDeleteMember = memberFacade.softDeleteMember(memberDeleteRequestDTO);
MemberDeletedResponse response = MemberDeletedResponse.toSoftDeleteMemberResponse(softDeleteMember);
return ResponseEntity.ok(response);
}
클라이언트 측에서는 AJAX 요청을 통해 DELETE 요청을 보내며, 비밀번호를 JSON 형식의 본문에 포함시켰습니다.
function deleteMember(event) {
event.preventDefault();
var password = $('#password').val();
$.ajax({
url: "/api/v1/member",
type: 'DELETE',
contentType: 'application/json',
data: JSON.stringify({password: password}),
headers: {
'Authorization': 'Bearer ' + localStorage.getItem("jwt")
},
success: function(memberDeleteResponse) {
if (memberDeleteResponse.success) {
alert(memberDeleteResponse.message);
localStorage.removeItem('jwt');
window.location.href = "/";
} else {
alert(memberDeleteResponse.message);
}
},
error: function(xhr, status, error) {
alert('회원 비밀번호가 일치하지 않습니다.');
}
});
}
논리적 삭제 문제 해결을 위해서는 엔티티 클래스에 @SQLDelete 및 @Where 어노테이션을 추가하고,
중복 체크 메서드를 수정하여 논리적으로 삭제된 레코드를 제외하고 중복을 확인하도록 했습니다.
@Entity
@Getter
@Builder
@EqualsAndHashCode(of = "id")
@AllArgsConstructor
@SQLDelete(sql = "update member set deleted = true where id = ?")
@Where(clause = "deleted = false")
public class Member extends AuditingFields implements Serializable, UserDetails {
@Column(name = "deleted")
private boolean deleted = Boolean.FALSE;
// Other fields and methods
}
중복 체크 메서드 수정했습니다.
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("SELECT COUNT(m) FROM Member m WHERE m.email = :email AND m.deleted = false")
Long countByEmailIgnoringDeleted(@Param("email") String email);
}
서비스 레이어에서는 중복 체크 로직을 추가하여 회원 가입 전에 중복을 확인하도록 했습니다.
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
public Member joinMember(MemberSaveRequestDTO memberDTO) {
checkForDuplicate(memberDTO);
Member memberEntity = Member.builder()
.name(memberDTO.getName())
.passwordHash(passwordEncoder.encode(memberDTO.getPassword()))
.email(memberDTO.getEmail())
.nickname(memberDTO.getNickname())
.role(memberDTO.getRole())
.build();
return memberRepository.save(memberEntity);
}
private void checkForDuplicate(MemberSaveRequestDTO memberDTO) {
if (memberRepository.existsByNicknameAndDeletedFalse(memberDTO.getNickname())) {
throw new DuplicateNicknameException("닉네임이 이미 사용 중입니다.");
}
if (memberRepository.countByEmailIgnoringDeleted(memberDTO.getEmail()) > 0) {
throw new DuplicateEmailException("중복된 이메일 주소입니다.");
}
}
}
이와 같은 방법으로 DELETE 요청을 통한 민감한 정보 전송 문제와
논리적 삭제로 인한 중복 체크 문제를 해결할 수 있었습니다.
그렇게 하면 왜 해결되는지
비밀번호와 같은 민감한 정보를 요청 본문에 포함시켜 전송하면,
서버 측에서 @RequestBody를 통해 데이터를 쉽게 받을 수 있습니다.
이는 URL에 민감한 정보를 포함시키는 것보다 보안상 안전합니다.
DELETE 요청의 본문을 사용하면 URL 길이 제한 문제도 피할 수 있습니다.
논리적 삭제의 경우, @SQLDelete 및 @Where 어노테이션을 사용하면
삭제된 레코드를 데이터베이스에서 효과적으로 필터링할 수 있습니다.
그렇기에, 삭제된 레코드가 중복 체크에 포함되지 않도록 하여
데이터베이스의 유니크 제약 조건 문제를 해결할 수 있습니다.
이러한 접근 방식은 보안성과 데이터 무결성을 동시에 높일 수 있습니다.
얼마나 개선되었는지
이 방법을 통해 클라이언트와 서버 간의 데이터 전송이 제대로 이루어졌으며, 요청이 성공적으로 처리되었습니다.
특히 보안상 위험도를 줄여 비밀번호 등의 민감한 정보를 안전하게 전송할 수 있게 되었습니다.
또한, 논리적으로 삭제된 레코드를 중복 체크에서 제외함으로써
데이터베이스의 유니크 제약 조건 문제를 해결할 수 있었습니다.
결과적으로 시스템의 안정성이 크게 향상되었다고 볼 수 있습니다.
배우는 것은 무엇인지
DELETE 메소드와 데이터 전송 방법에 대해 더 깊이 이해하게 되었습니다.
민감한 데이터를 URL에 포함시키는 것이 얼마나 위험한지 깨달았습니다.
논리적 삭제를 적용할 때 추가적인 로직과 복잡성을 이해하고, 이를 효과적으로 처리하는 방법을 배웠습니다.
Spring Data JPA의 기능을 활용하여 복잡한 데이터 처리 로직도 비교적 간단하게 구현할 수 있다는 것을 깨달았습니다.
또한, RESTful API 설계 원칙과 보안 고려사항을 학습하면서
더 안전하고 효율적인 웹 애플리케이션을 개발할 수 있는 능력을 키웠습니다.
무엇을 얻을 것인지
이번 문제 해결을 통해 더 효율적이고 유지보수성이 높은 코드를 작성하는 경험을 얻었습니다.
사용자가 민감한 정보를 안전하게 처리할 수 있도록 하여 사용자 신뢰도를 높일 수 있게 되었습니다.
Spring Security와 Hibernate의 다양한 기능을 활용하면서 보안과 성능을 동시에 고려한 시스템 설계 방법을 배웠습니다.
이러한 경험을 통해 앞으로의 프로젝트에서도 유사한 문제를 효과적으로 해결할 수 있을 것입니다.
또한, 지속적으로 학습하고 개선하여 더 나은 웹 애플리케이션을 개발하는 데 도움이 될 것입니다.
이 방법이 최선이었는지
현재 상황에서는 최선의 방법이었다고 생각합니다.
민감한 정보를 URL에 포함시키지 않고, 요청 본문에 포함시켜 전송하는 것이 보안상 더 안전한 접근 방식입니다.
또한, 논리적 삭제와 중복 체크 문제를 해결하기 위해 Hibernate의
@SQLDelete와 @Where 어노테이션을 사용하는 방법은
JPA의 기본 기능을 활용한 효율적인 방법이었습니다.
다른 방법은 없었는지
비밀번호 확인과 같은 민감한 데이터를 처리할 때 AJAX를 사용하여 비동기적으로 요청을 처리하거나,
PUT 메소드로 비밀번호를 전송하는 방법도 고려해볼 수 있습니다.
이러한 접근 방식은 RESTful 원칙을 따르면서도 보안성과 유연성을 제공할 수 있습니다.
'프로젝트(project)' 카테고리의 다른 글
HTTP 415 오류 해결 과정 (회원가입 + 로그인) (0) | 2024.07.28 |
---|---|
닉네임 중복 검증과 예외 처리 (0) | 2024.07.28 |
비밀번호 검증 및 데이터 무결성 유지 (0) | 2024.07.25 |
비밀번호 해싱과 Security로 보안 높이기 (0) | 2024.07.25 |
트랜잭션 및 외래키 제약 조건 문제 (0) | 2024.07.24 |