Spring Boot 프로젝트(회고)

제이 용·2026년 1월 10일

스프링 프로젝트

목록 보기
1/1
post-thumbnail

Spring Boot 프로젝트에서 AOP·인증/인가·Redis 분산락까지 — 트러블슈팅과 설계 기록

목표
단순 기능 구현을 넘어서, 왜 이런 구조를 선택했는지, 어떤 문제를 겪었고 어떻게 해결했는지를 기록하기 위한 글이다.


1. 프로젝트 배경

이 프로젝트는 공연 예매 서비스로,

  • 동시성 이슈가 명확한 좌석 선택 도메인
  • JWT 기반 인증/인가
  • Redis를 활용한 캐싱과 분산 락
    을 핵심 기술 포인트로 삼았다.

특히 튜터님·코드리뷰 상황에서

“왜 이렇게 설계했나요?”
라는 질문에 답할 수 있는 구조를 만드는 것을 목표로 했다.


2. 인증 / 인가 설계 (JWT)

2-1. 왜 세션이 아닌 JWT인가?

  • 서버 확장에 유리한 Stateless 구조
  • 모바일/프론트엔드와의 연동 용이
  • 인증 정보를 토큰 자체에 포함 가능

단점으로는 앞으로 구현할 로그아웃과 토큰 무효화가 어렵다는 점이었다.


2-2. AuthenticationManager를 사용한 이유

AuthenticationManager는 인증의 진입점 역할을 한다.

  • Controller → Service가 인증을 직접 처리하지 않게 함
  • 인증 로직을 Spring Security에게 위임

인증 책임을 프레임워크에 맡기고, 비즈니스 로직과 분리


2-3. JWT 인증 필터를 UsernamePasswordAuthenticationFilter 앞에 둔 이유

  • JWT는 로그인 이후 요청을 검증
  • UsernamePasswordAuthenticationFilter는 로그인 요청 전용
[JWT 인증 필터] → [UsernamePasswordAuthenticationFilter]

이미 토큰이 있는 요청은 굳이 로그인 필터를 탈 필요가 없다..!


2-4. UserDetails / UserDetailsService를 분리한 이유

구성요소역할
UserDetailsSpring Security가 이해하는 사용자 모델
UserDetailsService사용자 조회 책임

도메인 User와 보안 User를 분리해 결합도 감소


3. 로그아웃과 Blacklist 설계

3-1. JWT 로그아웃의 근본적인 문제

  • JWT는 서버에 저장되지 않음(Stateless 특성)
  • 한 번 발급되면 만료 전까지 유효

단순히 "로그아웃" API 호출로는 토큰을 무효화할 수 없음


3-2. Blacklist를 도입한 이유

이미 발급된 토큰을 서버 기준으로 차단하기 위함

  • 로그아웃 시 토큰을 Blacklist에 저장
  • 이후 요청마다 Blacklist 확인
  • 존재하면 인증 실패

Stateless 구조를 유지하면서도 즉시 무효화 보장


3-3. User 테이블과 분리한 이유

  • 인증 상태 ≠ 사용자 정보
  • 인증 관련 데이터는 조회 빈도가 높음
  • Redis 전환을 고려한 구조

책임 분리 + 확장성 확보


3-4 위 설계로 인한 차후 생각해야될 모델

수많은 유저가 로그아웃을 거치기 때문에 이에따른 병목 현상에 대해 고려

  • 일정 데이터가 쌓이면 삭제하도록 설계
  • 다른 방도가 별도로 필요할 것으로 보임..(;´д`)ゞ

4. 동시성 문제와 Redis 분산 락

4-1. 좌석 선택에서 발생하는 문제

  • 동시에 같은 좌석을 선택하는 요청
  • DB 트랜잭션만으로는 한계

4-2. Redis 분산 락을 선택한 이유

  • 멀티 인스턴스 환경 대응
  • DB 락보다 빠른 처리
  • TTL로 데드락 방지 가능

4-3. 락 구현 방식

SETNX + TTL
Lua Script로 안전한 해제
  • 락 획득 실패 시 즉시 예외
  • 락은 반드시 finally에서 해제

5. AOP로 락 로직 분리

5-1. 왜 AOP인가?

  • 락 로직은 횡단 관심사
  • 서비스 코드가 지저분해지는 문제

비즈니스 로직에서 락 코드 제거


5-2. @RedisLock 어노테이션 설계

  • SpEL을 활용한 동적 key 생성 (SpEL에 대해서는 차후 따로 다뤄볼 계획이다!!(⌐■_■))
  • TTL 정책을 어노테이션으로 명시
@RedisLock(key = "'seat:' + #seatId", ttl = 3000)

5-3. RuntimeException 문제와 개선 포인트

초기에는 AOP 내부에서 RuntimeException으로 감싸며 예외를 던졌다.

문제점:

  • 서비스 레벨의 CustomException이 그대로 전달되지 않음

개선 방향:

  • AOP에서는 예외를 감싸지 않고 그대로 throw
  • 또는 공통 인터페이스 기반 예외 처리

6. Scheduler + 분산 락

6-1. 왜 스케줄러에도 락이 필요한가?

  • 서버가 여러 대일 경우
  • 동일 스케줄이 중복 실행 가능

6-2. RedisSchedulerLockAspect 도입

  • @Scheduled 메서드에 어노테이션 적용

  • 이미 실행 중이면 그냥 종료

    중복 실행 방지 + 안정성 확보


7. 트러블슈팅 요약

이슈해결
JWT 로그아웃Blacklist 도입
좌석 중복 선택Redis 분산 락
락 코드 중복AOP 분리
스케줄 중복 실행Scheduler Lock
예외 전파 문제AOP 예외 정책 개선

8. 마무리

이번 프로젝트를 통해 느낀 점은

기술 선택의 이유를 설명할 수 있어야 진짜 내 코드가 된다

라는 것이었다.

단순 구현이 아니라,

  • 왜 이 구조인지
  • 어떤 대안이 있었는지
  • 왜 이걸 선택했는지

를 계속 고민한 경험이 큰 자산이 되었다.


다음으로 정리하고 싶은 주제

  • Redis vs DB Lock 비교 실험
  • JWT + Refresh Token 고도화
  • 트래픽 테스트와 병목 분석

꼭 해보고 싶다!!!!!!!~(≧▽≦)/~ 프로젝트 마무리한 모두 고생했고 좋은 결과만 있기를 (❁´◡`❁)

2개의 댓글

comment-user-thumbnail
2026년 1월 23일

TIL 작성 기원 13일차

답글 달기

돌아와 주세요 ㅠ

답글 달기