GROWTH LOG STORIES

함께 만든 성장을,
기록으로 연결합니다.

AI와 함께 배우고, 직접 만들고, 서로 나눈
그로스로그 멤버들의 생생한 성장 기록을 만나보세요.

성장 기록
5기+이어진 커뮤니티
HEALTHIO
PROJECT

사진 한 장으로 시작하는 건강 루틴, Health IO

대표 이야기 읽기

ALL STORIES

성장의 순간을
한곳에서 만나보세요.

프로젝트의 시작부터 모임에서 나눈 인사이트까지,
그로스로그가 함께 만든 기록을 모았습니다.
개의 성장 기록

성장일지

[GL 2기] 로그인 실패가 무한 반복된 이유, OOP 면접 정리, 프로젝트 생존법 (성장일지 5회차 큐레이션) 🌱

GROWTH LOG🌱 2026. 8. 7. 04:30
GL 2기 성장일지 5회차 큐레이션

안녕하세요! 그로스로그 입니다!😊 🌱

프레임워크 안쪽까지 따라 들어가 무한 루프를 잡아낸 기록, 면접 대비 개념 정리, 그리고 프로젝트를 끝까지 끌고 가는 법. 2기 5회차입니다.

성장일지는 멤버들이 2주에 한 번씩 자신의 성장을 기록하는 활동이에요. 매 회차 운영진이 모든 글을 읽고, 그중 특히 마음에 남은 글을 함께 골라 큐레이션합니다.

 

01

🔐 스프링 시큐리티 로그인 실패 시 무한 재로그인 문제 해결

오연수 님

이런 내용이에요
리팩터링을 한 뒤로 로그인에 실패하면 실패 응답이 오는 게 아니라 인증을 무한히 다시 시도하다가 타임아웃이 나는 문제를 만납니다.
저자는 디버거를 붙여 스프링 시큐리티의 ProviderManager 내부까지 따라 들어가고, 무한 루프가 생기는 정확한 코드 줄을 찾아냅니다.
그리고 거기서 멈추지 않고 "왜 리팩터링 전에는 멀쩡했는가"까지 추적해 원인을 설명합니다. 프레임워크 소스를 읽어 결론에 도달한 글이에요.

로그인 실패는 원래 가장 조용한 기능입니다. 아이디나 비밀번호가 틀렸으면 "틀렸습니다"를 돌려주고 끝이죠. 그런데 이 글의 상황은 다릅니다. 실패했는데 응답이 오지 않고, 서버가 같은 인증을 계속 다시 시도합니다. 결국 타임아웃이 나죠.

성공은 잘 되는데 실패만 이상한 경우입니다. 이런 버그가 특히 까다로워요. 개발 중에는 대개 성공 경로만 확인하고 넘어가니까요.

조금 더 들어가 보면

스프링 시큐리티에서 인증을 실제로 처리하는 건 AuthenticationManager이고, 그 대표적인 구현체가 ProviderManager입니다.

ProviderManager는 여러 개의 AuthenticationProvider를 들고 있습니다. "아이디/비밀번호로 인증하는 애", "토큰으로 인증하는 애" 같은 것들이요. 요청이 오면 이들에게 차례로 물어봅니다. 하나가 성공하면 거기서 끝, 전부 실패하면 예외를 던지는 구조예요.

여기까지는 단순합니다. 문제는 ProviderManagerparent라는 개념이 하나 더 있다는 점입니다.

parent"내 밑의 provider들이 아무도 처리 못 했을 때 대신 물어볼 상위 관리자"입니다. 여러 개의 보안 설정이 공통 인증 로직을 공유하는 상황을 위한 장치예요. 문서에도 "여러 ProviderManager 인스턴스가 동일한 parent를 공유할 수 있다"고 나옵니다.

의도 자체는 합리적입니다. 그런데 이 글의 상황에서는 parent가 자기 자신이었습니다.

디버거로 확인해보니 ProviderManagerparent가 또 ProviderManager, 그러니까 자기 자신이었던 거예요. 그러면 어떻게 될까요. 인증 실패 → 실패했으니 parent에게 물어봄 → parent는 자신 → 다시 인증 실패 → 다시 parent에게… 실패가 곧 재시도의 조건이 되면서 무한 루프가 됩니다.

