본문 바로가기
Back-end/SpringBoot

[Security] 내 security filter chain 구조

by backend 개발자 지망생 2025. 6. 22.

이번에 간단한 팀 프로젝트로 인증을 맡게 되었다.

정말 많이 배울 수 있었는데, 앞으로도 많은 걸 해야하는 것이구나라고 깨달을 수 있어서 더 의미있는 경험이었다 생각한다.

 

현재 이 security filter chain에 대한 대략적인 구조도를 기록하고, 나아갈 방향들을 적으려 한다.

 


 

1. 구조도

 

Client에서 요청이 오면 먼저 필터를 거치게 되는데, 내가 설계한 필터 순서는 다음과 같다.

 

1. logging filter:  거쳐서 들어오는 모든 요청들과 응답들을 찍을 수 있게 하여 디버깅할 때 도움을 줄 수 있도록 함.

2. ratelimit filter:  bucket4jredis에 연결하여서 login 등 인증 api 접근 시 버킷을 차감하여, 정책 상 정해 놓은 상한선을 넘을 시 429 에러를 던지는 필터.

3. securityContextHolederFilter: 빈 securityContext를 생성

4. csrf 필터: 서버에서 생성한 난수를 쿠키로 내려주게 되어, 프론트엔드에서 응답 헤더에 이 쿠키의 값을 보내어 동일한 지 체크하는 double submit 방식으로 CSRF 공격을 방지함

5. jwtAuthenticatoinfilter: HttpOnly 쿠키의 액세스 토큰을 꺼내서 서명 검증 후, 인증 정보를 SecurityContext에 세팅

6. oauth2Login DSLoauth 로그인 경로로 들어오는 데이터를 영속화시키고, 토큰을 발행하는 역할

7. filterSecurityInterceptorURL별 허용/거부 정책을 적용. 예를 들어 refresh, logout 엔드포인트는 모두 허용하고, 나머지는 인증이 필요, 해당이 되지 않는 엔드포인트의 경우에는 접근을 거부함.

 

토큰 관리 방식: 액세스·리프레시 토큰은 HttpOnly쿠키에 저장해 XSS를 막고 CSRF 공격은 앞서 설명한 Double-submit 방식과 samesite=lax 쿠키로 이중 방어를 통해 차단함

 


refresh token rotation flow

전채 구조도

 

 

1. refresh token rotation이 되는 flow

인증이 필요한 API에 만료된 Access Token을 포함해 요청이 들어오면, 요청은 앞서 설명한 필터들을 모두 거친 후, ExceptionHandler에서 40101 에러를 반환함

이 에러는 프론트엔드의 인터셉터를 통해 refresh API를 호출하는 트리거가 되며, 이 API 경로는 인증 없이 접근 가능한 공개 경로

요청은 TokenController를 거쳐 TokenService로 전달되고, 다음과 같은 절차로 처리:

 

  1. 쿠키에 담긴 Refresh Token 값을 기준으로 Redis에서 대응되는 값을 조회
  2. Redis에 저장된 토큰과 쿠키의 jti를 비교하여 검증 수행
  3. 검증이 통과되면 기존 Redis 토큰은 덮어쓰기(overwrite)하여 재사용을 방지
  4. 새로운 Access Token과 Refresh Token을 발급
  5. 프론트엔드는 새 토큰으로 원래 요청을 재시도

이 흐름을 통해 사용자가 토큰 만료 상황에서도 원활하게 인증을 이어갈 수 있도록 구성했다.

 

2. 예외상황(너무 많은 요청 차단)

 

refresh api의 경우에는 인증 엔드포인트이기 때문에 브루트포스와 같은 공격에 대비하기 위해 있는 필터인 ratelimitfilter에 버킷 제한을 했다.

따라서 이 요청 범위의 상한보다 많은 요청을 보낼 시 429 error를 반환할 수 있도록 했다.

 

3. 예외 상황(refresh token 만료 시)

이번에는 Refresh Token 만료 시의 예외 처리 흐름이다.

 

Refresh Token이 존재하지 않거나 만료된 상태에서 토큰 검증 로직에 진입하면, 40103 에러 코드를 반환하도록 구성했다. 이 에러는 프론트엔드에서 로그아웃 API를 호출하는 기준점으로 사용되며, 사용자는 이후 재로그인을 통해 인증을 이어가게 된다.

 


 

정리하면, 로깅 → 레이트리밋 → CSRF → JWT 검증의 다단계 필터 체계를 통해 보안 계층을 구성하고, 회전식 Refresh Token 전략으로 단일 세션만 유지되도록 설계했다.

 

이를 통해 세션 탈취, 브루트포스, CSRF 공격을 모두 방어하며, 인증 시스템이 안정적으로 동작할 수 있도록 구현했다.

 


 

TO-BE

나름 생각을 하며 구현한 필터이지만 리팩토링 할 것들이 많다고 생각했다. 추후에 진행 시에 적용할 수 있도록 기록한다.

 

1. ELK (Elasticsearch, Logstash, Kibana)

  • 목적: 요청/응답 로그, 인증 실패, 예외 상황 등을 수집 및 시각화하여 문제 추적과 보안 분석에 도움
  • 활용도: 운영 단계에서 필수적인 모니터링 및 이상 탐지 시스템으로 기능

2. Access Token은 Authorization 헤더로 전송

  • 목적: OAuth 2.0 표준 준수 + 쿠키와의 공격 벡터 분리 (XSS vs CSRF)
  • 효과: 클라이언트/서버 간 역할 구분이 명확해지고, 보안 사고 발생 시 영향 범위 축소 가능

3. Redis 저장 시 device id 등 식별자 포함

  • 목적: Refresh Token 탈취 시 동일한 토큰이 다른 디바이스에서 재사용되는 것 방지
  • 전략: Redis에서 조회 시 device id와 함께 체크하여 불일치하면 인증 거부