T_era

JdbcTemplate.query에서 sql의 컬럼을 바인딩할 수 없는 이유 본문

Programing/Spring

JdbcTemplate.query에서 sql의 컬럼을 바인딩할 수 없는 이유

블스뜸 2025. 5. 13. 15:40

프로젝트를 작성하면서 sql을 사용할 때 두 sql의 결과가 다르게 나온다

1. String sql = "SELECT " + attributeName + " FROM user WHERE user_id = ?", userId;
2. String sql = "SELECT ? FROM user WHERE user_id = ?", attributeName, Key;

위의 코드 중 1번만 제대로 작동한다
물론 둘다 좋은 예가 아니다
왜그럴까? 


문제점:

  1. SQL Injection 위험:
    • attributeName 변수는 외부로부터 입력받거나 동적으로 생성될 수 있습니다.
    • 만약 attributeName에 악의적인 SQL 코드가 포함된다면, 쿼리 실행 시 해당 코드가 그대로 실행되어 데이터베이스가 손상되거나 정보가 유출될 수 있다.
    • 예를 들어, attributeName이 "user_password FROM user; DELETE FROM user;"와 같은 값이라면, 의도하지 않은 SQL 명령이 실행될 수 있다.
  2. PreparedStatement의 이점 상실:
    • JdbcTemplate의 query() 메서드에서 두 번째 인자로 (rs, rowNum) -> rs.getString(attributeName) 익명 함수를 사용하고, 그 이후에 바인딩할 파라미터로 attributeName과 userId를 전달하고 있다.
    • 하지만 SQL 구문 자체에서 attributeName을 문자열 결합(+)으로 직접 삽입했기 때문에, JdbcTemplate이 제공하는 PreparedStatement의 파라미터 바인딩 기능을 활용하지 못하고 있다.
    • PreparedStatement는 SQL 구문을 미리 컴파일하여 SQL Injection 공격을 방지하고, 동일한 형태의 쿼리를 반복 실행할 때 성능 향상을 가져다준다.

올바른 작성 방식:

컬럼 이름을 동적으로 지정해야 하는 경우에는 일반적으로 다음과 같은 방식을 고려해야 합니다.

  1. 허용된 컬럼 목록 관리:
    • 애플리케이션에서 접근해야 하는 컬럼 이름들의 화이트리스트를 관리합니다.
    • 외부에서 제공된 attributeName이 이 화이트리스트에 존재하는지 확인한 후 SQL 구문을 동적으로 생성한다.
  2. PreparedStatement 파라미터 바인딩 (컬럼 이름은 불가):
    • PreparedStatement는 데이터 값을 바인딩하는 데 사용됩니다. 컬럼 이름, 테이블 이름과 같은 SQL 식별자는 파라미터로 바인딩할 수 없다.

수정 예시 (화이트리스트 기반):

List<Object> result = null;
String sql = "";
String safeAttributeName = attributeName; // 일단 변수에 할당

List<String> allowedAttributes = Arrays.asList("username", "email", "phone_number"); // 허용된 컬럼 목록

if (allowedAttributes.contains(attributeName)) {
    sql = "SELECT " + safeAttributeName + " FROM user WHERE user_id = ?";
    result = jdbcTemplate.query(
            sql,
            (rs, rowNum) -> rs.getString(safeAttributeName),
            userId
    );
} else {
    // 허용되지 않은 컬럼 이름에 대한 처리 (예: 예외 발생, 기본값 사용 등)
    System.err.println("허용되지 않은 컬럼 이름: " + attributeName);
    // 또는 throw new IllegalArgumentException("허용되지 않은 컬럼 이름: " + attributeName);
    result = Collections.emptyList(); // 예시로 빈 리스트 반환
}

요약:

  • SQL 구문에서 컬럼 이름을 직접 문자열 결합하는 것은 SQL Injection에 매우 취약하다.
  • JdbcTemplate의 파라미터 바인딩은 데이터 값에만 적용 가능하며, 컬럼 이름과 같은 식별자에는 사용할 수 없다.
  • 컬럼 이름을 동적으로 처리해야 할 경우에는 **허용된 목록(화이트리스트)**을 관리하여 안전하게 SQL 구문을 생성해야 한다.

따라서,  "SELECT ? FROM user WHERE user_id = ?" 형태로는 컬럼 이름을 파라미터로 바인딩할 수 없으며, 이는 SQL Injection 위험을 초래하는 잘못된 방식이다. 컬럼 이름을 동적으로 지정해야 한다면, 위에서 제시된 화이트리스트 기반의 접근 방식을 고려해야 한다.