상황
회원가입 후로그인 시 StackOverFlowError 발생


에러 로그 상세
2026-02-06T20:20:55.500+09:00 ERROR 28732 --- [payment-team-project] [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed: java.lang.StackOverflowError] with root cause
java.lang.StackOverflowError
at java.base/java.lang.String.equals(String.java:1834) ~[na:na]
at org.springframework.util.ReflectionUtils.isEqualsMethod(ReflectionUtils.java:510) ~[spring-core-7.0.3.jar:7.0.3]
at org.springframework.aop.support.AopUtils.isEqualsMethod(AopUtils.java:167) ~[spring-aop-7.0.3.jar:7.0.3]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:174) ~[spring-aop-7.0.3.jar:7.0.3]
at jdk.proxy2/jdk.proxy2.$Proxy147.authenticate(Unknown Source) ~[na:na]
at jdk.internal.reflect.GeneratedMethodAccessor41.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
원인 분석
초기 추정 원인들
SecurityConfig.java 파일의 UserDetailsService 중복 정의 및 순환 참조
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
UserDetails admin = User.builder()
.username("admin@test.com")
.password(passwordEncoder.encode("admin"))
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(admin);
}
InMemoryUserDetailsManager가 Bean으로 등록되었기 때문에, 별도로 구현한 CustomUserDetailsService가 무시된다.
1. SecurityConfig를 만드는 과정에서 authenticationManager를 구현해야 함.
2. Spring은 이를 위해서 SecurityConfig 내부에 구현된 userDetailsService를 찾아 호출
3. 그러나 non-static 메서드인 관계로 다시 SecurityConfig 객체를 생성하는 과정(1단계)으로 돌아감 (생성자 수준의 무한 루프)
*static으로 선언되어 있었다면 루프를 피할 수도 있었겠지만, 보안 설정상 권장되지는 않음.
그렇다면 왜 스프링은 외부에 CustomUserDetailsService가 빈으로 등록되어 있었음에도 불구하고 이를 무시했을까?
스프링의 우선순위와 명시적 설정 원칙
1. 명시적 빈 참조 : 만약 설정 파일 내부에 UserDetailsService가 빈으로 정의되어 있다면, 스프링은 이를 우선적으로 선택함.
2. 자동 검색 : 내부에 아무것도 없을 때만 컴포넌트스캔으로 등록된 외부 빈을 찾아 나섬
(이 경우 컴포넌트스캔으로 CustomUserDetailsService 빈이 등록되어 있었음에도 불구, 설정 클래스에서 직접 정의한 빈을 우선함)
CustomUserDetailsService.java
package com.paymentteamproject.security;
import com.paymentteamproject.domain.user.entity.User;
import com.paymentteamproject.domain.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
// DB에서 사용자 조회
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new IllegalStateException("사용자를 찾을 수 없습니다."));
// Spring Security에서 사용하는 UserDetails 객체로 변환
return org.springframework.security.core.userdetails.User.builder()
.username(user.getEmail())
.password(user.getPassword())
.authorities("ROLE_USER")
.build();
}
}
해결 방법
SecurityConfig에서 InMemoryUserDetailsManager 부분을 제거하고, 스프링이 우리가 만든 CustomUserDetailsService를 자동으로 찾아 쓰도록 한다.
결과
로그인 성공
인사이트
설정(Configuration)은 조립도일 뿐이며, 실제 부품(Service)은 설계도 밖에서 가져오는 것이 가장 안전하다.
'Projects > [Spring] Payment System Project(02FEB26~' 카테고리의 다른 글
| Troubleshooting: 로그인 성공 후 페이지 튕김 및 API 응답 검증 오류 해결 (0) | 2026.02.10 |
|---|---|
| Troubleshooting: JWT 액세스 토큰 자동 갱신(Silent Refresh) 실패 이슈 (0) | 2026.02.09 |