성공할 때는 이 경로를 아예 안 탑니다. 그래서 실패할 때만 문제가 났던 거예요.

📝 원문에서는 이 줄에서 멈춥니다

무한 루프를 만든 건 ProviderManager 안의 이 조건문입니다.

if (result == null && this.parent != null) {
    // Allow the parent to try.
    try {
        parentResult = this.parent.authenticate(authentication);
        result = parentResult;
    }
    ...
}

result는 인증에 성공했을 때만 값이 채워집니다. 실패해서 예외가 났으니 계속 null이죠. 그래서 첫 번째 조건은 항상 참입니다. 남은 건 this.parent != null 하나뿐인데, 하필 그게 자기 자신이니 참이 되고 — 그대로 다시 들어갑니다.

해결은 의외로 한 줄입니다.

@Bean
fun authenticationManager(
    http: HttpSecurity,
    loginAuthenticationProvider: LoginAuthenticationProvider,
    bearerTokenAuthenticationProvider: BearerTokenAuthenticationProvider
): AuthenticationManager {
    val authenticationManagerBuilder =
        http.getSharedObject(AuthenticationManagerBuilder::class.java)
    authenticationManagerBuilder
        .authenticationProvider(loginAuthenticationProvider)
        .authenticationProvider(bearerTokenAuthenticationProvider)
    authenticationManagerBuilder.parentAuthenticationManager(null)
    return authenticationManagerBuilder.build()
}

parentAuthenticationManager(null) — 부모를 명시적으로 없앱니다. 그러면 위 조건문의 두 번째 조건이 거짓이 되고, 루프가 사라집니다.

🧭 "왜 전에는 괜찮았을까"까지 간 것

이 글이 좋은 이유는 여기서 한 발 더 나갔다는 점입니다. 고치고 끝냈어도 됐는데, 리팩터링 전에는 왜 멀쩡했는지를 마저 찾아봅니다.

리팩터링 전에는 ProviderManager를 직접 new로 만들고 있었습니다. 그 생성자는 parent 자리에 null을 넣어요. 그래서 애초에 문제가 생길 수 없었습니다.

리팩터링 후에는 AuthenticationManagerBuilder로 바꿨습니다. 그런데 이 빌더는 parent스프링이 자동 설정 과정에서 대신 채워 넣습니다. 저자는 HttpSecurityConfiguration 클래스까지 찾아 들어가 그 지점을 확인해요.

개발자가 쓴 코드는 어디에도 틀린 게 없었습니다. 객체를 만드는 방식을 바꿨더니, 프레임워크가 조용히 채워 넣는 기본값이 달라졌던 거죠.

이게 프레임워크 기반 개발의 가장 어려운 부분입니다. 내가 안 쓴 코드가 내 코드와 함께 돌아갑니다. 평소에는 그게 편의지만, 문제가 생기면 "내가 쓴 것"과 "프레임워크가 채운 것"을 갈라서 봐야 원인이 잡혀요.

큐레이터 노트

멈춰야 할 지점에서 한 번 더 갔기 때문에 뽑았습니다.

무한 루프를 해결한 것만으로도 충분히 좋은 글입니다. 그런데 여기서 그쳤다면 다음에 비슷한 일이 생겼을 때 또 헤맸을 거예요. 원인을 "빌더가 parent를 자동으로 채운다"까지 밝혀뒀기 때문에, 이 지식은 다른 문제에도 그대로 쓰입니다.

스택 오버플로에서 도움받은 과정을 숨기지 않은 점도 좋았어요. 검색어까지 그대로 적어두셨더라고요. 실무는 원래 그렇게 흘러갑니다. 남의 답을 가져오되 내 상황과 맞는지 확인할 수 있는 능력, 그게 앞의 디버깅이 만들어준 것이죠.

시큐리티는 자료가 많은 것 같으면서도 내 설정과 똑같은 사례는 잘 안 나오는 영역입니다. 저자도 글 첫머리에 그 어려움을 적어두셨어요. 그래서 이런 기록 하나가 더 값집니다.

원문 읽으러 가기  ↗devlemon.tistory.com/15
 

02

🎓 객체지향 프로그래밍(OOP) 면접 대비

이범수 님

