카테고리 없음

Spring Security JWT 설정에서 DI 문제, 왜 신경 써야 할까? (직접 주입 vs Spring 관리)

w.llama 2025. 5. 8. 21:52

"코드가 돌아가는데 뭐가 문제냐고?" 

지금 팀원이 작성한 코드를 코드리뷰하며 타 팀원이 지적한문제를 정리하고자 한다.
처음에 AuthenticationManager를 생성자 주입해서 쓸 때도 동작은 했어.
근데 이 방식은 Spring의 DI 원칙을 무시하는 꼼수였다. 차이점을 명확히 알자!

1. DI 원칙 위반 → 유지보수 문제

원본 코드 (문제점)

	private final AuthenticationManager authenticationManager; //문제인 부분
    
	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws
		AuthenticationException {
... 생략
	return authenticationManager.authenticate(token); //문제인 부분
	}
  • DI 원칙 위반 : Spring이 관리하는 빈을 수동으로 주입 받음.
  • 순환 의존성 가능성 : SecurityConfig <-> LoginFilter 간 의존성 꼬임
  • 테스트 불가능 : Mock 객체 주입이 매우 복잡해

개선 코드 (해결책)

//Spring이 관리하는 빈 사용
return this.getAuthenticationManager().authenticate(token);
  • Spring 이 제공하는 기본 메커니즘을 사용
  • 의존성 주입을 Spring에게 위임하여 설정을 간소화

이를 해결하기 위한 과정

1. LoginFilter에서 AuthenticationManager 제거

2. SecurityConfig 수정

//빈 등록필수
@Bean
public AuthenticationManager authenticationManager(
    AuthenticationConfiguration configuration) throws Exception {
    return configuration.getAuthenticationManager();
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
	// 기존 내용 없음
    
    // 변경 LoginFilter 생성 (AuthenticationManager 주입 X)
    LoginFilter loginFilter = new LoginFilter(jwtUtil, refreshRepository);
    loginFilter.setAuthenticationManager(authenticationManager);
    
    ... 생략
    
    // 기존
    http.addFilterBefore(new JWTFilter(jwtUtil), LoginFilter.class)
			.addFilterAt(new LoginFilter(jwtUtil, authenticationManager(authenticationConfiguration), refreshRepository),
				UsernamePasswordAuthenticationFilter.class);
    // 변경            
    http
        .addFilterBefore(jwtFilter, LoginFilter.class)
        .addFilterAt(loginFilter, UsernamePasswordAuthenticationFilter.class);
}

2. DI(의존성 주입)가 뭐길래? 

간단 비유

"커피 머신이 커피콩을 직접 고르지 않고,
바리스타가 원두를 제공하는 방식"

핵심 개념

  • 제어 역전(IoC): 객체 생성 주체가 개발자 → 프레임워크
  • 장점: 코드 재사용성 ↑, 결합도 ↓, 테스트 용이성 ↑

현제 코드 적용 예

// Before: 직접 원두 선택 (의존성 강결합)
new LoginFilter(..., authenticationManager);

// After: 바리스타에게 맡김 (Spring DI)
this.getAuthenticationManager() // Spring이 알아서 주입

결론: DI는 선택이 아닌 필수다

"동작 한다 ≠ 좋은 코드"

  • DI 원칙 준수 → 유지보수성 ↑, 확장성 ↑
  • Spring 기능 최대 활용 → 생산성 ↑
  • 테스트 편의성 → 버그 감소

처음엔 귀찮아 보여도 DI 원칙을 지키는 게 장기적으로 더 효율적으로 판단되었다.