Spring Cloud Gateway에서 Passport기반 내부 인증 시스템 구현하기

full-boram·2025년 3월 11일
1
post-thumbnail

배경

아키텍처

스마일게이트 부트캠프를 진행하며 주식 기반 채팅 서비스 주톡피아를 구현했다. 당시 MSA구조로 아래와같이 아키텍처 설계를 진행했다.

고민

MSA 환경에서 각 마이크로서비스는 독립적으로 동작하지만, 인증과 인가는 공통된 요구 사항이었고, 인증서버를 담당한 나는 “어떻게 하면 효율적으로 인증/인가를 진행할 수 있을까?”하는 고민이 생겼다!

또한 현재는 모든 서버가 Spring Boot 기반으로 운영되고 있었지만, 향후 서비스가 확장되면서 다른 프레임워크가 도입될 가능성도 있었기 때문에, 기존 JWT 기반 인증 방식을 어떻게 유연하게 적용할 수 있을지에 대한 확장성도 함께 고민하게 되었다.

처음 생각

처음에는 두가지 방식이 생각났다.

  1. 각 서비스에서 인증/인가를 개별적으로 처리하자

  2. 인증 서버를 매번 호출하자

하지만 아무리 생각해봐도 인증 로직이 중복되어 있어 유지보수성과 확장성이 떨어지고, 보안 측면에서도 허가되지 않은 요청이 서비스에까지 도달할 위험이 있다는 문제가 느껴졌다.

구현

이를 해결하기 위해 토스에서 사용하는 방식을 참고하여 Gateway와 인증 서버에 Passport를 도입하여 인증 및 인가를 수행하였다.(여담이지만 캠프 당시 기술 블로그, 유튜브를 참 많이 참고했던것같다..!!!)

Passport는 MSA 환경에서 통합된 인증 정보를 관리하는 방식으로, JWT 기반으로 사용자의 정보를 포함하며 Gateway에서 인증을 수행하고 이후의 요청에서는 Passport를 활용하여 인가를 처리한다. 이 과정에서 Spring Cloud Gateway의 비동기 방식을 고려하여 ConcurrentHashMap + Mono 방식으로 구현하였다.

또한 Gateway에서는 JWT 검증 및 인가 과정을 자동화하기 위해 세 개의 필터 (JwtAuthenticationFilter, PassportAuthorizationFilter, PassportRelayFilter) 를 구현하였다. 이로써 인증 및 인가 과정을 통합적으로 관리할 수 있게 되었고, 그 결과 유지보수성과 확장성이 크게 향상되었다. 추가적으로, 팀 내 생산성을 높이기 위해 @CurrentUser 커스텀 어노테이션을 제공하여, 컨트롤러에서 사용자 정보를 쉽게 주입받을 수 있도록 하였다.

아래에선 구현과정을 상세히 설명해보겠다!


주제

Spring Cloud Gateway 기반 passport 개념을 활용한 내부 인증 시스템 구현기


내용

1. 도입 : Passport 개념과 중요성, MSA 구조에서의 필요성

1.1 인증(Authentication)과 인가(Authorization)의 중요성

앞에 설명한 배경을 갖고 Passport 개념을 활용한 내부 인증 시스템을 구현했다! 구현으로 이어지기전에 난 우선 인증과 인가의 개념을 정리했고 각 역할에 맞게 인증과 인가 로직을 분리하여 설계하고자 했다. 개념을 알아야 gateway에서 진행해야할지 인증서버에서 진행해야할지 정할 수 있다고 생각했기 때문이다.


인증(Authentication)은 사용자의 신원을 확인하는 과정이며, 인가(Authorization)는 해당 사용자가 특정 리소스에 접근할 수 있는지를 결정하는 과정이며 자세히 알아보자.


인증(Authentication)은 누가 담당할까?
"인증"이란?
사용자가 올바른 로그인 정보를 입력했는지 확인하고, JWT를 생성해 사용자 정보를 Passport 형태로 관리하는 기반을 마련하는 과정이다.

담당: "Auth 서버"

  • 사용자가 로그인하면 Auth 서버가 JWT를 생성하여 반환한다.
  • JWT에는 사용자의 정보 (예: userId, role, 권한 등)가 포함된다.
  • 이후 API 요청을 할 때, 클라이언트는 JWT를 Authorization 헤더에 담아서 요청한다.
    즉, Auth 서버는 사용자의 신원을 확인하는 인증의 역할을 한다.

인가(Authorization)는 누가 담당할까?
"인가"란?
인증된 사용자가 요청한 리소스에 접근할 권한이 있는지 검증하는 과정이다.
예를 들면, 일반 사용자가 관리자 페이지에 접근하면 안되고, 올바른 사용자가 아닐 경우 서버에 접근을 할 수 없어야한다.

