티스토리 뷰

반응형

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

운영에서 진짜 중요한 것들: 보안 테스트 · 로깅 · 모니터링 (안 터지게 만드는 기술)


이제 구현은 끝났다.
근데 실무에서는 여기서 질문이 바뀐다.

“이거… 운영에서 안 터질까요?
“누가 공격하면 어디서 확인하죠?
“권한 오류, 인증 실패… 로그로 남나요?

이번 글은 기능 추가가 아니라 ‘운영 안정성’ 이야기다.
솔직히 말하면, 이 파트가 빠진 보안 구현은 반쪽짜리다.


0️⃣ 이 글에서 다룰 것 (실무 필수)

  • 보안 테스트 전략 (단위 / 통합 / 시나리오)
  • 인증·인가 실패 로그 설계
  • JWT 오류를 “사고 징후”로 바꾸는 방법
  • 운영에서 꼭 필요한 모니터링 포인트
  • 최소 비용으로 시작하는 실전 세팅

1️⃣ “보안 테스트요? 로그인만 되면 되는 거 아닌가요?”

❌ 이 생각이 제일 위험하다.

보안 테스트는 “잘 되는지”가 아니라
👉 **“안 될 때, 제대로 막히는지”**를 보는 테스트다.


2️⃣ 꼭 만들어야 하는 보안 테스트 시나리오 TOP 6

✅ 1. 인증 없이 보호 API 접근

GET /private/hello

기대 결과

  • HTTP 401
  • 컨트롤러 로직 진입 ❌

✅ 2. 잘못된 JWT로 접근

Authorization: Bearer invalid.jwt.token

기대 결과

  • HTTP 401
  • SecurityContext 비어 있음

✅ 3. 만료된 JWT

  • exp 지난 토큰

기대 결과

  • HTTP 401
  • 로그에 token expired

✅ 4. ROLE 없는 사용자 → 관리자 API

GET /admin/hello

기대 결과

  • HTTP 403
  • 인증은 성공, 인가에서 차단

✅ 5. 다른 tenantId로 데이터 접근

GET /contracts/{otherTenantId}

기대 결과

  • HTTP 403 또는 데이터 없음
  • 절대 다른 테넌트 데이터 노출 ❌

✅ 6. 로그인 연속 실패 (브루트포스 징후)

반응형
  • 같은 IP
  • 같은 계정
  • 짧은 시간 내 다수 실패

기대 결과

  • 로그에 명확히 남음
  • (선택) 계정 잠금 or 알림

3️⃣ Spring Security 테스트 코드 기본 패턴 (Kotlin)

3-1. 인증 없는 요청 테스트

@Test
fun `인증 없이 보호 API 접근 시 401`() {
    mockMvc.get("/private/hello")
        .andExpect {
            status { isUnauthorized() }
        }
}

3-2. ROLE 테스트

@Test
@WithMockUser(username = "user", roles = ["USER"])
fun `USER 는 ADMIN API 접근 불가`() {
    mockMvc.get("/admin/hello")
        .andExpect {
            status { isForbidden() }
        }
}

📌 이 테스트는
**“보안이 깨졌을 때 바로 알려주는 안전장치”**다.


4️⃣ 인증·인가 실패 로그, 이렇게 남겨야 의미 있다

❌ 흔한 로그 (의미 없음)

Authentication failed

⭕ 실무에서 쓸 로그

[AUTH_FAIL]
ip=203.0.113.10
username=test
reason=INVALID_TOKEN
path=/admin/hello

👉 누가 / 어디서 / 왜
이 3가지가 무조건 있어야 한다.


5️⃣ JWT 필터에 로깅 포인트 추가 (실전 코드)

if (!jwtProvider.validateToken(token)) {
    log.warn(
        "[AUTH_FAIL] ip={}, token=invalid, path={}",
        request.remoteAddr,
        request.requestURI
    )
}

이 로그는 나중에:

  • 공격 탐지
  • 장애 분석
  • 고객 문의 대응

전부에 쓰인다.


6️⃣ 인증/인가 예외를 “의도된 응답”으로 만들기

6-1. AuthenticationEntryPoint (401)

@Component
class JwtAuthEntryPoint : AuthenticationEntryPoint {

    override fun commence(
        request: HttpServletRequest,
        response: HttpServletResponse,
        authException: AuthenticationException
    ) {
        response.status = HttpServletResponse.SC_UNAUTHORIZED
        response.writer.write("""{"message":"unauthorized"}""")
    }
}

6-2. AccessDeniedHandler (403)

@Component
class JwtAccessDeniedHandler : AccessDeniedHandler {

    override fun handle(
        request: HttpServletRequest,
        response: HttpServletResponse,
        accessDeniedException: AccessDeniedException
    ) {
        response.status = HttpServletResponse.SC_FORBIDDEN
        response.writer.write("""{"message":"forbidden"}""")
    }
}

👉 프론트엔드는 이제
401 / 403 기준으로 명확한 UX 분기 가능.


7️⃣ 운영에서 꼭 봐야 하는 보안 지표 5가지

📊 필수 지표

  1. 인증 실패 횟수 (401)
  2. 인가 실패 횟수 (403)
  3. JWT 만료 오류 빈도
  4. 로그인 실패 Top IP
  5. 관리자 API 접근 시도 로그

👉 이 중 하나라도 갑자기 튀면
보안 이슈 or 버그다.


8️⃣ 최소 비용으로 시작하는 운영 세팅

✔ 로그

  • Logback
  • JSON 로그 (선택)
  • 일단 파일만이라도 남겨라

✔ 모니터링

  • Spring Actuator
  • Micrometer
  • Prometheus + Grafana (나중에)

✔ 알림 (선택)

  • 슬랙 Webhook
  • 관리자 로그인 실패 다수 발생 시 알림

9️⃣ 실무에서 진짜 많이 터지는 운영 사고

❌ 보안 예외를 전부 500으로 반환

→ 프론트도, 운영도 지옥

❌ 인증 실패 로그 없음

→ 공격인지 버그인지 구분 불가

❌ tenant 사고를 로그로 못 찾음

→ 대응 지연 = 신뢰 하락


🔚 이번 편 핵심 요약

  • 보안은 테스트 + 로깅 + 모니터링 까지가 한 세트
  • “안 되는 경우”를 먼저 테스트해야 한다
  • JWT 오류는 장애 신호
  • 운영 로그는 “사람이 읽을 수 있게” 남겨라

다음 글 예고 (번외, 진짜 마지막)

**보안 사고 시나리오 리허설:

“토큰 유출 / 권한 오류 / 테넌트 사고가 났다면?”**

  • 사고 유형별 대응 플로우
  • 로그로 원인 찾는 법
  • 서비스 중단 없이 수습하는 전략
  • 사후 개선 포인트 정리

👉 이 글까지 보면 ‘운영 가능한 시큐리티’ 완성


 

스프링시큐리티, springsecurity, 코틀린, kotlin, jwt, 보안테스트, 모니터링, 로그설계, 백엔드개발, 운영보안

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