Projects/[Spring] Payment System Project(02FEB26~

[결제 시스템 프로젝트] 로그인 시 StackOverFlowError 해결하기

montmer27 2026. 2. 6. 22:33

상황

회원가입 후로그인 시 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)은 설계도 밖에서 가져오는 것이 가장 안전하다.