담당: "Gateway + 개별 서비스"

(1) Gateway에서 1차 인가 수행

  • JWT와 JWT로 만들어진 Passport를 검증하고, 사용자의 기본적인 권한을 확인한다.
  • 예를 들어, 로그인이 필요한 API인지 확인하거나 토큰이 유효한지 검증하는 역할이다.
  • Passport의 주요 데이터를 HTTP 헤더에 추가하여 개별 서비스로 전달함으로써, 이후 서비스들이 별도로 사용자 정보를 조회하지 않고도 인가 처리를 수행할 수 있게 한다.
    ✅ 즉, Gateway는 기본적인 접근 제한을 담당하고, Passport를 개별 서비스로 전달하는 역할을 한다.

(2) 개별 서비스에서 2차 인가 수행

  • 서비스별로 세부적인 권한을 다시 한 번 검증해야 한다.
  • 예를 들어, 게시글을 수정하려고 할 때, 해당 게시글의 작성자인지 확인하는 과정이 필요할 수 있다.
  • 이 단계에서 Passport의 정보를 활용하여 세부적인 권한을 검증한다.
    ✅ 즉, 최종적으로 개별 서비스가 세부적인 권한을 확인하여 리소스 접근을 결정한다.

단계담당역할
1. 인증 (Authentication)Auth 서버로그인 시 JWT 또는 Passport 발급
2. 1차 인가 (Authorization)GatewayJWT/Passport 검증 및 기본적인 접근 제한
3. 2차 인가 (Authorization)개별 서비스서비스별 세부적인 권한 확인 (예: 리소스 소유 여부 등)

결론적으로 "인증"은 Auth 서버가 담당하고, "인가"는 Gateway와 개별 서비스가 함께 담당하는 구조로 구현하였다!




1.2 Passport란?

앞서 설명한 인증과 인가의 역할을 명확히 구분하고, 이를 효율적으로 처리하기 위해 도입한 것이 바로 Passport다.

처음에는 단순히 JWT만으로 인증/인가를 처리하려 했지만, 서비스가 많아지고 각 서비스에서 JWT를 검증하거나 파싱하는 로직이 중복되면서 점차 구조가 복잡해지고 유지보수가 어려워졌다.

그래서 생각한 것이 바로 "Passport라는 단일한 인증 정보 관리 단위"를 만들어 모든 서비스를 관통하는 인증 기준점으로 삼는 것이었다.


Passport는 MSA 환경에서 통합된 사용자 인증 정보를 관리하는 구조적 방식이다. JWT 기반으로 만들어지며, 한 번 인증된 사용자 정보를 Gateway를 통해 Passport 형태로 변환하고, 이후 요청에서는 해당 Passport를 기반으로 인가 과정을 처리한다.

쉽게 말하면, 여권(Passport)을 한 번 발급받으면, 입국심사(Gateway)에서 확인하고 나서부터는 어느 나라(서비스)를 방문해도 더 이상 매번 신분증을 꺼낼 필요가 없는 구조인 셈이다.

기술적으로는 JWT에 담긴 사용자 정보(예: userId, role, 권한 등)를 Passport 객체로 변환하고, 이를 Gateway에서 1차 검증 및 Relay 처리한 뒤, 개별 서비스에서는 해당 정보를 바탕으로 2차 인가를 수행하도록 설계하였다.



1.3 MSA 구조에서 Passport가 필요한 이유

그렇다면 왜 굳이 Passport라는 구조를 도입해야 했을까? 처음엔 단순히 JWT만으로 충분하지 않을까 생각했다. 하지만 실제로 MSA 환경에서 아래와 같은 문제들이 발생하며 Passport의 필요성이 더욱 명확해졌다.


1. 중앙 집중형 인증 및 인가 구조 필요

MSA 구조에서 각 서비스가 독립적으로 운영되는 만큼, 인증/인가 로직까지 각자 처리하게 되면 코드가 중복되고 일관성이 사라지게 된다.

Passport를 도입하면서 우리는 Gateway에서 인증을 단일화하고, 그 결과로 생성된 Passport를 모든 서비스에서 공통적으로 활용할 수 있게 되었다.

이를 통해 인증은 한 곳에서 관리되고, 각 서비스는 Passport를 통해 필요한 인가 정보만 검증하면 되므로 로직이 단순화되고 유지보수가 쉬워졌다.


2. 성능 최적화

앞서 생각한 방식으로 구현했다면 서비스마다 JWT를 파싱하거나, 사용자의 권한을 검증하기 위해 DB를 조회하는 일이 빈번했을 것이다. 하지만 Passport를 도입할 경우에는 JWT 기반 Passport를 Gateway에서 한 번만 검증하고, 이후 요청은 캐싱된 Passport 정보를 바탕으로 처리가 가능해져 성능적 이점이 예상되었다.


