판다라이브 보안 점검 기능 개선
목차
판다라이브 보안 점검 기능은 관리자나 운영팀이 특정 상태·위험 요소를 점검하는 흐름을 포함한다. 그런 기능일수록 입력값이 조금만 이상해도 조용히 잘못 처리되거나, 예외가 안 잡혀 화면이 깨지거나, 트랜잭션 경계가 흐릿해서 데이터가 반만 저장되는 일이 생긴다. 이번 작업은 그 부분들을 정리한 것이다.
수정 대상은 내부 클래스였다. Spring MVC 구조에서 하나의 Controller(또는 그에 준하는 클래스) 안에 static inner class로 요청 VO, 응답 VO를 정의하거나, 특정 기능 단위로 로직을 클래스 내부에 분리해두는 패턴이 있다. 규모가 크지 않은 기능은 파일을 잘게 쪼개는 것보다 이게 관리하기 편한 경우도 있다. 다만 내부 클래스가 커지면 해당 파일이 빠르게 비대해지고, 비즈니스 로직과 요청 처리가 뒤섞인다. 이번 작업에서도 그 구조를 유지하면서 안에서 처리되는 로직을 정리했다.
파라미터 검증과 트랜잭션 경계
요청 파라미터 바인딩은 예외 케이스가 생각보다 많다. 특히 보안 점검 기능이면 숫자여야 하는 값에 빈 문자열이 들어오거나, 고정된 코드값인데 예상 외 값이 오는 경우를 방어해야 한다. 이번에는 이런 케이스들을 명시적으로 처리하는 방향으로 정리했다.
// 파라미터 검증 — null/빈 값을 조기에 차단
if (param.getTargetId() == null || param.getTargetId().isBlank()) {
return ResponseEntity.badRequest().body("targetId is required");
}
트랜잭션 범위도 이번에 손봤다. DB를 건드리는 로직에 @Transactional을 달아두는 건 기본인데, 문제는 범위가 너무 넓거나 너무 좁을 때다. 너무 넓으면 읽기 전용 쿼리까지 트랜잭션을 타면서 락이 길어지고, 너무 좁으면 여러 INSERT/UPDATE가 각각 독립 트랜잭션으로 묶여 일부만 반영되는 상황이 생긴다. 보안 점검 특성상 상태를 변경하는 로직이 포함되면 이 경계를 명확히 해야 한다.
@Transactional(rollbackFor = Exception.class)
public void processAssessment(AssessmentParam param) {
mapper.insertAssessmentLog(param);
mapper.updateTargetStatus(param);
}
rollbackFor = Exception.class는 습관적으로 달게 되는데, checked exception까지 롤백 대상에 포함시키려면 명시해야 한다. Spring의 @Transactional 기본값은 RuntimeException과 Error만 롤백하기 때문이다. 이번 작업에서도 이 부분을 확인하고 정리했다. 기존 코드에 어노테이션 범위가 불명확한 메서드가 있었고, 그냥 두면 나중에 부분 커밋 문제가 생길 여지가 있었다.
MyBatis 쪽은 SQL XML에서 쿼리를 관리하기 때문에 resultMap이 잘못 매핑되거나 컬럼명이 바뀌면 런타임에서야 터진다. 이번에 결과 매핑도 같이 확인했다. 컬럼 alias를 명시하거나 resultMap을 따로 정의하는 게 유지보수에 유리하다. SELECT * 로 가져와서 Java 필드명이랑 자동 매핑되길 기대하면, 컬럼이 하나 추가/변경됐을 때 추적하기 까다로워진다.
예외 처리 전략 선택과 로그
RuntimeException을 그대로 올려보낼 것인가, 잡아서 응답으로 변환할 것인가. 기능마다 다르게 결정해야 하는 문제다.
| 상황 | 선택 |
|---|---|
| 공통 ExceptionHandler가 있고, 일반 오류 | 전파 (throw) |
| 화면에 구체적 에러 메시지 필요 | 잡아서 응답 변환 |
| 외부 호출 실패 등 복구 가능한 케이스 | 잡아서 fallback 처리 |
공통 ExceptionHandler가 있다면 각 메서드에서 try-catch로 잡는 것보다 전파 쪽이 낫다. 처리 방식이 메서드마다 달라지면 나중에 오류 응답 스펙이 제각각이 되고, 코드도 길어진다. 반면 특정 예외는 화면에 구체적인 안내를 줘야 할 때 잡아서 변환하는 게 맞다. 이번 작업에서는 점검 결과 조회 쪽은 전파로 두고, 상태 변경 로직에서 특정 비즈니스 예외는 잡아서 응답 코드로 내려주는 방식을 택했다.
로그도 챙겼다. 운영 환경에서 이상 동작이 생기면 로그가 전부인데, 입출력값을 INFO로 남기지 않으면 "왜 이 요청이 이렇게 처리됐지?"를 나중에 추적하기 어렵다. 보안 관련 기능이면 특히 어떤 요청이 들어왔고, 어떤 판단이 이루어졌는지 흔적이 있어야 한다.
log.info("[assessment] targetId={}, status={}, result={}",
param.getTargetId(), param.getStatus(), result);
단, 민감한 값은 로그에 찍으면 안 된다. 점검 대상 식별자 정도만 남기고 세부 내용은 마스킹하거나 생략하는 게 맞다.
JSP가 뷰 레이어를 담당하는 구조에서 화면 렌더링 쪽 변경이 같이 들어갈 때는, 서버 응답 구조가 바뀌면 JSP 쪽도 함께 확인해야 한다. 서버에서 내려주는 모델 키 이름이 바뀌면 JSP에서 EL 표현식으로 꺼낼 때 조용히 빈 값이 나온다. 화면에 아무것도 안 뜨는데 서버 로그에는 오류가 없는 케이스가 이런 경우다.
배포는 로컬 기동 확인 → 개발 DB 반영 → 배포 순서로 처리했다. 작업 규모가 크지 않더라도 이 순서는 지키는 게 낫다. 개발 DB에서 SQL이 실제로 의도대로 동작하는지 확인 없이 바로 배포하면 쿼리 오류로 기능이 먹통 되는 경우가 있다. MyBatis XML은 컴파일 타임에 오류가 안 잡히기 때문에 실행 전 확인이 특히 중요하다.
기존 로직과의 호환성도 챙겼다. 내부 클래스 수정은 범위가 좁아 보이지만, 같은 파일 안에서 공유 메서드나 멤버 변수를 참조하고 있으면 예상치 못한 곳에 영향이 생긴다. 수정한 로직이 다른 흐름에 영향 없는지 호출 경로를 따라 추적하고, 리뷰 때 그 부분을 명시적으로 짚었다.
작업 자체는 크지 않았다. 그래도 보안 점검 기능은 로직보다 "이 흐름이 정말 안전한가"를 따지는 시간이 더 걸린다. 트랜잭션, 예외, 로그, 파라미터 방어까지 네 가지를 한 묶음으로 같이 챙기는 게 나중에 운영에서 덜 고생한다.
댓글 0
첫 댓글 달아줘.