일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- Controller
- 코딩테스트
- 직무면접
- scikit-learn
- disjoint set
- union-find
- 음수가 포함된 최단경로
- 최단경로
- 유니온 파인드
- compiler
- Android Studio
- spring boot
- Python
- Django
- clean code
- BufferedReader
- 벨만 포드 알고리즘
- 기술면접
- 거쳐가는 정점
- 우선순위큐
- 플로이드 와샬
- onclick
- 엔테크서비스
- kmeans
- Java
- bottom-up
- 다익스트라
- top-down
- 동적계획법
- dto
- Today
- Total
춤추는 개발자
[Spring] Mybatis의 동적쿼리와 변수 본문
✅ Mybatis
Spring 웹 어플리케이션에서 쿼리 기반으로 데이터베이스에 접근하는 SQL Mapper 프레임워크
[ 🔑 간단 요약 ]
Mybatis의 쿼리가 작성되는 XML 문서에서 요청 파라미터와 동적 쿼리 기반으로 변수를 작성하는 2가지 케이스
쿼리가 작동하지않는 이슈를 접하고, 각 케이스를 비교
✅ 배경과 상황
새로운 API를 개발하고 있던 팀원에게 질문을 받았다.
똑같은 쿼리인데 WAS에서는 조회 결과가 안나오고, DB 툴에서는 결과가 나옵니다.
실제로 WAS에서는 조회 결과가 0이 나오고, DBeaver에서는 정확한 조회결과가 나왔다. 이런 경우는 처음 접해서 원인을 어떻게 찾아야할지 고민이었다. WAS와 DB간의 연동, 회사내 방화벽, 반환 타입 등 다각도로 보던 중 동적쿼리가 눈에 들어왔다.
기존에 작성된 쿼리는 아래 예시와 비슷하다.
SELECT *
FROM #{TABLE_NAME}
WHERE COLUMN1 = #{column1}
분명 Where절의 value는 컬럼명 = 'value'처럼 따옴표로 감싸서 변수값임을 표현해야 한다. 하지만, FROM절에서는 TABLE명을 그대로 작성해야 하는데 위처럼 똑같은 동적 쿼리인 #{}를 사용한다면 아래처럼 인식될 것이다.
FROM 'table_name'
✅ 해결방법 및 비교
혹시나 싶어 FROM절에 테이블명을 직접 작성하고 디버그해보니 정확한 조회 결과가 나왔다. 그렇다면 테이블명은 동적인 변수값을 줄 수 없는 걸까? 답은 아래와 같이 작성하면 된다.
SELECT *
FROM ${TABLE_NAME}
WHERE ${COLUMN_NAME} = #{column1}
'#' 대신에 '$'를 사용하면 테이블과 컬럼에도 동적인 변수를 적용할 수 있다. 단순히 두 가지 방식(#, $)은 동적 쿼리라는 공통점이 있지만, 실행 방식에 따른 차이점이 존재한다.
$ {} | # {} |
Statement 방식으로 파라미터가 출력되어서 전달된다. | PreparedStatement 방식으로 파싱된 쿼리가 출력된다. |
값에 '(따옴표)가 붙지 않는다. | 값에 '(따옴표)가 자동으로 붙는다. |
SQL Injection과 같은 보안 취약점이 존재한다. | 보안 차원에서 유리하다. |
테이블/컬럼명에 사용된다 | 요청 파라미터의 value에 사용된다. |
[ 실행 방식의 차이점 ]
PreparedStatement | Statement |
동적쿼리를 '?'로 바인딩 | 동적쿼리를 '값'으로 바인딩 |
쿼리를 캐싱하여 재활용 | 새로운 쿼리로 인식 |
두 실행 방식에 따라 작성된 쿼리문이 어떻게 파싱되는지 알아보자.
-- 실제 쿼리문
SELECT *
FROM ${TABLE_NAME}
WHERE COLUMN_NAME = #{column1}
-- 파싱되어 오라클로 전달된 쿼리문
SELECT *
FROM TABLE_NAME
WHERE COLUMN_NAME = ?
-- 실제 동작 쿼리문
SELECT *
FROM TABLE_NAME
WHERE COLUMN_NAME = 'abcde'
각 단계별로 본다면 실제 쿼리문에 작성한 ${}는 파싱된 쿼리문과 실제 동작된 쿼리문에 들어간 값이 동일하다. 하지만, #{}의 경우, 오라클로 전달된 쿼리문에는 변수가 '?'로 인식되어 여기에 파라미터가 바인딩된다.
[ SQL Injection ]
그렇다면 ${}를 썼을 때, SQL Injection에 어떻게 취약한지 알아보자.
-- 실제 쿼리문
SELECT *
FROM TABLE_NAME
WHERE id = '${id}' AND password = #{password}
-- id에 10' -- 라는 값이 입력되면,
SELECT *
FROM TABLE_NAME
WHERE id = '10' --' AND password = #{password}
이런식으로 password에 대한 WHERE절을 지워버리고 id가 10인 데이터를 추출할 수 있게 된다.
물론 WAS에서 쿼리를 수행하기 전. 데이터 검증 로직을 추가하면 되지 않냐고 생각할 수 있다. 하지만, 로직이 추가되는 것 자체가 성능상 어떤 영향을 줄지, 다양한 종류의 SQL Injection을 대비하기에는 무리가 있고, 휴먼 에러로 인해 검증 로직에 결함이 있었다면 결국, 보안에 빈틈을 주게될 것이다.
'Developer's_til > 스프링 프레임워크' 카테고리의 다른 글
[Spring] API 속도 개선을 위한 여정 (0) | 2024.02.17 |
---|---|
[Spring] Static 변수와 스프링 빈 (1) | 2023.12.20 |
[Spring] XSS필터와 Surrogate pair (0) | 2023.11.10 |
[Spring] 완전한 AOP 솔루션을 제공하는 AspectJ (0) | 2023.10.25 |
[inflearn] 스프링 MVC 2편 review (0) | 2023.06.11 |