TraceBoard 방명록 기능 개발 이슈 해결 과정
2025.07.23 (WED)
💡 TL;DR: TraceBoard 방명록 기능 개발 과정에서 발생한 중복 제출, API 호출 최적화, IP 기반 권한 관리, 보안 취약점 등의 주요 이슈들을 해결한 경험을 정리했습니다. React와 Spring Boot를 활용한 풀스택 개발에서 마주칠 수 있는 실제 문제점들과 해결 방안을 다룹니다.
🔍 해결한 주요 문제들
1. 중복 폼 제출로 인한 400 에러 해결
문제 상황: 방명록 제출 시 빠른 클릭으로 인한 중복 제출 발생
사용자가 제출 버튼을 연속으로 클릭하면 동일한 요청이 여러 번 서버로 전송되어 400 Bad Request 에러가 발생했습니다. 이는 사용자 경험을 크게 해치는 문제였죠.
해결 방법: 로딩 상태 관리 및 중복 제출 방지 로직 추가
// EntryForm.js - 중복 제출 방지
const [loading, setLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
if (loading) return; // 이미 제출 중이면 무시
setLoading(true);
try {
const response = await axios.post('/api/entry', { name, content });
// 성공 처리...
} catch (error) {
// 에러 처리...
} finally {
setLoading(false);
}
};
2. 중복 API 호출 문제 해결
문제 상황: EntryForm에서 API 호출 후 Guestbook에서 또다시 API 호출하는 구조
기존 설계에서는 EntryForm 컴포넌트에서 데이터를 서버에 저장한 후, 부모 컴포넌트인 Guestbook에서 다시 동일한 API를 호출하는 비효율적인 구조였습니다.
해결 방법: 데이터 흐름 단순화
// 기존: Guestbook.js에서 중복 API 호출
const addEntry = async (newEntry) => {
const response = await axios.post('/api/entry', newEntry); // 중복!
setEntries(prev => [...prev, response.data]);
};
// 개선: 단순한 상태 업데이트만
const addEntry = (savedEntry) => {
setEntries(prev => [...prev, savedEntry]);
};
이렇게 변경하여 불필요한 네트워크 요청을 제거하고 애플리케이션 성능을 개선했습니다.
3. IP 기반 삭제 기능 구현
요구사항: 자신이 작성한 글만 삭제할 수 있는 기능
방명록의 특성상 로그인 기능 없이도 사용자가 자신의 글을 관리할 수 있어야 했습니다. IP 주소를 활용한 간단하면서도 효과적인 권한 관리 시스템을 구현했습니다.
해결 방법:
- 백엔드에서 IP 비교를 통한 권한 검증
- 프론트엔드에서 삭제 버튼 및 확인 다이얼로그 추가
// EntryService.java - IP 기반 권한 검증
@Transactional
public boolean deleteEntry(Long id) {
String clientIp = getClientIp();
Optional<Entry> entryOptional = entryRepository.findById(id);
if (entryOptional.isEmpty()) return false;
Entry entry = entryOptional.get();
if (!clientIp.equals(entry.getIp())) {
return false; // 권한 없음
}
entryRepository.deleteById(id);
return true;
}
// EntryList.js - 삭제 버튼 구현
const handleDelete = async (entryId) => {
if (!window.confirm('정말로 삭제하시겠습니까?')) return;
try {
await axios.delete(`/api/entry/${entryId}`);
onDeleteEntry(entryId);
} catch (error) {
alert('삭제 권한이 없거나 오류가 발생했습니다.');
}
};
4. IP 주소 보안 취약점 해결
문제 상황: API 응답에서 사용자 IP 주소가 노출되는 보안 위험
개발 초기에는 디버깅 목적으로 IP 주소를 JSON 응답에 포함시켰는데, 이는 사용자 프라이버시 침해 가능성이 있는 심각한 보안 취약점이었습니다.
해결 방법: Jackson의 @JsonIgnore 어노테이션 활용
// Entry.java - IP 필드 숨김 처리
@JsonIgnore // JSON 응답에서 IP 주소를 숨김
@Column(length = 45)
private String ip;
@JsonIgnore // JSON 응답에서 IP getter를 숨김
public String getIp() {
return ip;
}
이를 통해 내부적으로는 권한 검증에 IP를 사용하면서도, 클라이언트에는 민감한 정보를 노출하지 않도록 했습니다.
🚀 배포 및 CI/CD 이슈 해결
GitHub Actions 배포 파이프라인 수정
문제: 서버에서 JAR 파일이 빌드되지 않아 구버전 코드 실행
GitHub Actions를 통해 코드는 정상적으로 배포되었지만, 서버에서 새로운 JAR 파일이 빌드되지 않아 이전 버전의 코드가 계속 실행되는 문제가 발생했습니다.
해결: 배포 스크립트에 서버 사이드 빌드 추가
# .github/workflows/deploy.yml
- name: Deploy to server
script: |
cd ~/project/devzip
git pull origin master
./gradlew clean build -x test # 서버에서 빌드 수행
docker compose down
docker compose build
docker compose up -d
📋 개발 과정에서 배운 점
1. 상태 관리의 중요성
React에서 중복 API 호출을 방지하는 올바른 상태 설계의 중요성을 깨달았습니다. 컴포넌트 간의 데이터 흐름을 명확히 정의하고, 불필요한 네트워크 요청을 최소화하는 것이 성능과 사용자 경험 모두에 큰 영향을 미칩니다.
2. 보안 고려사항
개발 초기부터 민감한 데이터(IP 등) 노출 방지를 고려해야 합니다. 디버깅을 위해 임시로 추가한 코드가 프로덕션까지 이어지지 않도록 주의가 필요합니다.
3. 사용자 경험
로딩 상태, 확인 다이얼로그 등 세심한 UX 처리가 사용자 만족도에 큰 영향을 미칩니다. 기술적 구현뿐만 아니라 사용자가 실제로 겪는 경험을 고려한 개발이 중요합니다.
4. 배포 자동화
CI/CD 파이프라인에서 빌드 과정이 누락되면 코드는 배포되었지만 실제로는 변경사항이 반영되지 않는 문제가 발생할 수 있습니다. 배포 스크립트의 각 단계를 명확히 정의하고 검증하는 것이 중요합니다.
🛠 기술 스택
- Backend: Spring Boot 3.3.1, JPA/Hibernate, MySQL
- Frontend: React 18, Axios
- Deployment: Docker Compose, GitHub Actions
- Security: IP-based authorization, Jackson @JsonIgnore
📈 성과 및 개선 사항
이번 개발을 통해 다음과 같은 성과를 얻을 수 있었습니다:
- 안정성 개선: 중복 제출 방지로 에러율 감소
- 성능 최적화: 불필요한 API 호출 제거로 네트워크 트래픽 감소
- 보안 강화: 민감 정보 노출 방지로 프라이버시 보호
- 사용자 경험 향상: 직관적인 삭제 기능과 확인 프로세스 구현