3. 보안성 강화

JWT는 내부에 사용자 정보를 담고 있고, 서명을 통해 변조를 방지할 수 있다. 이를 기반으로 한 Passport는 위·변조 방지와 사용자 검증이라는 두 가지 목적을 동시에 만족시킨다.

Gateway에서 JWT의 유효성과 서명을 검증한 뒤, 유효한 사용자 정보만 Passport로 변환하여 하위 서비스로 전달되도록 했기 때문에, 허가되지 않은 요청이 내부 서비스까지 도달하는 것을 방지할 수 있고, 전체 서비스의 신뢰성을 높일 수 있었다.





2. 구현 방식 비교: 왜 ConcurrentHashMap + Mono였을까?

Passport 개념을 실제로 구현하기 위해 여러 방식을 검토하게 되었다.

초기에는 단순하게 ThreadLocal을 활용하는 구조로 접근했지만, WebFlux 기반의 비동기 환경이란 점을 기준으로 ConcurrentHashMap + Mono 조합이 가장 안정적이고 확장 가능한 방식이라는 결론에 도달했다.


2.1 ThreadLocal 기반 인증 시스템

처음에는 기존의 Spring MVC 스타일처럼, ThreadLocal을 활용해 인증 정보를 저장하는 구조를 고려했다.

이를 위해 별도의 JootalkpiaAuthenticationContext 클래스를 만들어 인증 정보를 담았고, 이후 각 요청에서 이 Context를 통해 사용자 정보를 꺼내 쓸 수 있도록 했다.

하지만 이 방식은 MSA 환경, 특히 Spring WebFlux의 비동기 구조와 맞지 않는다는 한계를 예상했다.

  • WebFlux와의 비호환성: ThreadLocal은 스레드 단위로 데이터를 저장하기 때문에, 이벤트 루프 기반의 비동기 처리 모델에서는 일관된 데이터를 유지하기 어렵다.
  • 동시성 이슈: 여러 요청이 하나의 스레드를 공유하거나, 동일한 Context를 참조하게 되면 예상치 못한 인증 정보 누수 혹은 충돌이 발생할 수 있다.
  • 유지보수 및 확장성 문제: 각 서비스마다 인증 컨텍스트를 관리해야 하기 때문에, 구조가 복잡해지고 수정이 어려워진다.

이러한 문제들을 때문에 WebFlux 환경에 완벽히 호환되면서도 안전한 인증 정보 공유 방식을 고민하게 되었다.



2.2 왜 ConcurrentHashMap + Mono 방식이 최적일까?

결국 선택한 방식은 ConcurrentHashMap + Mono 조합이었다.

Gateway에서 JWT를 검증한 후, 사용자 정보를 기반으로 생성한 Passport를 ConcurrentHashMap에 캐싱하고,

서비스 간 전달 시에는 Mono를 통해 비동기적으로 처리하는 구조로 구현했다.

이 방식은 WebFlux의 비동기 흐름을 그대로 따르면서도, 인증 정보를 안전하게 공유할 수 있는 구조였다.


방식 비교

항목ThreadLocal 기반 (Spring MVC)ConcurrentHashMap + Mono (Spring WebFlux, MSA)
Passport 저장 방식ThreadLocal (스레드 단위 저장)ConcurrentHashMap + Mono (전역 캐싱 + 비동기 처리)
데이터 검증 방식HMAC 서명 (SHA-256)HMAC 서명 (SHA-256)
Passport 검증 위치각 서비스 내부Gateway 필터에서 검증 후 전달
JWT 검증 방식미구현 (직접 구현 필요)Auth 서버에 WebClient로 비동기 요청
Reactive 지원 여부❌ (Spring MVC 기반, 블로킹)✅ (Reactor 기반 완전 지원)

정리

  • 비동기 처리에 최적화: Mono를 활용해 인증 정보도 완전히 비동기 흐름 안에서 처리 가능
  • 전역 공유 구조: ConcurrentHashMap으로 인증 정보(Passport)를 캐싱하여 효율적인 접근 보장
  • Spring Cloud Gateway와의 자연스러운 호환성: 필터 체인에서 인증 정보 조회 및 전달이 간결해짐
  • 보안성 확보: JWT를 Auth 서버와 연동하여 서명 기반 검증 → 신뢰할 수 있는 Passport 생성 가능

결과적으로 이 구조를 통해 비동기 환경에서 안정적이고 확장 가능한 인증 시스템을 갖출 수 있었고,