이런 내용이에요
자바 백엔드 기술 면접에서 자주 나오는 객체지향 질문 15개를 정리한 글입니다. 클래스와 객체, 다형성, 캡슐화, 상속, SOLID, 인터페이스와 추상 클래스, 의존성 주입, 싱글톤까지요.
특징은 개념만 적지 않았다는 점이에요. "프로젝트에 어떻게 적용했는가"에 자기 경험을 붙였습니다.
정답을 외우는 정리가 아니라 말로 설명할 수 있게 만드는 정리입니다.

기술 면접 준비 글은 흔합니다. 그런데 대부분은 정의를 옮겨 적는 데서 끝나요. 이 글은 결이 다릅니다.

조금 더 들어가 보면

면접에서 객체지향 질문이 계속 나오는 이유가 있습니다. 정답이 짧아서가 아니라, 대답의 깊이가 사람마다 확 갈리기 때문입니다.

"다형성이 뭔가요"에 대한 대답은 세 층위로 나뉩니다.

1층은 정의입니다. "같은 인터페이스를 공유하는 객체들이 다르게 동작하는 성질입니다." 맞는 말이지만 외운 티가 납니다.

2층은 구분입니다. 오버로딩과 오버라이딩을 나눠 설명할 수 있는 단계예요. 이 글도 여기를 짚습니다. 이름은 같고 매개변수가 다른 게 오버로딩, 부모의 메서드를 자식이 다시 정의하는 게 오버라이딩.

3층이 경험입니다. "검색 기능에서 조건이 여러 가지라 같은 이름의 메서드를 매개변수만 다르게 뒀습니다." 이 대답부터는 면접관이 되물을 거리가 생깁니다. 왜 그렇게 했는지, 다른 방법은 없었는지. 대화가 되기 시작하는 거예요.

이 글이 15개 질문 대부분에 자기 프로젝트를 붙여둔 게 그래서 의미가 있습니다.

📝 원문에서는 이렇게 씁니다

다형성을 설명하며 든 예시가 명료합니다.

// 레포지토리 인터페이스: SearchRepository
public interface SearchRepository {
    List<Product> search(String keyword);
    List<Product> search(String keyword, String category);
    List<Product> search(String keyword, String category,
                         double minPrice, double maxPrice);
}

이름은 전부 search 하나입니다. 호출하는 쪽은 "검색한다"만 알면 되고, 무엇으로 검색하는지는 넘기는 값이 정합니다. searchByKeyword, searchByKeywordAndCategory처럼 이름을 따로 지었다면 쓰는 쪽이 매번 정확한 이름을 기억해야 했겠죠.

🧭 함께 읽으면 좋은 축

글에 나온 개념들은 따로 노는 게 아니라 하나로 이어집니다.

SOLID의 마지막 D(의존성 역전)의존성 주입(DI), 그리고 싱글톤은 사실 한 줄기예요.

의존성 역전은 "구체적인 것 말고 추상적인 것에 기대라"는 원칙입니다. 그 원칙을 실제로 지키게 해주는 도구가 의존성 주입이고요. 스프링이 생성자로 객체를 넣어주는 그 방식이 바로 이 원칙의 구현입니다.

싱글톤도 마찬가지입니다. 글에서 싱글톤의 문제로 "테스트가 어렵다"를 들고, 해결책으로 "DI를 활용해 컨테이너에서 주입받자"를 답으로 냅니다. 정확한 연결이에요. 직접 만들어 쓰면 바꿔치기가 안 되지만, 주입받으면 테스트에서 가짜 객체로 갈아끼울 수 있으니까요.

싱글톤의 또 다른 답인 무상태(stateless)도 짚어두셨습니다. 하나뿐인 객체가 상태를 들고 있으면 여기저기서 그 상태를 건드려 사고가 나지만, 아예 상태를 안 가지면 그럴 일이 없죠. 스프링 빈이 대부분 상태 없이 설계되는 이유가 이겁니다.

큐레이터 노트

공부 기록이자 자산으로 남을 글이라 뽑았습니다.

면접 준비 노트는 시험 끝나면 버려지기 쉽습니다. 그런데 이 글은 자기 프로젝트 사례가 붙어 있어서, 나중에 다시 읽어도 "내가 그때 이런 판단을 했었지"를 되살릴 수 있어요. 이력서나 포트폴리오를 쓸 때 그대로 재료가 됩니다.

