게시: 2025/5/14
By: 황시우
개요
FITDAY에 인증 인가를 개발하는데 있어 관리자 로그인과 OAuth2.0 기반 소셜 로그인을 구현했습니다.
이 과정에서 Factory Method 패턴을 적용해 관리자 로그인, 서드파티 애플리케이션 로그인을 각각 독립적인 핸들러로 분리하였고, 새로운 로그인 방식을 쉽게 추가할 수 있도록 구조화했습니다.
이후 JWT를 발급해 RefreshToken을 Redis로 캐싱 했을때와 DB에 저장했을 때의 응답 속도를 비교 분석하여 캐싱 방식이 실제로 얼마나 성능을 개선하는지 구체적인 수치와 함께 다뤄봤습니다.
Factory Method 패턴이란?
- 객체 생성 코드를 별도의 팩토리 클래스나 메서드로 분리하여, 객체 생성의 책임을 위임하는 디자인 패턴입니다.
- 인터페이스나 추상 클래스를 정의하고 이를 구현한 클래스가 필요할 때 팩토리를 통해 생성합니다.
즉, 여러개의 서드파티 애플리케이션(Naver, Google 등)을 추가할 때 별도의 노력없이 쉽게 추가할 수 있습니다.
Factory Method 개요
구성 요소 | 역할 |
Product | 생성될 객체의 인터페이스 또는 추상 클래스 |
ConcreteProduct | Product 인터페이스를 구현한 실제 클래스 |
Creator | Product를 반환하는 Factory Method 선언 |
ConcreteCreator | Factory Method를 구체적으로 구현하여 ConcreateProduct 생성 |
FITDAY에 적용한 Factory Method 패턴 개요
구성요소 | 역할 |
Product | AuthService, OAuth2AuthService |
ConcreteProduct | LocalAuthService (admin), KakaoAuthService (kakao) |
Creator | AuthServiceFactory |
ConcreteCreator | AuthServiceFactory에 주입된 AuthService, OAuth2AuthService 빈 |
Factory Method를 활용한 로그인 과정
우선 kakao로 소셜로그인 과정에 대해 설명해 드리겠습니다.
티스토리에서 코드블럭을 사용하면 코드가 잘 보이지 않아서 스크린샷으로 대체하였습니다.
Controller
Creator (Factory)
ConcreteProduct (kakao)
위 사진에서 kakao로 요청이 들어온다면, 팩토리를 호출하여 스프링 컨텍스트에 등록된 AuthService 구현체들 중 키가 kakao인 빈을 꺼내고 해당 빈을 OAuth2AuthService로 캐스팅하여 반환합니다.
(관리자 로그인과 소셜로그인을 분리하기 위해 OAuth2AuthService는 AuthService를 상속했습니다.)
이후 getAuthorizationUri 를 통해 리다이렉트 URI를 생성하여 반환합니다.
Controller
KakaoService
kakao develper에서 리다이렉트 URI를 설정해놓고, 사용자가 kakao 로그인 완료 시 설정된 callback URI로 인가코드를 전달받습니다.
이후 Factory Method 패턴에서 동일한 방식으로 kakao용 AuthService 빈을 가져와 authenticate 메서드를 호출합니다.
이 과정에서 kakao AccessToken을 발급받고 사용자 정보를 가져온 뒤, RefreshToken과 함께 DB에 저장하고 FITDAY 전용 JWT AccessToken을 생성해 클라이언트로 전달합니다.
추후에 Google을 적용하고자 한다면, GoogleAuthService 구현체만 추가한다면 빠른시간에 확장할 수 있습니다!
로그아웃 과정
사용자가 로그아웃을 한다면 어떻게 처리하는지 고민해 봤습니다.
두 가지 방안이 있는데,
1. AccessToken 블랙리스트 저장
블랙리스트로 저장하면 로그아웃 즉시 토큰 무효화를 할 수 있습니다.
따라서 보안에 장점이 있지만, 매 요청마다 조회 비용이 추가로 발생한다는 단점이 있습니다.
2. 짧은 TTL에 의존하기
이 방법은 AccessToken의 만료 시간을 1분 ~ 5분 정도로 짧게 설정하는 것입니다.
이후 로그아웃 시 클라이언트가 토큰을 즉시 버리도록 유도합니다. (localStorage.removeItem)
이 방법의 장점은 구현이 간단하지만 단점은 만료 전까지는 탈취된 토큰으로 요청이 가능하다는 점입니다.
FITDAY의 특성상 보안에 덜 민감한 콘텐츠 이므로 AccessToken 만료 시간을 5분으로 설정해서 사용하기로 결정하였습니다.
추후에 서비스를 보안해서 추가한다면 필요시 1번의 방법으로 리팩토링 할 예정입니다.
Redis 캐싱 vs DB 저장
테스트 목적
RefreshToken을 발급 후 Redis 캐시와 DB에 각각 저장했을 때, 로그인 API의 응답지연에 어떤 차이가 나는지 비교해 보겠습니다.
테스트 환경
모니터링 도구 | 인스턴스 |
Pinpoint | API 서버 : t3a.small, Pinpoint 서버 : t3.medium |
- t3a.small => CPU 2코어, 2GB 메모리
- t3.medium => CPU 2코어, 4GB 메모리
테스트 시나리오
FITDAY API에 요청을 보내 Redis 캐싱 방식과 DB 저장 방식으로 각각 처리한 뒤, Pinpoint-Agent로 응답 시간을 수집합니다.
DB 저장
위 사진에서 DB에 저장 했을때는 네트워크 요청은 차이가 없지만, 트랜잭션이 발생해 233 ms가 추가로 발생한 것을 확인할 수 있었습니다.
Redis 캐싱
Redis 캐싱, DB 저장 부하 테스트
최종적으로 DB에 저장한 경우에는 410 ms, Redis 캐싱한 경우에는 231 ms가 소요되어 응답 속도가 약 43.7 % 단축된 것을 확인 할 수 있었습니다!
마지막으로
조회 수 캐싱 전략, hot, recent 캐싱 전략을 거쳐 AccessToken 캐싱을 경험해 보았는데, 상황에 따라 캐싱을 적용하면 성능이 월등하게 높아진다는 것을 눈으로 확인할 수 있었던 경험이었습니다.
FITDAY 서버 개발을 마무리 하였는데, 앞으로 프론트를 공부하면서 필요한 기술들이 생기면 추가로 계속해서 개발을 할 예정입니다.
문제를 스스로 고민하고 해결하는 과정을 통해 개발자로서 자신감이 크게 향상되었고, 전반적으로 매우 의미있고 좋은 경험이었습니다.
참고
'프로젝트' 카테고리의 다른 글
[FITDAY] 99.98% 성능 개선 최적화 여정 (2) | 2025.05.15 |
---|---|
[FITDAY] CI/CD 자동화 여정기 (0) | 2025.05.01 |