[Day29] 쇼핑몰 실습 - MyBatis, SQL Injection
📍 Pull Request Template
.github/pull_request_template.md 파일을 만들어 PR을 작성할 때 일관성을 유지
✅ 다양한 템플릿이 있으니 팀원과 상의해서 정하기 ! 아래는 하나의 예시 템플릿일 뿐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
## 개요
<!---- 변경 사항 및 관련 이슈에 대해 간단하게 작성해주세요. 어떻게보다 무엇을 왜 수정했는지 설명해주세요. -->
<!---- Resolves: #(Isuue Number) -->
## PR 유형
어떤 변경 사항이 있나요?
- [x] 새로운 기능 추가
- [ ] 버그 수정
- [ ] CSS 등 사용자 UI 디자인 변경
- [ ] 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
- [ ] 코드 리팩토링
- [ ] 주석 추가 및 수정
- [ ] 문서 수정
- [ ] 테스트 추가, 테스트 리팩토링
- [ ] 빌드 부분 혹은 패키지 매니저 수정
- [ ] 파일 혹은 폴더명 수정
- [ ] 파일 혹은 폴더 삭제
## PR Checklist
PR이 다음 요구 사항을 충족하는지 확인하세요.
- [x] 커밋 메시지 컨벤션에 맞게 작성했습니다.
- [x] 변경 사항에 대한 테스트를 했습니다(버그 수정/기능에 대한 테스트).
✅ pull Request할 때마다 설정해둔 템플릿이 같이 뜸
📍 commit message convention
1
2
3
4
5
6
7
feat: 새로운 기능 추가
fix: 버그 수정
docs: 문서 수정
style: 코드 포맷 변경, 세미콜론 누락, 코드 변경 없음
refactor: 프로덕션 코드 리팩터링
test: 테스트 추가, 테스트 코드 리팩터링, 프로덕션 코드 변경 없음
chore: 빌드 테스크 업데이트, 패키지 매니저 환경설정, 프로덕션 코드 변경 없음
📍 Postman
신기하게도 어제 쓸 일이 있어서 이미 설치했다!
맥은 brew를 이용해서 설치할 수 있다.
1
brew install --cask postman
GET 요청
✅ GET 요청 시, 데이터를 URL 파라미터에 추가해서 요청할 수 있음
1
GET /users?id=1
POST 요청
Spring에 찍힌 회원가입 성공
1
2
3
4
오랜만에 어제 포스트맨을 사용할 때 parameter와 body가 헷갈렸는데
오늘 다시 보니 get 요청할 때 param을 사용,
post 요청 시에는 body에 넣어 요청이 생각 났다.
역시 오래 사용하지 않으면 까먹는다. 계속해서 공부하자 !
📍 SQL Injection
SQL Injection(=SQL 삽입 공격)은 사용자가 입력한 데이터가 SQL 쿼리의 구조를 변경하여 예상치 못한 동작을 유발하는 보안 취약점
SQL Injection 예시
- 다음과 같은 로그인 쿼리가 있다
1 2
Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("select * from member where email='" + email + "' and pwd='" + password + "'");
- 사용자가
email
과password
입력란에 다음과 같은 값을 입력하면1 2 3 4
{ "email" : "' or ''='", "pwd" : "' or ''='" }
- 실제 실행되는 SQL
1
SELECT * FROM member WHERE email='' or ''='' AND pwd='' or ''=''
✅ SQL에서는 “(큰따옴표), ‘(작은따옴표) 같은 문자가 SQL 구문의 일부로 해석될 수 있음 !
email='' OR ''=''
→ 항상 참(True)pwd='' OR ''=''
→ 항상 참(True)
즉, 입력 데이터가 SQL의 구조를 바꿔 모든 계정에 로그인할 수 있는 보안 취약점이 발생 ! -> SQL Injection 공격의 대표적인 사례
postman에서 실행해본 결과 -> 모든 계정에서 접근 가능하므로 member 테이블에 가장 첫 계정이 뜸
PreparedStatement 사용으로 해결
PreparedStatement
는 SQL 쿼리를 미리 컴파일한 후, 데이터만 바인딩하는 방식으로 동작
statement
를 사용한 기존 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
public Member login(Member m) throws Exception {
Class.forName(DB_DRIVER); // Driver 클래스를 로드
Connection con=DriverManager.getConnection(DB_URL,DB_USER,DB_PW);
Statement stmt=con.createStatement();
ResultSet rs=stmt.executeQuery("select * from member where email='"+m.getEmail()+"' and pwd='"+m.getPwd()+"' ");
if(rs.next()) {
String nickname = rs.getString("nickname");
m.setNickname(nickname);
}
return m;
}
💡 PreparedStatement
가 적용된 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Member login(Member m) throws Exception {
Class.forName(DB_DRIVER);
Connection con=DriverManager.getConnection(DB_URL,DB_USER,DB_PW);
PreparedStatement stmt=con.prepareStatement("select * from member where email=? and pwd=? ");
stmt.setString(1, m.getEmail()); // 첫 번째 `?`에 email 값 바인딩
stmt.setString(2, m.getPwd()); // 두 번째 `?`에 pwd 값 바인딩
ResultSet rs=stmt.executeQuery();
if(rs.next()) {
String nickname=rs.getString("nickname");
m.setNickname(nickname);
}
return m;
}
어떻게 SQL Injection을 막을까?
?
에 들어가는 값은 단순한 데이터로 취급되며, SQL의 구조를 바꿀 수 없음.- 입력값이 자동으로 이스케이프 처리되어, SQL 구문이 변형되지 않음.
- Escape(이스케이프)란 ? 특정 문자가 html이나 다른 마크업 언어로 해석되지 않도록 변환하는 과정
📍 MyBatis를 사용하기 (feat. ConnectionPool)
MyBatis는 SQL을 XML이나 어노테이션으로 관리하면서 객체와 매핑할 수 있는 프레임워크이다. JDBC의 번거로운 Connection 및 PreparedStatement 코드 없이 손쉽게 SQL을 실행할 수 있다. 또한 Connection Pool과 함께 사용하면 성능도 향상된다.
MyBatis를 사용하는 과정
1. SQL Mapper 파일 생성 (src/main/resources/mapper/product.xml)
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jes.cafe.dao.ProductDao">
<select id="getAllProducts" resultType="Product" >
select * from product
</select>
</mapper>
namespace="com.jes.cafe.dao.ProductDao"
- 해당 mapper를 ProductDao와 연결
<select id="getAllProducts" resultType="Product">
- getAllProducts 메서드 실행 시
select * from product
쿼리를 실행하고, 결과를Product
객체 리스트로 변환
- getAllProducts 메서드 실행 시
2. application.properties 설정
👉 MyBatis가 XML Mapper를 인식하도록 설정을 추가
1
2
3
4
logging.level.com.shop.cafe=debug
mybatis.type-aliases-package=com.shop.cafe.dto
mybatis.mapper-locations=mapper/*.xml
logging.level.com.shop.cafe=debug
- MyBatis 쿼리 실행 로그를 디버그 모드로 출력.
mybatis.type-aliases-package=com.shop.cafe.dto
- Product 같은 DTO 클래스의 패키지를 지정해 별칭으로 사용 가능.
mybatis.mapper-locations=mapper/*.xml
- mapper 폴더 안의 *.xml 파일을 MyBatis Mapper로 등록.
3. ProductDao.java를 interface로 바꿈 (@Mapper 변경)
👉 MyBatis를 사용하면 DAO 클래스를 직접 구현하지 않고 인터페이스만 만들면 자동으로 구현됨.
1
2
3
4
5
6
7
8
9
10
package com.shop.cafe.dao;
import java.util.*;
import org.apache.ibatis.annotations.Mapper;
import com.shop.cafe.dto.Product;
@Mapper
public interface ProductDao {
public List<Product> getAllProducts() throws Exception;
}
@Mapper
- MyBatis가 이 인터페이스를 구현체로 매핑하도록 함.
List<Product> getAllProducts();
- mapper/product.xml에 있는
<select id="getAllProducts">
와 연결됨.
- mapper/product.xml에 있는
총 코드 정리
오늘은 수업 따라가면서 중간 중간에 결과가 다르게 떠서 진도 따라가느라 쉬는시간에도 계속 정리하고, 못들은거는 찾아보면서 채워나갔다. git도 말썽이고 postman 결과도 자꾸 달라서 정신없었다. 학교 수업도 오늘처럼 열심히 듣지는 않았던거같은데,, 그래도 뭔가 뿌듯한,,그런,,느낌 !
JWT