순환 참조 오류

황인우·2025년 2월 17일

1. 오류 메시지

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  customAuthenticationFilter defined in file [/Users/hwang-in-woo/projects/NBE3-4-2-Team10/backend/out/production/classes/com/ll/TeamProject/global/security/CustomAuthenticationFilter.class]
↑     ↓
|  userService defined in file [/Users/hwang-in-woo/projects/NBE3-4-2-Team10/backend/out/production/classes/com/ll/TeamProject/domain/user/service/UserService.class]
↑     ↓
|  securityConfig defined in file [/Users/hwang-in-woo/projects/NBE3-4-2-Team10/backend/out/production/classes/com/ll/TeamProject/global/security/SecurityConfig.class]
└─────┘

Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

Process finished with exit code 1

작업 과정에서 위와 같은 순환 참조 오류가 발생하는 경우가 있었다.

user.changePassword(passwordEncoder.encode(password));

userService에서 비밀번호 변경 과정 중 비밀번호 암호화를 진행할 때 발생하는데

코드 상으로는 순환 의존성이 없을 것 같음에도 오류가 발생하고 있다.


2. 순환 참조 오류가 나는 이유를 알아보자!

1️⃣ 첫번째, securityConfig > customAuthenticationFilter 의존성

.addFilterBefore(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

가장 먼저, 스프링 시큐리티의 필터로 CustomAuthenticationFilter를 등록하는 과정에서

당연히 customAuthenticationFilter에 대한 의존성이 추가된다.

2️⃣ 두번째, customAuthenticationFilter > userService

SiteUser user = userService.getUserFromAccessToken(accessToken);

...

Optional<SiteUser> opUser = userService.findByApiKey(apiKey);

...

String newAccessToken = userService.genAccessToken(user);

그리고 customAuthenticationFilter 내부에서 인증 과정에서

엑세스 토큰을 기반으로 사용자를 조회하거나,

API 키를 통해 사용자를 찾고 새로운 엑세스 토큰으로 갱신하는 과정에서

UserService 에서 정의된 메서드들을 사용하게 되며 해당 의존성이 추가된다.

3️⃣ 마지막, userService > securityConfig

user.changePassword(passwordEncoder.encode(password));

마지막으로 비밀번호 변경을 위해 passwordEncoder를 가져오게 되는데

현재 passwordEncoder는 securityConfig 안에서 bean으로 등록되어 있다.

@Bean
@Lazy
PasswordEncoder passwordEncoder() {
	return new BCryptPasswordEncoder();
}

바로 여기가 문제인 부분이다.

securityConfig와 비밀번호 암호화는 직접적인 관련이 없는데 같이 묶여있기 때문에

비밀번호 암호화를 사용하려면 securityConfig의 모든 부분이 의존성으로 엮이게 되는 것이다.


3. 해결방법

이에 대한 해결은 간단하다.

securityConfig의 필터체인 부분이나 CORS 와 비밀번호 암호화는 사용하는 상황이 다르기 때문에

PasswordEncoder를 독립적인 클래스 파일로 분리하면 된다.

아래와 같이 PasswordEncoderConfig.java 로 분리하였다.

@Configuration
public class PasswordEncoderConfig {

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

순환 오류 문제가 해결된다.

0개의 댓글