15개를 한 번에 정리한 분량 자체도 좋았습니다. 개념 하나씩 흩어 적으면 나중에 찾기 어려운데, 한 편으로 묶여 있으니 되짚어보기 좋아요.

그리고 이 글이 시리즈의 1편이라는 점이 반가웠습니다. 다음 글은 자료구조와 알고리즘이더라고요. 계속 이어가려는 계획이 보이는 기록이 좋은 기록입니다.

원문 읽으러 가기  ↗velog.io/@beomsu/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%…
 

03

🧭 프로젝트 미세팁

백종오 님

이런 내용이에요
스터디 내부 프로젝트를 앞두고 쓴 글입니다. 기술이 아니라 프로젝트를 끝까지 끌고 가는 방법을 다룹니다.
"모든 기능은 리소스가 필요하다", "계획은 변경하라고 있지만 계획이 없으면 아무것도 할 수 없다", "결국 사람이 하는 일이다" — 네 개의 문장으로 묶여 있어요.
무엇보다 망해본 사람의 글입니다. 실제 시간 계산까지 적혀 있습니다.

기술 블로그에서 보기 어려운 종류의 글입니다. 그런데 개발자가 실제로 가장 자주 겪는 실패는 기술이 아니라 여기서 나옵니다.

조금 더 들어가 보면

글의 첫 번째 문단이 가장 셉니다. 실제 가용 시간을 숫자로 계산합니다.

하루 2시간씩, 주 5일, 12주. 계산하면 120시간입니다. 하루 8시간 기준으로 환산하면 15일이 채 안 됩니다. 12주짜리 프로젝트가 실제로는 보름짜리인 셈이죠.

여기서 환경 설정, 프레임워크 세팅, 기획 논의를 빼면 1주일은 순식간에 사라집니다. 남는 시간이 얼마 없다는 게 숫자로 드러나요.

"12주 남았다"와 "실작업 12일 남았다"는 완전히 다른 감각입니다. 앞의 문장은 사람을 안심시키고, 뒤의 문장은 오늘 무엇을 할지 정하게 만듭니다. 이 계산 하나가 이 글에서 가장 실용적인 부분이에요.

두 번째로 눈에 띄는 건 역량 배분입니다. 기능을 상 2 / 중 3 / 하 5로 나눠 배정하자는 제안인데요, 여기에 중요한 단서가 붙습니다. "기능적 중요도와 개발 난이도는 같지 않다."

이걸 헷갈리면 배분이 어긋납니다. 로그인은 서비스에서 제일 중요한 기능이지만 라이브러리를 붙이면 반나절에 끝나기도 합니다. 반대로 눈에 잘 안 띄는 데이터 동기화가 며칠을 잡아먹기도 하죠. 중요도로 나누면 일정이 무너지고, 난이도로 나눠야 사람이 안 무너집니다.

세 번째는 핵심 기능 선행입니다. 저자는 "완성 못 해도 핵심 기능은 공부가 남는다"고 씁니다.

이건 실무에서 쓰는 표현으로 바꾸면 위험이 큰 것부터 하라(risk-first)입니다. 잘 모르는 부분, 될지 안 될지 확신 없는 부분을 먼저 건드리는 거예요. 쉬운 것부터 하면 진도는 잘 나가는 것 같지만, 어려운 게 뒤에 몰려 있다가 마감 직전에 터집니다.

🧭 그리고 "결국 사람이 하는 일"

마지막 문단은 커뮤니케이션입니다. 저자의 표현이 재미있어요. "분명 한국말인데 무슨 말인지 모르는 기적"을 겪게 된다고요.

여기서 나온 제안이 좋습니다. 클라이언트 역할과 개발자 역할을 둘 다 해보라는 것.

요청하는 쪽에 서보면 "이 정도는 간단하지 않나"가 왜 나오는지 알게 되고, 받는 쪽에 서보면 그 한마디가 왜 3일짜리인지 알게 됩니다. 양쪽을 다 겪어본 사람이 요청을 잘 쪼개서 말합니다. 팀에서 오래 살아남는 사람의 특징이기도 하고요.

큐레이터 노트

성장일지의 성격을 잘 보여주는 글이라 뽑았습니다.

