티스토리 뷰

반응형

스프링 시큐리티 완전 처음부터 13편 (Kotlin)

보안 사고 리허설: 토큰 유출 · 권한 오류 · 테넌트 사고가 났다면 어떻게 대응할까


이 글은 제발 안 겪었으면 좋겠지만,
한 번은 반드시 겪게 되는 상황을 다룬다.

“어… 로그 보니까
이상한 요청이 계속 들어오는데요?”

“다른 회사 데이터가 보였다는 문의가 왔어요…”

“관리자 API에 평소보다 10배 많은 호출이 찍혔습니다.”

이때 중요한 건
코드를 얼마나 잘 짰는지가 아니라
👉 얼마나 침착하게, 순서대로 대응하느냐다.

이번 글은
**실제 운영 환경에서 바로 써먹는 ‘보안 사고 대응 리허설’**이다.
(책 한 챕터 쓴다는 마음으로 간다.)


0️⃣ 보안 사고 대응의 기본 원칙 (이거부터 박자)

🔴 절대 하지 말아야 할 것

  • “일단 서버 재시작”
  • “일단 다 막아”
  • “일단 로그부터 지워”

🟢 반드시 지켜야 할 것

  1. 확대 방지
  2. 원인 파악
  3. 영향 범위 확인
  4. 복구
  5. 재발 방지

이 순서가 바뀌면
사고는 두 배로 커진다.


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

  1. @PreAuthorize 누락
  2. ROLE prefix 실수 (ADMIN vs ROLE_ADMIN)
  3. SecurityConfig URL 규칙 누락
  4. 테스트에서 @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보안, 개발블로그

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/03   »
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
글 보관함
반응형