COGO 서버를 개발하며 JwtAuthenticationFilter를 구현하던 중 이전 프로젝트에서 작성했던 코드와 비교를 하며 궁금증이 생겼다.
JwtAuthenticationFilter에서는 Jwt를 파싱해서 스프링 시큐리티에 인증 정보를 주입한다. 이때 데이터베이스를 확인하여 유저가 실제로 존재하는지 체크하고 인증 정보를 주입할까? 아니면 Jwt 토큰을 파싱해서 UserDto를 customOAuth2User(또는 UserDetails)에 넣고 인증정보를 주입할까? 에 대한 고민이 생겼고 어떤 방법이 옳은 방법인지, 혹은 각 방법에 어떤 장단점이 있는지 찾아보게 되었다.
JWT 인증 필터에서의 접근 방식 비교
JWT 토큰을 검증하고 인증 정보를 설정하는 JwtAuthenticationFilter에서 사용할 수 있는 두 가지 주요 접근 방식을 비교해 보자.
1. 토큰 정보만으로 인증
// JwtAuthenticationFilter Class
String username = jwtUtil.getUsername(accessToken);
Role role = jwtUtil.getRole(accessToken);
CustomOAuth2User customOAuth2User = new CustomOAuth2User(username, role);
// CustomOAuth2User Class
public CustomOAuth2User(String username, Role role) {
this.userDTO = UserDto.builder()
.username(username)
.role(role)
.build();
}
이 방식은 JWT 토큰 자체에 포함된 정보만으로 인증을 수행한다. 토큰이 유효하다면 그 안에 포함된 사용자 정보와 권한을 추출하여 인증 객체를 생성한다.
2. 데이터베이스에서 사용자 확인 후 인증
String username = jwtUtil.getUsername(accessToken);
Optional<User> user = userRepository.findByUsername(username);
if (!user.isPresent())
// throw
CustomOAuth2User customOAuth2User = new CustomOAuth2User(
user.get().getUsername(),
user.get().getRole()
);
두 접근 방식의 장단점
토큰 정보만 사용하는 방식:
- 장점:
- 데이터베이스 조회가 없어 성능이 좋음
- 서버 부하 감소
- 토큰 자체가 자기 완결적(self-contained)인 특성에 맞음
- 단점:
- 토큰이 유효한데 사용자가 삭제된 경우 문제 발생 가능
- 사용자 권한이 변경되었을 때 즉시 반영되지 않음
DB 확인 후 인증하는 방식:
- 장점:
- 항상 최신 사용자 정보와 권한으로 인증
- 삭제된 사용자에 대한 즉시 거부 가능
- 단점:
- 매 요청마다 DB 조회 필요 (성능 저하)
- JWT의 장점인 Stateless 특성 약화
추천 접근 방식:
모바일 앱 서비스에서 사용자 정보가 자주 변경되지 않는다면 토큰 정보만 사용하는 방식이 더 효율적이다. 다만 다음과 같은 보완을 고려해 볼 수 있다:
- 블랙리스트 토큰 확인: 로그아웃하거나 삭제된 사용자의 토큰을 Redis와 같은 빠른 저장소에 블랙리스트로 관리
- 짧은 토큰 만료 시간 설정: 액세스 토큰의 만료 시간을 짧게 설정하고, 리프레시 토큰을 통해 갱신하는 방식 사용
- 중요 작업에만 DB 확인: 일반 요청은 토큰만으로 처리하고, 권한 변경이나 민감한 작업에만 DB 확인 추가
결론적으로, 모바일 앱 서비스의 특성과 성능 요구사항을 고려할 때 현재처럼 토큰 정보만으로 인증하는 방식이 적절하며, 필요한 경우 특정 엔드포인트에서만 추가 검증을 수행하는 것이 좋다.
최근에 우아한 기술블로그에서 oauth2와 캐싱에 대한 글을 봤는데, 내 고민과 크게 관련된 글은 아니지만 이 글을 보고 내 고민에서도 캐싱을 적용시키는 것에 대해서 생각해 보게 되었다.
https://techblog.woowahan.com/2617/
aop를 이용한 oauth2 캐시 적용하기 | 우아한형제들 기술블로그
서론 저는 현재 미래사업부문에서 신규 서비스를 만들고 있으며, 신규 서비스는 기존 우아한 형제들에서 서비스중인 배달의민족과 디펜던시를 갖지 않는 독립적인 서비스다 보니 모든 것을 처
techblog.woowahan.com
토큰 정보만으로 인증
String username = jwtUtil.getUsername(accessToken);
Role role = jwtUtil.getRole(accessToken);
CustomOAuth2User customOAuth2User = new CustomOAuth2User(username, role);
캐싱을 사용한 사용자 확인 접근 방식
String username = jwtUtil.getUsername(accessToken);
User user = userCacheService.getUser(username); // 캐시에서 먼저 조회, 없으면 DB 조회 후 캐싱
if (user == null) {
throw new UsernameNotFoundException("사용자를 찾을 수 없습니다: " + username);
}
CustomOAuth2User customOAuth2User = new CustomOAuth2User(user.getUsername(), user.getRole());
두 접근 방식 비교
1. 성능
- 현재 방식:
- 매우 빠름 (O(1) 시간 복잡도)
- DB 조회나 캐시 조회 없음
- 캐싱 방식:
- 캐시 히트 시 빠름 (O(1)에 가까움)
- 캐시 미스 시 DB 조회 필요 (첫 요청 또는 캐시 만료 후)
- 추가적인 메모리와 컴퓨팅 리소스 필요
2. 데이터 일관성 및 보안
- 현재 방식:
- 토큰이 유효하면 인증됨
- 사용자 삭제/비활성화가 있어도 토큰 만료 전까지 인증 가능
- 권한 변경사항이 즉시 반영되지 않음
- 캐싱 방식:
- 캐시 TTL(Time-To-Live) 설정에 따라 최신 사용자 상태 반영
- 사용자 삭제/권한 변경 후 캐시 갱신/만료 시 반영
- 캐시 갱신 정책에 따라 일관성 수준 조정 가능
3. 구현 복잡성
- 현재 방식:
- 매우 단순하고 직관적
- 별도의 캐싱 인프라 불필요
- 캐싱 방식:
- 캐시 설정, 갱신 정책, 오류 처리 등 추가 구현 필요
- Redis 등 캐싱 시스템 설정 및 유지 필요
- 사용자 정보 변경 시 캐시 무효화 로직 필요
4. 확장성
- 현재 방식:
- 서버 확장 시 별도 고려사항 없음
- 토큰 검증만 하므로 부하 적음
- 캐싱 방식:
- 사용자 수 증가에 따라 캐시 크기 고려 필요
- 분산 환경에서 캐시 일관성 유지 고려 필요
- 캐시 서버 확장성 고려 필요
상황별 선택:
캐싱 방식이 적합한 경우:
- 사용자 상태나 권한이 자주 변경되는 서비스
- 높은 보안이 요구되는 서비스 (삭제된 계정의 즉시 접근 차단 필요)
- 이미 Redis 등 캐싱 인프라가 구축되어 있는 경우
- 토큰 만료 시간이 길게 설정된 서비스
현재 방식이 적합한 경우:
- 사용자 상태나 권한이 거의 변경되지 않는 서비스
- 최대 성능이 중요한 서비스
- 토큰 만료 시간이 짧게 설정된 서비스
- 구현 단순성이 중요한 경우
모바일 앱 서비스에서는 대부분 캐싱 방식의 이점이 드러나지 않을 수 있다. 특히 토큰 만료 시간이 짧다면(30분~1시간), 현재 방식의 단순함과 성능 이점이 더 클 수 있다. 다만 사용자 권한 변경이 즉시 반영되어야 하거나 사용자 계정 삭제 후 즉시 접근 차단이 필요한 경우라면 캐싱 방식을 고려해 볼 수 있을 것이다.
'백엔드 > Java + Spring' 카테고리의 다른 글
[Java Spring] COGO 개발일지 - 코드 중복 해결에 대한 고민 해결책 (DI, AOP, 어노테이션) (0) | 2025.03.22 |
---|---|
[백엔드] Blog - 모든 엔티티 생성 시간, 업데이트 시간 설정하기 @CreatedDate, @LastModifiedDate, BaseEntity (1) | 2024.09.24 |
[백엔드] Blog - 전역 예외 커스텀 처리하기 @RestControllerAdvice, @ExceptionHandler (1) | 2024.09.24 |
[백엔드] Blog - 실행 오류 정리 (application.yml, SDK, test) (0) | 2024.09.19 |
[백엔드] 회원탈퇴(Soft delete) 및 회원복구 (1) | 2024.09.08 |