728x90
서비스 개요 및 상황
Gateway 병목 피하려고 Auth 서버 분리
- Gateway 중앙화: 인증, 라우팅, 로깅, rate limit 등 공통 책임이 과도하게 집중되면 병목 지점이 될 수 있음
특히 Gateway 단에서만 인증하고 내부 서비스를 신뢰하는 구조는 Gateway 우회나 내부 호출 신뢰 문제 만들 수 있음 - Auth 중앙화: 인증 로직만 위임 -> 각 서비스 독립 실행, Auth 서버에 JWT 검증 위임
(인증 필요한 요청마다 Auth 서버 네트워크 호출 비용 발생)- 여러 인스턴스로 스케일 아웃하면 부하 분산 가능
Gateway 중앙 인증 구조
Gateway 죽으면 전체 다운
모든 인증 트래픽 Gateway 집중
Client → Gateway (인증)
→ 모든 서비스
현재 구조
모든 서비스가 Auth 서버의 `/validation-token` 호출하는 구조
Client → Service
→ Auth (검증: /validate-token 호출)
@PostMapping("/v1/auth/validate-token")
ApiResponse<AuthUserResponse> getUserInfo(@RequestHeader("Authorization") String authorizationHeader);
장점
- 검증 로직 Auth에만 존재
- 토큰 구조 변경 시 Auth만 수정
- 도메인 서비스는 비즈니스에 집중
문제점
- 모든 요청마다 Auth 호출
- Auth 장애 = 전 서비스 인증 불가
- 네트워크 latency 발생
- 트래픽 올림
=> 1) 사실상 Auth에 의존하는 중앙화 + SPOF(Single Point of Failure)
- Auth의 `JwtAuthProvider`: 실제 JWT 서명, 만료 확인, Claims 파싱
- 각 도메인 서비스의 `JwtFilter`: 패턴(필터 구조) 재사용
- 2) `JwtFilter` 클래스 자체가 각 서비스마다 물리적으로 존재
-> Auth 엔드포인트 URL or 응답 DTO 구조 or 헤더 파싱 방식 변경 시 모두 수정 필요
- 2) `JwtFilter` 클래스 자체가 각 서비스마다 물리적으로 존재
public class JwtFilter extends OncePerRequestFilter {
ApiResponse<AuthUserResponse> authResponse = authServiceClient.getUserInfo("Bearer " + token);
}
// 1. 헤더에서 토큰 꺼내기
String token = resolveToken(request);
// 2. Auth한테 검증 위임 (JWT 로직 없음)
authServiceClient.getUserInfo("Bearer " + token);
// 3. SecurityContext에 세팅
SecurityContextHolder.getContext().setAuthentication(authentication);
해결 1) Gateway + JWKS 기반 로컬 검증
- Gateway = 라우팅 + 공통 기능
- 인증 = 각 서비스에서 JWKS(JSON Web Key Set) 기반 로컬 검증
- JWKS: JWT 서명 검증에 쓰는 공개키를 JSON 형태로 노출하는 표준 엔드포인트
Client
↓
Gateway (라우팅, rate limit, logging)
↓
Service
↓
JWT 로컬 검증 (JWKS)
장점
- 인증 부하 분산: 모든 인증 요청이 Auth로 몰리지 않고 각 서비스에서 분산 처리
- 공개키를 캐싱하여 사용 -> 매 요청마다 Auth 호출이 사라져 네트워크 호출↓, latency ↓
- Zero Trust 구조: Gateway나 내부 네트워크 신뢰 X 각 서비스가 JWT 직접 인증 검증
시퀀스 다이어그램
로그인 플로우 (카카오 OAuth)

API 요청 플로우 (Gateway + JWKS)

추가 고민
토큰 즉시 무효화 불가
stateless 특성상 발급 후 즉시 무효화 어려움
로그아웃/탈취 시 Auth DB에 블랙리스트 넣어도 각 서비스는 JWKS 캐시로만 검증
-> 만료 전까지 유효한 토큰을 계속 통과시킴
해결 방법
- 각 서비스에서 Redis 블랙리스트 조회 추가
- Access Token 만료 시간을 짧게 설정
- 금융 도메인 특성상 Access Token은 매 요청마다 전송되어 노출 가능성 ↑
-> 5분으로 짧게 설정해 탈취되더라도 피해 최소화 - Refresh Token은 만료 시에만 전송되어 Access Token 대비 노출 빈도 ↓
-> 24시간으로 설정해 사용자 재로그인 빈도 ↓
=> 외부 의존 없이 보안성과 사용자 편의성 동시 확보
- 금융 도메인 특성상 Access Token은 매 요청마다 전송되어 노출 가능성 ↑
해결 2) `common-auth-lib` 모듈 사용
JWT 검증 로직, JWKS 캐싱 로직 모듈로 분리
-> 각 서비스에서 해당 모듈 의존해 동일한 인증 로직 재사용
=> 인증 로직 변경 공통 모듈에서만 관리, 서비스별 코드 수정 없이 일관된 인증 정책 유지
common-auth-lib
└── JwtFilter.java ← JWKS 로컬 검증 로직
└── JwksClient.java ← 공개키 캐싱
└── JwksProperties.java ← Auth URL 설정
각 서비스: implementation 'com.plantify:common-auth-lib'
참고
인증(Authentication) vs 인가(Authorization)
- 인증: ‘누구인가’(토큰 검증)
- ex. 사용자가 제출한 JWT를 검증해 토큰 발급자가 맞는지, 유효 기간이 남았는지, 변조되지 않았는지 확인
- 인가: ‘무엇을 할 수 있는가’(`hasRole` 규칙)
- 인증이 끝난 사용자에게 어떤 API에 접근할 수 있는지, 어떤 자원에 대한 권한이 있는지 판단
- `auth`의 `validate-token`으로 `userId`와 `role` 식별 → 각 서비스 `SecurityConfig`로 권한 판별
HS256 vs RS256(JWKS)
- HS256: 대칭키 기반 (secret 하나 공유), Auth와 모든 서비스 같은 키 공유
- 키 유출 시 전체 서비스 위험
- 서비스마다 secret 배포 필요
- 키 rotation 어려움
- RS256(JWKS): 비대칭키 기반(Auth: private key로 서명 / Service: public key로 검증)
, 공개키는 JWKS endpoint로 제공- 서비스는 공개키만 보유 -> 보안 ↑
- 키 rotation 가능
- JWT header: kid 포함 -> 해당 kid 키 없으면 JWKS 재조회
- Auth 서버 호출 없이도 JWT 검증 가능, JWKS 캐싱해 로컬에서 검증
728x90
반응형
'💻 > 프로젝트' 카테고리의 다른 글
| [PlantiFy] 숲 꾸미기 서비스 - UI 행위 단위 GraphQL 설계 (0) | 2025.11.28 |
|---|---|
| [PlantiFy/v2] MSA 환경에서 결제 시스템 구축하기 2 - Redis 분산 락과 멱등성 설계로 동시성 리팩토링 (0) | 2025.11.21 |
| [PlantiFy/v2] MSA 환경에서 결제 시스템 구축하기 1 - 서비스 개요 / 책임 분리 기반 상태 전이 결제 설계 (0) | 2025.09.25 |
| [PlantiFy] 숲 꾸미기 서비스 - 서비스 개요 / JPA N+1 문제 및 해결 (0) | 2025.02.23 |
| [PlantiFy] 실시간 AI 채팅 서비스 - 서비스 개요 / AI 응답 지연 환경에서 WebFlux-gRPC 스트리밍으로 블로킹 병목 제거 (0) | 2025.02.23 |