개발자의 공부방/스프링

스프링시큐리티] 순환 참조 에러

  • -
728x90
반응형

스프링시큐리티를 적용하는 중에 아래와 같은 에러가 발생했다.

 

에러 메시지

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

Description:

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

┌─────┐
|  webSecurityConfig defined in file [/Users/june/Repository/june/out/production/classes/toyblog/june/springbootdev/config/WebSecurityConfig.class]
↑     ↓
|  userService defined in file [/Users/june/Repository/june/out/production/classes/toyblog/june/springbootdev/service/UserService.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.

 

 

WebSecurityConfig.java

package toyblog.june.springbootdev.config;

import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import toyblog.june.springbootdev.config.jwt.TokenProvider;
import toyblog.june.springbootdev.config.oauth.OAuth2AuthorizationRequestBseOnCookieRepository;
import toyblog.june.springbootdev.config.oauth.OAuth2SuccessHandler;
import toyblog.june.springbootdev.config.oauth.OAuth2UserCustomService;
import toyblog.june.springbootdev.repository.RefreshTokenRepository;
import toyblog.june.springbootdev.service.UserService;

@RequiredArgsConstructor
@Configuration
public class WebSecurityConfig {
    private final OAuth2UserCustomService oAuth2UserCustomService;
    private final TokenProvider tokenProvider;
    private final RefreshTokenRepository refreshTokenRepository;
    private final UserService userService;

    @Bean
    public WebSecurityCustomizer configure() {
        return (web) -> web.ignoring()
                .requestMatchers(PathRequest.toH2Console())
                .requestMatchers("/img/**", "/css/**", "/js/**");
    }

    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
                .httpBasic(HttpBasicConfigurer::disable)
                .formLogin(FormLoginConfigurer::disable)
                .logout(LogoutConfigurer::disable);

        http.sessionManagement(configure -> configure.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

        http.authorizeHttpRequests(authorize ->
                authorize.requestMatchers("/api/token").permitAll()
                        .requestMatchers("/api/**").authenticated()
                        .anyRequest().permitAll());

        http.oauth2Login(oauth2Login ->
                oauth2Login.loginPage("/page")
                        .authorizationEndpoint(authorizationEndpoint ->
                                authorizationEndpoint.authorizationRequestRepository(oAuth2AuthorizationRequestBseOnCookieRepository()))
                        .successHandler(oAuth2SuccessHandler())
                        .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint.userService(oAuth2UserCustomService)));

        http.logout(logout -> logout
                .logoutSuccessUrl("/login"));

        http.exceptionHandling(exceptionHandling ->
                exceptionHandling.defaultAuthenticationEntryPointFor(
                        new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), new AntPathRequestMatcher("/api/**")));

        return http.build();
    }

    @Bean
    public OAuth2SuccessHandler oAuth2SuccessHandler() {
        return new OAuth2SuccessHandler(tokenProvider
                , refreshTokenRepository
                , oAuth2AuthorizationRequestBseOnCookieRepository()
                , userService);
    }

    @Bean
    public TokenAuthenticationFilter tokenAuthenticationFilter() {
        return new TokenAuthenticationFilter(tokenProvider);
    }

    // * 쿠키에 저장할 인증 요청 및 상태 저장소
    @Bean
    public OAuth2AuthorizationRequestBseOnCookieRepository oAuth2AuthorizationRequestBseOnCookieRepository() {
        return new OAuth2AuthorizationRequestBseOnCookieRepository();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

 

 

UserService.java

package toyblog.june.springbootdev.service;

import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import toyblog.june.springbootdev.domain.User;
import toyblog.june.springbootdev.dto.AddUserRequest;
import toyblog.june.springbootdev.repository.UserRepository;

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    public Long save(AddUserRequest addUserRequest) {

        return userRepository.save(User.builder()
                        .email(addUserRequest.getEmail())
                        .password(bCryptPasswordEncoder.encode(addUserRequest.getPassword()))
                        .build())
                .getId();
    }

    public  User findById(Long userId) {
        return userRepository.findById(userId)
                .orElseThrow(() -> new IllegalArgumentException("Unexpected user"));
    }

    public User findByEmail(String email) {
        return userRepository.findByEmail(email)
                .orElseThrow(() -> new IllegalArgumentException("Unexpected user"));
    }

}

 

 

원인

WebSecurityConfig.java에서 OAuth2SuccessHandler를 생성할 때 UserService를 주입하고 있고

UserService.java에서 bCryptPasswordEncoder를 직접 생성하고 있어서 문제가 되는 부분이었다.

 

원인 코드

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder; // * 1) 원인

    public Long save(AddUserRequest addUserRequest) {

        return userRepository.save(User.builder()
                        .email(addUserRequest.getEmail())
                        // * 2) 원인.
                        .password(bCryptPasswordEncoder.encode(addUserRequest.getPassword()))
                        .build())
                .getId();
    }

 

 

해결 방법

BCryptPasswordEncoder를 필드 주입이 아닌 인스턴스를 직접 생성해서 넣어준다

public Long save(AddUserRequest addUserRequest) {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();

        return userRepository.save(User.builder()
                        .email(addUserRequest.getEmail())
                        .password(bCryptPasswordEncoder.encode(addUserRequest.getPassword()))
                        .build())
                .getId();
    }
반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.