GPT 요약

My-Books 프로젝트에서 Spring Security를 검토했지만, MSA 환경에서의 인증정보 불일치와 자원 소모 문제로 사용하지 않았습니다.
대신, 간단한 인증/인가 요구사항을 충족하기 위해 Spring AOP를 활용해 효율적인 인가 처리를 설계했습니다.
이를 통해 사용자 경험을 개선하고 시스템 자원 소비를 최소화했습니다.

My-Books 프로젝트에서의 경험을 다룬 글입니다.

My-Books 프로젝트에서 저의 주된 역할은 인증/인가 프로세스를 설계하고 구현하는 것이었습니다.
처음에는 당연히 Spring Security를 기반으로 구현하려 했지만 결과적으로는 선택하지 않았는데요 그 과정을 얘기하려 합니다.

Spring Security란?

Spring Security는 Spring Framework 기반의 애플리케이션에서 인증(Authentication)과 인가(Authorization)를 손쉽게 구현할 수 있도록 제공되는 강력한 보안 프레임워크입니다.

사용자가 애플리케이션에 로그인하면, Authentication 객체가 생성되며 Authentication 객체는 Spring Security의 SecurityContext에 저장되어 애플리케이션 전반에서 사용자의 인증 상태를 관리합니다.

또한 Filter Chain을 통해 모든 HTTP 요청을 가로채고, 보안 검사를 수행합니다.

사용하지 않은 이유

Spring Framework에 완벽하게 호환되면서 쉽게 구현이 가능하다니.. 사용하지 않을 이유가 없어 보입니다.
하지만 다음과 같은 이유로 사용하지 않았습니다.

기존의 Spring Security를 활용한 접근 방식에서는 다음과 같은 문제가 있었습니다.

Security Context의 오버헤드

Security Context는 기본적으로 세션에 저장하여 상태를 유지하기 때문에 MSA로 구성된 My-Books 프로젝트에서는 인증정보 불일치 문제가 발생 가능합니다.

이 문제는 SessionCreationPolicyStateless하게 설정하면 해결이 가능하지만
세션을 사용하지 않을 뿐 매 요청마다 SecurityContext를 생성하기 때문에 추가적인 자원 소비가 이뤄지게 됩니다.

이는 서버에 불필요한 오버헤드를 발생시켰고, MSA 환경에서 효율적이지 않다고 판단했습니다.

과정의 복잡성

Spring Security는 내부적으로 DelegatingFilterProxy가 요청을 먼저 받고, 이후 여러 Security 필터를 거치는 구조로 동작합니다. 이 과정이 간단한 인가 로직을 구현하는 데 불필요하게 복잡하다고 생각했습니다.

테스트 코드의 생산성 저하

Security가 활성화된 상태에서는 테스트 코드 작성 시 Security 옵션을 비활성화하거나, Security가 제공하는 User 객체를 주입해야 했습니다. 이는 유닛 테스트 시 불필요한 설정을 요구해 생산성을 떨어뜨렸습니다.

해결 방법

위 문제를 해결하기 위해 다음과 같이 인증/인가 프로세스를 다음과 같이 설계했습니다.
1. JWT 기반 인증:
• 로그인 시, 액세스 토큰을 쿠키에 저장하고 리프레시 토큰을 Redis에 저장.
• 모든 요청 시 클라이언트는 액세스 토큰을 헤더에 담아 전송.
2. Spring Gateway에서 인가 처리
• Gateway의 AbstractGatewayFilterFactory를 상속받아 커스텀 필터를 구현
• 이 필터는 헤더에서 JWT를 추출하고, 유효성을 검증한 후 권한을 확인

하지만 이 과정에서 예외사항에 대한 처리의 필요성이 생겼고(Invalid 토큰,유효기간 만료,권한 불일치..) 이를 해결하기 위해 횡단 관심사를 처리할 수단을 찾게됐습니다.

Filter vs Interceptor vs AOP

Spring Security의 대안으로 생각한 것은 Filter, Interceptor, AOP였습니다.
위에서 언급한 것들은 모두 횡단 관심사를 처리할 수 있는 수단으로, 인가 처리에 사용 가능합니다.

이들은 비슷해 보이지만 차이점이 존재했는데요. Filter는 Spring 스펙이 아닌 Servlet의 스펙으로, DispatcherServlet 이전에 실행되어 모든 HTTP 요청을 대상으로 작동합니다. 그렇기 때문에 Filter는 Spring 컨테이너와 무관하게 작동하므로, Spring Bean이나 컨트롤러 또는 서비스 계층의 로직과 연동된 처리는 어렵습니다.

Interceptor는 Spring MVC에서 제공하는 기능으로, 컨트롤러 실행 전후에 추가 로직을 삽입하는 데 사용됩니다.
Spring MVC 컨트롤러 수준에서 동작하기 때문에 Filter보다 Spring Application과 밀접하게 협력한다는 장점은 있지만, 컨트롤러보다 추상화 정도가 낮은(실제 비즈니스 로직이 존재하는) 서비스 계층에서의 상세한 처리는 불가능합니다.

또한, Filter와 Interceptor 모두 요청 전과 후에 대한 처리를 지원하지만, 이를 하나의 메서드에서 전후를 동시에 처리하는 방법은 없기 때문에 AOP를 선택하게 됐습니다.

내가 사용한 Spring AOP

현재 My-Books는 Front Server에서 사용자 요청 시 Gateway Server로 전달되어 인가 처리를 하는 구조를 가집니다.

또한 인가 처리 실패 시 에러가 발생하고 Front Server는 그에 따른 알맞은 응답을 반환해 사용자의 사용성을 높이는 목표가 존재했습니다.따라서 호출 후 에러발생에 따른 추가적인 로직 실행이 가능해야 됐으므로 Spring AOP의 @Around를 사용하기로 결정했습니다.

인가 처리가 실패하는 경우에 따라서 액세스 토큰을 재발급하거나 특정 페이지로 보내는 등의 작업을 상세 설계하여
사용자의 사용성을 높일 수 있었습니다.

이와 관련해 추가적인 링크 남기면서 마무리 하겠습니다.

+ Recent posts