성장일지는 기술 글만 쓰는 활동이 아닙니다. 개발자로 일하며 배운 것이면 무엇이든 기록의 대상이에요. 이 글은 코드가 한 줄도 없지만, 프로젝트를 해본 사람이라면 문장마다 짚이는 데가 있을 겁니다.

무엇보다 직접 망해봤다는 게 글에 드러납니다. "뜨거운 열정만으로 망한 프로젝트들을 많이 시도해보면서"라는 문장이 이 글의 근거예요. 성공 사례보다 실패 사례에서 나온 조언이 더 구체적인 이유입니다.

시작 전에 썼다는 점도 좋았습니다. 끝나고 쓰는 회고는 많지만, 시작하기 전에 "이렇게 하면 망하더라"를 정리해두는 사람은 드물어요. 같은 스터디 사람들에게 실제로 도움이 됐을 글입니다.

원문 읽으러 가기  ↗velog.io/@getbak/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%…

💡 세 편을 겹쳐 읽으며

이번 회차 세 글은 서로 멀어 보입니다. 프레임워크 디버깅, 면접 개념 정리, 프로젝트 운영론.

그런데 나란히 놓으면 하나의 축이 보입니다. 세 글 모두 "보이지 않는 기본값"을 다룹니다.

오연수 님의 글에서 무한 루프를 만든 건 개발자가 쓴 코드가 아니라 프레임워크가 조용히 채워 넣은 parent였습니다. 이범수 님의 글에서 싱글톤이 위험해지는 건 아무도 의도하지 않았는데 전역 상태가 공유되기 때문이고요. 백종오 님의 글에서 프로젝트가 무너지는 건 "12주니까 넉넉하겠지"라는 검증 안 된 전제 때문입니다.

셋 다 "당연히 이럴 것"이라고 믿었던 것이 실제로는 달랐던 이야기예요.

그래서 세 글이 하는 일도 같습니다. 기본값을 꺼내서 눈에 보이게 만드는 것이요. 디버거로 parent를 확인하고, 개념을 말로 설명해보고, 가용 시간을 숫자로 계산합니다.

막연히 알고 있는 것을 명시적으로 만드는 순간, 그게 맞는지 틀린지 비로소 판단할 수 있게 됩니다. 이번 회차 세 글이 각자의 방식으로 그 일을 했습니다.

이번 회차에서 가져갈 것

1. 실패 경로도 테스트하기. 로그인 성공은 다들 확인하지만 실패는 그냥 넘어가기 쉽습니다. 이번 버그는 정확히 그 지점에 있었어요. 잘못된 입력, 권한 없는 접근, 만료된 토큰 — 실패 시나리오도 성공만큼 확인해야 합니다.

2. 프레임워크가 채우는 기본값 의심하기. 코드를 안 바꿨는데 동작이 달라졌다면, 객체를 만드는 방식이 바뀌지 않았는지 보세요. 직접 new로 만들 때와 빌더로 만들 때, 자동 설정이 개입하는 정도가 다릅니다.

3. 개념은 "내 사례"와 붙여서 외우기. 면접에서든 실무에서든 정의만 아는 지식은 되물으면 무너집니다. 배운 개념 하나마다 내 프로젝트에서 그걸 쓴 지점을 한 줄씩 붙여두세요. 그 한 줄이 나중에 대화를 만듭니다.

4. 프로젝트 시작 전에 가용 시간부터 계산하기. "몇 주 남았다"가 아니라 "실작업 며칠"로 환산해보세요. 그 숫자를 보고 나면 기능 목록이 저절로 줄어듭니다. 그리고 어려운 것부터 손대세요.


좋은 글 남겨주신 오연수 님, 이범수 님, 백종오 님께 감사드립니다! 😊

다음 큐레이션으로 다시 찾아오겠습니다! 🌱

- 에디터 · 성장일지 큐레이터 -


※ 본 큐레이션은 각 저자가 공개한 글을 소개하는 것이며, 모든 원문의 저작권은 저자에게 있습니다. 저자 본인의 요청이 있을 경우 즉시 수정 또는 삭제합니다.

WRITE YOUR GROWTH

오늘의 배움을
내일의 성장으로.

기록은 나를 돌아보게 하고, 공유는 우리를 연결합니다.
당신의 다음 성장 이야기도 그로스로그에서 시작해보세요.

커뮤니티 둘러보기