[오류 분석] Spring Security 로그인 시 StackOverflowError 발생 원인 정리

KUN·2025년 5월 30일

개요

Spring Security을 사용하여 로그인을 구현하던 중, StackOverflowError 예외가 발생했다.
어떤 객체가 toString()을 반복적으로 호출하면서 무한 재귀에 빠진 것으로 보인다.


오류

오류 메시지를 보면 singletonInstance가 반복적으로 toString()을 호출하면서 예외가 발생한 것을 확인할 수 있다.
당연하지만... 내 코드에서는 toString()을 명시적으로 호출하지 않았다는 점이다.

가정 #1 ( 실패 )

시도

우선, AuthenticationManager를 설정한 유일한 지점을 확인해보기로 했다.
아래 코드는 @Configuration 클래스 안에 정의된 것으로, 서버가 시작될 때 한 번 실행되며, 요청이 들어올 때마다 실행되는 코드는 아니다.

결과

Parameter 0 of constructor in 
com.newsfeed.testagram.domain.login.controller.LoginController
required a bean of type 'org.springframework.security.authentication.AuthenticationManager' 
that could not be found.

Action:
Consider defining a bean of type
'org.springframework.security.authentication.AuthenticationManager' 
in your configuration.

타깃 VM에서 연결 해제되었습니다. 주소: '127.0.0.1', 전송: '소켓'
종료 코드 1(으)로 완료된 프로세스

예상대로 이건 필수적으로 필요한 메서드이므로, 추가하는 것이 맞다.


가정 #2 ( 실패 )

직접 소스코드를 들어가보기로 했다.
총 2개의 메서드에서 singletoneintance를 사용한다.

제일 의심스러운 부분이다.
하지만, 이것도 곧 아니라는 걸 알게되었다. sigletonInstance 가 null 일 경우에만 생성해주는 경우이다.
우리는 null 이 아니라 무한 호출에 빠졌고, 디버그에서 singleton이 null 이라는 건 보지못했다.

포기하지 않고 계속 들어가도 toString 이 자기 자신을 호출하는 부분을 찾지 못했다.


가정 #3 ( 성공 )

StackOverflow 글을 찾아보기로 했다.
StackOverflowError in Spring Security AuthenticationManager

이글에서 해답을 알아냈다.

문제는 아마도 LoginController에서 다음 코드로 인증을 시도할 때 발생했을 것입니다.

AuthenticationManager.authenticate(..)는 
내부적으로 UserDetailsService.loadUserByUsername(..)를 호출해서
해당 사용자가 존재하는지를 확인합니다. 

따라서 해결 방법은,
UserDetailsService 인터페이스를 구현한 새로운 Service 클래스를 생성하고,
그 안에서 loadUserByUsername(..) 메서드를 구현하는 것입니다.
이렇게 해야 Spring Security가 데이터베이스에서 사용자를 검증할 수 있습니다.

이 글을 통해 알게 된 중요한 사실은,
나는 지금까지 UserDetailsService의 구현체를 만들지 않았다는 점이다.

글 속 설명에 따르면,
AuthenticationManager.authenticate()는 내부적으로
UserDetailsService.loadUserByUsername()를 호출해서 사용자의 존재 여부를 확인한다고 한다.


이 부분이 결정적인 힌트가 되었다.

그렇다면 나도 UserDetailsService의 구현체를 직접 만들어
스프링 시큐리티가 로그인 시 이 구현체를 참조하게 하면
지금 발생한 문제를 해결할 수 있지 않을까?


진짜로 해결이 되었다.


배운 점

  • Spring Security를 처음 접했을 때,
    UserDetailsService라는 인터페이스를 누군가 이미 구현해둔 개인적인 유틸 클래스나 커스텀 코드인 줄 알았다.
    그래서 그동안은 그게 프레임워크와 직접 연결돼 있다는 사실조차 몰랐다.
    그냥 어떤 예제에서 봤던 익숙한 코드일 뿐이라고 여겼던 거다.

    이게 예시로 만든 클레스가 아니라 전부 내부적으로 작동하는 클레스 였다는 걸 알았다.
    즉, 내 문제는 Filter > AuthenticationManager > AuthenticationProvider > ?(이부분) 이 문제였던 것 이였다.

profile
배우노라, 실험하노라, 기록하노라

0개의 댓글