나아가 각 서비스가 인증 정보를 직접 다루지 않아도 되어 인증 로직의 일관성과 재사용성까지 확보할 수 있었다.




3. Passport 기반 인증 및 인가 시스템

3.1 Passport 인증 과정

Passport 기반 시스템에서는 인증 흐름이 다음과 같이 구성된다:

  1. Gateway가 클라이언트 요청의 Authorization 헤더 확인
    클라이언트가 JWT를 담아 요청하면 Gateway에서 이를 추출하여 유효성을 판단한다.
  2. Passport가 없거나 만료된 경우
    Gateway는 Auth 서버의 /validate API를 호출하여 JWT를 검증하고, 그 결과로부터 Passport를 생성한다.
  3. Passport 기반 인가 수행
    발급된 Passport 정보를 캐싱하고, 이를 바탕으로 사용자 권한을 확인하며 인가 처리에 활용한다.
    최종적으로 사용자 정보를 UserInfo 객체에 저장하여 이후 컨트롤러나 서비스에서 활용 가능하게 한다.




4. Custom Annotation: @CurrentUser 도입

4.1 @CurrentUser 구현 방식

Passport 기반 인증 시스템을 도입하면서, 컨트롤러 단에서 사용자 정보를 보다 간결하고 일관된 방식으로 주입받기 위해 커스텀 어노테이션인 @CurrentUser를 구현했다.

  • Spring MVC의 HandlerMethodArgumentResolver를 확장한 CurrentUserArgumentResolver를 정의하여,
  • Gateway에서 전달한 X-Passport-User 헤더를 파싱하고, 이를 UserInfo 객체로 변환한 뒤 컨트롤러 파라미터에 자동 주입한다.

이를 통해 각 컨트롤러마다 별도로 JWT 파싱 로직을 구현할 필요 없이, 단순히 어노테이션만 붙이면 Passport 정보를 활용할 수 있게 되었다.


4.2 @CurrentUser 사용 예시

@GetMapping("/api/v1/user/me")
public ResponseEntity<UserInfo> getUserInfo(@CurrentUser UserInfo userInfo) {
    return ResponseEntity.ok().body(userInfo);
}

이처럼 @CurrentUser를 활용하면 사용자 인증 정보가 필요한 모든 API에서 코드 중복 없이 간결하게 사용자 정보를 주입받을 수 있다.





5. Gateway 필터 구성: Passport 기반 흐름

5.1 Gateway 필터의 역할 및 흐름

Passport 인증 시스템의 핵심은 Spring Cloud Gateway에서 동작하는 세 개의 필터이다. 각 필터는 인증과 인가 과정을 책임지는 역할로 나뉘며, 필터 체인 순서대로 다음과 같이 동작한다:

  1. JwtAuthenticationFilter
    클라이언트 요청에서 Authorization 헤더(JWT)를 추출하고, Auth 서버로 검증 요청을 보낸다.
    검증이 성공하면 Passport를 생성하고 이후 필터로 전달한다.
  2. PassportAuthorizationFilter
    생성된 Passport를 기반으로 사용자의 인증 정보를 추출하고, 이를 UserInfo 형태로 가공하여 요청 컨텍스트에 저장한다.
  3. PassportRelayFilter
    가공된 Passport 사용자 정보를 X-Passport-User 헤더에 실어 downstream 서비스로 전달한다.

이 구조 덕분에 모든 인증 로직은 Gateway에서 사전 처리되고, 개별 서비스는 인가와 도메인 로직에만 집중할 수 있게 되었다.




6. 기대 효과 및 결론

6.1 기대 효과

  • 비동기 WebFlux 환경에 최적화되어 높은 성능을 유지할 수 있다.

  • 인증/인가 로직이 통합되어 유지보수가 쉬워지고 확장성 확보가 가능하다.

  • @CurrentUser 어노테이션 도입으로 컨트롤러 코드가 간결해지고 개발 생산성이 향상되었다.

Spring Cloud Gateway를 중심으로 한 인증 구조 덕분에 서비스 간 일관된 인증 체계가 정립되었다.


6.2 결론

Passport 기반 인증 시스템은 MSA 환경에서 중앙 집중화된 인증 관리, 높은 성능, 보안성, 그리고 확장성을 모두 만족시킬 수 있었다.

이 시스템을 통해 인증/인가 구조를 체계적으로 정비할 수 있었고,

향후 새로운 서비스가 추가되더라도 공통된 인증 흐름 속에서 손쉽게 통합될 수 있도록 기반을 마련했다.

앞으로도 사용자 경험을 해치지 않으면서도 더욱 안전하고 유연한 인증 체계를 만들어가는 것이 목표이다.


profile
공유로 가득찬 사람이 되고싶습니다.

0개의 댓글