개념
보는 것에서 바꾸는 것으로
모듈 9(레슨 10)에서 Charles·Wireshark·tcpdump가 각각 어떤 도구인지 개념을 봤다. 그리고 레슨 12에서 브라우저 DevTools로 요청을 확인하는 법을 익혔다.
이 레슨은 그다음이다. 프록시의 진짜 값어치는 보는 것이 아니라 바꾸는 것 에 있기 때문이다.
서버 팀에 부탁한다. "결제 API가 500을 반환하는 상황을 만들어주세요. 그때 화면이 어떻게 되는지 봐야 합니다."
답이 온다. "그 코드는 지금 못 건드립니다. 스테이징에서 장애를 일부러 낼 수는 없어요."
이 대화가 QA에게 익숙하다면 이 레슨이 필요하다. 프록시로는 서버를 건드리지 않고 그 상황을 만들 수 있다. 응답을 가로채서 500으로 바꿔 내 화면에만 전달하면 되기 때문이다.
이 레슨은 Charles를 기준으로 설명하지만, 개념은 도구가 달라도 같다 — Proxyman, mitmproxy, Fiddler 모두 같은 기능을 다른 이름으로 제공한다.
프록시가 중간에 서는 원리
[ 평소 ]
앱/브라우저 ────────────────▶ 서버
[ 프록시를 켜면 ]
앱/브라우저 ───▶ 프록시 ───▶ 서버
│ │
│ └── 요청을 보거나 바꿔서 전달
└───── 응답을 보거나 바꿔서 전달HTTP는 그냥 보인다. 문제는 HTTPS 다. 암호화돼 있으니 중간에서 내용을 볼 수 없다. 그래서 프록시는 자기 인증서를 기기에 설치 하게 하고, 그 인증서로 트래픽을 복호화한 뒤 다시 암호화해 서버로 보낸다.
이게 중간자 공격(MITM)과 기술적으로 같은 구조 라는 점이 중요하다. 그래서 아래 원칙이 따라온다.
⚠️ 테스트가 끝나면 인증서를 지우고 프록시 설정을 해제한다. 인증서가 설치된 기기는 그 프록시를 신뢰 한다. 공용 와이파이에서 켜둔 채 돌아다니면 그 자체가 위험이고, 개인 기기에 남겨두면 잊는다.
설정 — 여기서 대부분 막힌다
처음 쓰는 사람이 막히는 지점이 정해져 있어서 순서대로 정리한다.
1) PC 브라우저 트래픽 보기
Charles를 켜면 대개 시스템 프록시가 자동으로 잡힌다. HTTPS를 보려면 두 가지를 해야 한다.
SSL 은 통신을 암호로 감싸는 기술의 옛 이름이다(지금은 TLS 라고 부르지만 도구 메뉴에는 옛 이름이 그대로 남아 있다). 암호로 감싸져 있으니 그냥은 안을 못 보고, 내 컴퓨터가 믿는 증명서를 하나 깔아 두어야 도구가 중간에서 풀어 보여 줄 수 있다. 아래 "루트 인증서 설치" 가 그 작업이다.
- 루트 인증서 설치 (
Help → SSL Proxying → Install Charles Root Certificate) - SSL Proxying 대상 지정 (
Proxy → SSL Proxying Settings)
두 번째를 빠뜨리는 경우가 많다. 인증서를 설치해도 어떤 호스트를 복호화할지 지정하지 않으면 여전히 암호문만 보인다.
SSL Proxying Settings 예시
*.example.com : 443 ← 우리 서비스만
api.example.com : 443*:* 로 전부 지정할 수도 있지만 권하지 않는다. 무관한 트래픽까지 쏟아져
목록에서 우리 요청을 찾기 어려워지고, 관계없는 서비스의 개인정보까지
캡처에 담긴다.
2) 모바일 기기 트래픽 보기
여기가 프록시를 쓰는 가장 큰 이유다. 앱에는 DevTools가 없다.
① PC와 모바일 기기를 같은 네트워크에 둔다
② 모바일 Wi-Fi 설정에서 프록시를 수동으로 지정
호스트: PC의 IP 포트: 8888 (Charles 기본)
③ 모바일 브라우저에서 chls.pro/ssl 접속 → 인증서 다운로드
④ 기기 설정에서 인증서를 신뢰하도록 활성화
· iOS: 설정 → 일반 → 정보 → 인증서 신뢰 설정
· Android: 설정에서 CA 인증서 설치안 보일 때 확인할 것 두 가지.
- 인증서 피닝(certificate pinning) — 앱이 서버 인증서를 코드에 박아두고 검증하는 방식이다. 이러면 프록시 인증서를 거부해 아예 통신이 안 된다. 금융·결제 앱에서 흔하다. 이건 설정 문제가 아니라 의도된 보안 조치 이므로, 개발팀에 디버그 빌드를 요청하는 것이 정답이다.
- 안드로이드 7 이상의 사용자 인증서 제한 — 앱이 명시적으로 허용하지 않으면 사용자가 설치한 CA를 신뢰하지 않는다. 역시 디버그 빌드가 필요하다.
기능 1 — 브레이크포인트: 요청·응답을 멈춰 세운다
가장 강력한 기능이다. 특정 요청에 브레이크포인트 를 걸면 그 요청이 프록시에 도착했을 때 멈추고, 내용을 편집한 뒤 보낼지 물어본다.
요청 브레이크포인트
앱이 보낸 것: {"quantity": 1, "couponId": "SUMMER"}
내가 바꾼 것: {"quantity": -1, "couponId": "SUMMER"}
↑ 클라이언트 검증을 우회한 값
→ 서버가 이걸 어떻게 처리하는지 본다
응답 브레이크포인트
서버가 준 것: 200 {"orders": [ ...20건 ]}
내가 바꾼 것: 200 {"orders": []}
→ 빈 목록일 때 화면이 어떻게 되는지 본다요청 브레이크포인트가 열어주는 테스트 영역 이 넓다. 화면에서는 만들 수 없는 입력을 서버에 보낼 수 있기 때문이다.
| 시도 | 확인하는 것 |
|---|---|
| 수량을 음수·0·매우 큰 값으로 | 서버 측 검증이 있는가 |
| 필수 필드를 빼고 전송 | 400을 주는가, 아니면 500이 나는가 |
| 다른 사용자의 리소스 ID로 변경 | 권한 검증(모듈 12 레슨 8의 403) |
| 가격·할인율을 조작 | 서버가 값을 재계산하는가, 클라이언트를 믿는가 |
| 만료된 토큰으로 전송 | 401 처리와 재로그인 유도 |
네 번째가 실무에서 실제로 사고가 나는 지점이다. 화면에서 계산한 최종 금액을 그대로 서버가 받아 저장하는 구현이 드물지 않다. 모듈 16 (레슨 5)에서 배운 클라이언트를 신뢰하면 안 된다 는 원칙을 직접 확인하는 방법이다.
이건 허가받은 환경에서만 한다. 우리 서비스의 테스트 환경이거나 명시적으로 승인된 범위여야 한다. 모듈 16에서 다룬 원칙과 같다.
기능 2 — Map Local: 응답을 파일로 갈아끼운다
특정 요청의 응답을 로컬 파일 내용으로 대체 한다. 브레이크포인트가 매번 손으로 고치는 방식이라면, Map Local은 자동이다.
GET /api/orders → ~/test-data/orders-empty.json
GET /api/user → ~/test-data/user-vip.json서버 없이 프런트엔드를 테스트하는 가장 빠른 방법 이다. 실무에서 쓰는 상황들이다.
| 상황 | 준비할 파일 |
|---|---|
| 빈 상태(주문 0건) 화면 | 빈 배열 응답 |
| 대량 데이터(1,000건) 렌더링 | 큰 배열 응답 |
| 긴 텍스트·이모지·특수문자 | 경계값이 담긴 응답 |
| 등급별 화면(VIP·휴면·정지) | 상태별 사용자 응답 |
| 아직 개발 안 된 신규 API | 스펙대로 만든 가짜 응답 |
마지막 줄이 특히 값을 한다. 백엔드가 아직 없어도 OpenAPI 스펙(모듈 12 레슨 4)만 있으면 프런트엔드 테스트를 시작할 수 있다. 그리고 나중에 실제 API가 나왔을 때 내 가짜 응답과 다르면 그게 계약 위반 이다.
Map Remote는 비슷하지만 요청을 다른 서버로 돌린다. 운영 앱을 스테이징 API에 붙여 보는 식으로 쓴다.
기능 3 — Rewrite: 규칙으로 자동 치환
브레이크포인트와 Map Local 사이에 있는 기능이다. 정규식 규칙 으로 헤더나 본문을 자동으로 바꾼다.
규칙 예시
· 모든 요청의 User-Agent 헤더를 특정 값으로 → 기기별 분기 테스트
· 응답의 "status":"ACTIVE" → "status":"SUSPENDED" → 정지 계정 화면
· 요청 헤더에 실험 그룹 지정 추가 → A/B 테스트 특정 그룹 강제
· 응답 상태 코드 200 → 503 → 점검 중 화면세 번째가 유용하다. 기능 플래그나 A/B 테스트로 나뉜 화면을 확인할 때, 개발팀에 요청하지 않고 헤더 하나로 특정 그룹에 들어갈 수 있는 경우가 많다.
기능 4 — Throttling: 느린 네트워크를 진짜로 재현
DevTools에도 쓰로틀링이 있지만(레슨 12), 프록시 쓰로틀링은 모바일 앱에도 적용된다 는 것이 다르다. 그리고 대역폭·지연·패킷 손실을 더 세밀하게 설정할 수 있다.
| 설정 | 만들어내는 상황 |
|---|---|
| 낮은 대역폭 | 이미지·영상 로딩 지연, 타임아웃 |
| 높은 지연(latency) | 응답 대기 중 중복 조작, 순서 역전 |
| 패킷 손실 | 재시도 로직이 동작하는가 |
| 대역폭 제한 + 큰 응답 | 부분 로딩 처리 |
모듈 17(모바일 테스트)에서 다루는 네트워크 전환(Wi-Fi ↔ LTE ↔ 지하철 음영)의 상당 부분을 여기서 재현할 수 있다.
기능 5 — Repeat / Compose: 요청을 다시, 또는 새로
- Repeat — 잡힌 요청을 그대로 다시 보낸다. 중복 요청 처리(멱등성) 를 확인하는 가장 빠른 방법이다. 결제 요청을 두 번 보내면 두 번 결제되는가?
- Repeat Advanced — 같은 요청을 N회, 동시에 보낸다. 가벼운 동시성 테스트가 된다(재고 차감 경쟁 상태 등).
- Compose — 잡힌 요청을 편집해 새로 만들어 보낸다. Postman과 비슷하지만 실제 앱이 보낸 요청을 출발점으로 삼는다 는 것이 다르다. 헤더·토큰이 이미 채워져 있으므로 훨씬 빠르다.
실무 팁: 모바일 앱의 API를 Postman으로 테스트하려 할 때, 헤더와 토큰을 손으로 재현하느라 시간을 쓴다. Charles에서 잡은 요청을 Compose로 열거나 cURL로 복사 하면 그 과정이 생략된다.
mitmproxy — 터미널과 자동화가 필요할 때
Charles가 GUI 도구라면 mitmproxy 는 터미널 기반이고 오픈소스다. 같은 일을 하지만 성격이 다르다.
| Charles / Proxyman | mitmproxy | |
|---|---|---|
| 인터페이스 | GUI | 터미널(+ 웹 UI) |
| 비용 | 상용(체험 제공) | 무료·오픈소스 |
| 자동화 | 제한적 | 파이썬 스크립트로 확장 |
| CI 연동 | 어렵다 | 가능 |
| 학습 곡선 | 낮다 | 중간 |
mitmproxy의 결정적 장점은 스크립트 다. 파이썬으로 요청·응답을 프로그래밍적으로 조작할 수 있어, 반복 테스트를 자동화 할 수 있다.
# 모든 주문 API 응답을 빈 목록으로 바꾸는 스크립트
def response(flow):
if "/api/orders" in flow.request.pretty_url:
flow.response.text = '{"orders": []}'Charles에서 손으로 하던 Map Local이 코드 몇 줄이 된다. 그리고 이걸 CI에 넣으면 "빈 상태 화면"을 매 릴리즈마다 자동으로 확인 할 수 있다. 모듈 13의 UI 자동화와 결합하는 지점이다.
캡처 파일을 다룰 때의 원칙
프록시 세션에는 모든 것이 들어 있다. 토큰, 쿠키, 개인정보, 결제 정보.
| 하지 말 것 | 대신 |
|---|---|
| 세션 파일을 이슈에 그대로 첨부 | 필요한 요청만 골라 내보내기 |
| 캡처 화면을 자르지 않고 스크린샷 | 토큰·개인정보 부분 가리기 |
| 캡처 내용을 AI에 붙여 넣기 | 민감 정보 제거 후, 또는 하지 않기 |
| 공용 네트워크에서 프록시 켜두기 | 테스트 후 즉시 해제 |
| 개인 기기에 인증서 남기기 | 테스트 종료 시 삭제 |
모듈 18b(레슨 9)에서 다룬 원칙 그대로다. 편의를 위해 통째로 붙여 넣는 순간 사고가 난다.
실무에서 왜 필요한가
QA가 프록시를 다루면 개발팀에 부탁해야 하던 일의 상당수를 스스로 할 수 있게 된다. 에러 응답 재현, 빈 상태 확인, 등급별 화면, 느린 네트워크, 중복 요청. 이것들을 서버 코드 수정 없이 만들어낼 수 있다는 것은 테스트 범위와 속도를 동시에 바꾼다.
특히 모바일 앱 QA에서는 사실상 필수 다. 앱에는 DevTools가 없으므로 "이 화면이 어떤 API를 부르는지"조차 프록시 없이는 알 수 없다. 결함을 프런트엔드/백엔드로 가르는 판단도 여기서 시작한다.
보안 관점의 확인도 QA의 일이 된다. 요청 값을 조작해 서버가 검증하는지 보는 것은 모듈 16(레슨 5)의 취약점을 실제로 확인하는 방법이고, "클라이언트에서 막았으니 괜찮다"는 주장이 사실인지 5분이면 알 수 있다.
채용 관점에서도 신호가 된다. "Charles 사용 경험"은 공고에 흔히 적히는데, 실제로 요구되는 것은 트래픽을 보는 것이 아니라 조작해서 상황을 만들어낸 경험 이다. Map Local로 백엔드 없이 프런트를 검증했다거나, 요청 변조로 권한 검증 누락을 찾았다는 이야기는 구체적이다.