티스토리 뷰
스프링 시큐리티 완전 처음부터 13편 (Kotlin)
보안 사고 리허설: 토큰 유출 · 권한 오류 · 테넌트 사고가 났다면 어떻게 대응할까
이 글은 제발 안 겪었으면 좋겠지만,
한 번은 반드시 겪게 되는 상황을 다룬다.
“어… 로그 보니까
이상한 요청이 계속 들어오는데요?”“다른 회사 데이터가 보였다는 문의가 왔어요…”
“관리자 API에 평소보다 10배 많은 호출이 찍혔습니다.”
이때 중요한 건
코드를 얼마나 잘 짰는지가 아니라
👉 얼마나 침착하게, 순서대로 대응하느냐다.
이번 글은
**실제 운영 환경에서 바로 써먹는 ‘보안 사고 대응 리허설’**이다.
(책 한 챕터 쓴다는 마음으로 간다.)
0️⃣ 보안 사고 대응의 기본 원칙 (이거부터 박자)
🔴 절대 하지 말아야 할 것
- “일단 서버 재시작”
- “일단 다 막아”
- “일단 로그부터 지워”
🟢 반드시 지켜야 할 것
- 확대 방지
- 원인 파악
- 영향 범위 확인
- 복구
- 재발 방지
이 순서가 바뀌면
사고는 두 배로 커진다.
1️⃣ 사고 유형 ① JWT 토큰 유출 의심

📌 전조 증상
- 특정 사용자로부터 비정상적인 API 호출 급증
- 평소 쓰지 않던 IP / 국가에서 요청
- JWT 만료 전인데 인증 성공 로그 계속 발생
🧠 1단계: 상황 판단
먼저 이걸 구분해야 한다.
질문의미
| 같은 IP인가? | 봇/스크립트 가능성 |
| 다른 IP인가? | 토큰 탈취 가능성 |
| 특정 계정인가? | 계정 단위 사고 |
| 전체인가? | 시스템 설계 문제 |
🛑 2단계: 즉시 조치 (확대 방지)
JWT 기반에서 가장 현실적인 대응
✔ 옵션 A (가장 쉬움)
- Access Token 만료 시간을 줄인다 (즉시 배포)
- 민감 API 재인증 요구
✔ 옵션 B (조금 고급)
- 해당 사용자 status = SUSPENDED
- JWT 검증 시 사용자 상태 체크
if (user.isSuspended) {
throw AuthenticationException("suspended user")
}
👉 토큰 자체를 폐기 못 해도,
토큰의 ‘효력’을 무력화할 수 있다.
🔍 3단계: 원인 파악
체크리스트:
- 프론트에서 localStorage 사용 중인가?
- HTTPS 누락 구간 있는가?
- 로그에 JWT 전체를 찍고 있지 않은가?
- 프록시 / CDN 헤더 노출 문제 없는가?
👉 실무에서 로그에 토큰 찍어놓고 유출되는 경우 많다.
2️⃣ 사고 유형 ② 권한 오류 (403이 아니라 200이 나왔다)

이건 가장 위험한 유형이다.
“USER 인데 ADMIN API가 호출됐습니다”
🧠 1단계: 즉시 재현 시도
- 같은 계정
- 같은 토큰
- 같은 요청
👉 재현 안 되면 절대 고치지 마라
(재현 전 수정 = 원인 영구 미궁)
🔍 2단계: 흔한 원인 TOP 4
- @PreAuthorize 누락
- ROLE prefix 실수 (ADMIN vs ROLE_ADMIN)
- SecurityConfig URL 규칙 누락
- 테스트에서 @WithMockUser만 믿고 실서비스 확인 안 함
🛑 3단계: 임시 차단
.authorizeHttpRequests {
it.requestMatchers("/admin/**").denyAll()
}
👉 관리자 기능 잠깐 막는 건 사고가 아니다
👉 데이터 노출이 사고다
3️⃣ 사고 유형 ③ 멀티 테넌트 사고 (이건 최악)

“A 회사 사용자가
B 회사 데이터를 봤어요”
이건 보안 + 법적 리스크다.
🧠 1단계: 즉시 서비스 영향 차단
- 문제 API 전체 차단
- 캐시 무효화
- 관리자 기능 임시 중단
🔍 2단계: 원인 90%는 이거다
repository.findAll() // ❌
또는
findById(id) // tenant 조건 없음 ❌
👉 테넌트 조건 누락
🛠 3단계: 구조적 해결 (임시 땜질 ❌)
fun findByTenantIdAndId(tenantId: Long, id: Long)
그리고:
- 모든 Service 메서드에서
- tenantId 자동 주입
🧪 4단계: 재발 방지 테스트
@Test
fun `다른 테넌트 데이터 접근 시 결과 없음`() {
// tenantA 토큰으로 tenantB 데이터 조회
// 결과는 empty or 403
}
👉 이 테스트는 필수다.
4️⃣ 사고 대응 로그, 이렇게 남겨야 ‘증거’가 된다
❌ 의미 없는 로그
ERROR occurred
⭕ 사고 대응용 로그
[SECURITY_INCIDENT]
type=TENANT_DATA_LEAK
user=test
tenantId=1
targetTenantId=2
path=/contracts/99
ip=203.0.113.10
👉 나중에:
- 내부 보고
- 고객 설명
- 재발 방지 문서
전부 이 로그로 한다.
5️⃣ 사고 후 반드시 해야 하는 사후 조치
✔ 1. Post-Mortem 작성
- 원인
- 발견 경로
- 영향 범위
- 재발 방지
✔ 2. 테스트 추가
- “다시는 터지지 않게”
✔ 3. 가이드 업데이트
- 신규 개발자에게 공유
6️⃣ 이 글을 끝까지 읽은 사람에게 솔직한 말
보안 사고는
실력 부족의 증거가 아니다.
사고 이후에
어떻게 대응하고
어떻게 구조를 바꾸느냐가
진짜 실력이다.
이 리허설을 머릿속에 한 번이라도 돌려본 사람은
실제 상황에서 절대 패닉에 빠지지 않는다.
🔚 시리즈 진짜 마지막 요약
- 구현 → ❌
- 운영 → ❌
- 사고 대응까지 포함해야 보안 설계다
스프링시큐리티, springsecurity, 코틀린, kotlin, jwt, 보안사고, incidentresponse, 멀티테넌트, saas보안, 개발블로그
'study > kotlin' 카테고리의 다른 글
| 스프링 시큐리티 완전 처음부터 15편 (Kotlin) (0) | 2026.01.12 |
|---|---|
| 스프링 시큐리티 완전 처음부터 14편 (Kotlin) (0) | 2026.01.07 |
| 스프링 시큐리티 완전 처음부터 12편 (Kotlin) (0) | 2026.01.05 |
| 스프링 시큐리티 완전 처음부터 11편 (Kotlin) (0) | 2026.01.04 |
| 스프링 시큐리티 완전 처음부터 9편 (Kotlin) (0) | 2025.12.31 |
- Total
- Today
- Yesterday
- JWT
- Next.js
- kotlin
- fastapi
- nextJS
- 압박면접
- SEO최적화
- REACT
- llm
- 백엔드개발
- Docker
- flax
- Express
- ai철학
- rag
- DevOps
- Redis
- CI/CD
- NestJS
- seo 최적화 10개
- Prisma
- 프론트엔드개발
- 개발블로그
- 딥러닝
- JAX
- 웹개발
- node.js
- PostgreSQL
- 쿠버네티스
- Python
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |

