개발자의 공부방/스프링
스프링시큐리티] 순환 참조 에러
- -
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();
}
반응형
'개발자의 공부방 > 스프링' 카테고리의 다른 글
JPA] QClass import 안되는 문제 (0) | 2024.02.28 |
---|---|
스프링시큐리티] http deprecated 리팩토링 (0) | 2024.01.12 |
스프링] 스프링 시큐리티 CORS 문제 해결하기 (0) | 2023.11.15 |
스프링] SPRING_SESSION 테이블 자동 생성이 안될 때 해결법 (5) | 2023.11.07 |
스프링] 쿠키의 Same Site를 알아보자 (0) | 2023.08.11 |
Contents
소중한 공감 감사합니다