본문 바로가기
시큐어코딩/JAVA

[시큐어코딩] Java에서 NullPointerException 방지하는 안전한 코딩 전략

by ICT리더 리치 2025. 4. 9.

NullPointerException은 Java 개발에서 가장 흔하고도 위험한 오류 중 하나입니다. 단순한 예외로 끝나지 않고, 보안 취약점으로 이어질 수 있다는 점에서 실무 개발자와 보안 담당자 모두가 반드시 주의해야 할 항목입니다.

이번 포스팅에서는 널포인터 역참조 취약점의 원인과 발생 구조를 분석하고, 실제 Spring Boot 프로젝트에서 안전하게 대응하는 시큐어코딩 기법을 소개합니다. 취약한 코드 예제부터 안전한 코드 구성, 실무 체크리스트까지 실전 중심으로 정리해드립니다.

1. 널포인터 역참조란 무엇인가?

널포인터 역참조(Null Pointer Dereference)는 null인 객체에 접근하거나 메서드를 호출할 때 발생하는 오류입니다. 이 문제는 단순한 런타임 오류로 보이지만, 사용자 데이터 누락, 인증 우회, 예외 누락에 따른 보안 허점으로 이어질 수 있습니다.

특히 Spring, JPA, JSP 등에서는 의도치 않은 null 객체 전달로 인해 컨트롤러에서 NullPointerException이 발생하고, 서버 오류 페이지가 노출되거나 정상 로직이 동작하지 않아 서비스 장애로 연결될 수 있습니다.

2. 실무에서 자주 발생하는 취약한 코드

아래 예제는 null 여부 검사를 하지 않고 객체의 메서드에 접근하는 전형적인 코드 패턴입니다. 서비스 로직 중 중간에 null이 들어오면 바로 예외가 발생하며, 예외처리 없는 경우 전체 요청이 실패합니다.

// 사용자 이름을 반환하는 간단한 메서드
public class UserService {

    public String getUserName(User user) {
        return user.getName(); // user가 null이면 예외 발생
    }

    public void printUserName(User user) {
        System.out.println("User name: " + getUserName(user));
    }

    public void process() {
        User user = null; // 외부에서 null 전달된 상황 가정
        printUserName(user); // NPE 발생
    }

    public static void main(String[] args) {
        new UserService().process();
    }
}

이 코드는 user 객체가 null일 경우, 메서드 호출 자체가 불가능해져 서버 로그에 NullPointerException 예외가 발생합니다. 보안 로그에 민감 정보가 출력될 수도 있으며, 서비스 흐름이 비정상 종료될 수 있습니다.

3. 안전한 코딩 패턴 (Spring 기반 예제)

Java에서는 명확한 null 체크와 Optional 활용을 통해 NullPointerException을 예방할 수 있습니다. 아래 코드는 Optional을 사용해 null 객체의 접근을 안전하게 처리하며, null일 경우 사용자에게 적절한 기본 메시지를 반환합니다.

// 안전한 null 처리 구조
public class UserService {

    public static class User {
        private String name;
        public User(String name) { this.name = name; }
        public String getName() { return name; }
    }

    public Optional<String> getUserName(User user) {
        return Optional.ofNullable(user)
                       .map(User::getName)
                       .filter(name -> !name.isBlank());
    }

    public void printUserName(User user) {
        String displayName = getUserName(user)
                .orElse("이름 정보 없음");

        System.out.println("User name: " + displayName);
    }

    public void process(User user) {
        printUserName(user);
    }

    public static void main(String[] args) {
        User user1 = new User("Alice");
        User user2 = null;
        User user3 = new User("");

        UserService service = new UserService();
        service.process(user1);
        service.process(user2);
        service.process(user3);
    }
}

위 코드는 user가 null인 경우에도 예외를 발생시키지 않고, 기본 메시지 "이름 정보 없음"을 반환하여 흐름을 유지합니다. 보안상 민감한 예외 메시지를 숨기고, 사용자 경험을 보호하는 시큐어코딩의 대표 사례입니다.

 

실무에서는 다음의 추가 전략도 고려할 수 있습니다:

  • Controller에서 DTO 매핑 시 null-safe 처리
  • JPA Entity 조회 시 Optional user = repository.findById(id)
  • 서비스 로직에서 assertNotNull 대신 Optional 사용

4. 실무 Q&A

Q. Optional을 너무 많이 쓰면 오히려 복잡하지 않나요?

Optional은 null 체크를 더 명확하게 하기 위한 수단입니다. 다만 DTO나 내부 필드에서는 과도하게 사용하면 가독성이 떨어질 수 있으므로, Service나 Controller 레이어의 입력값 처리에 국한해 사용하는 것이 좋습니다.

Q. null 체크를 전역적으로 처리할 수 있는 방법은 없나요?

Spring에서는 @ControllerAdviceExceptionHandler를 활용해 NullPointerException이 발생했을 때 통합된 에러 응답을 제공할 수 있습니다. 하지만 근본적으로는 사전에 방지하는 코드 패턴이 가장 중요합니다.

Q. JPA에서 Entity 조회 시 null 문제가 자주 발생하는데 해결 방법이 있을까요?

findById()는 Optional을 반환하기 때문에, orElseThrow() 또는 orElse(null)로 명시적 분기 처리가 필요합니다. 이 과정을 생략하면 NullPointerException이 언제든지 발생할 수 있습니다.

5. 마무리 요약

NullPointerException은 단순한 버그처럼 보일 수 있지만, 보안상 민감한 정보 유출, 예외 흐름 누락, 서비스 장애로 이어질 수 있는 중대한 이슈입니다. 이 포스팅에서 소개한 Optional 활용, null-safe 분기 처리는 실무 보안 코드 리뷰에서도 반드시 체크되어야 할 항목입니다.

  • Optional.ofNullable() 활용하여 null 안전성 확보
  • 필드 초기화 시 기본값 제공
  • Service, Controller에서 null 체크 분기 로직 명확화
  • JPA 조회 시 Optional 기반 예외 분기 처리
  • ExceptionHandler로 전역 예외 응답 처리 구성
반응형