Knowledge/Trouble Shooting

순환 참조(Circular References) 해결

똑똑한망치 2024. 6. 10. 16:40
728x90
반응형

🤣 문제 상황

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

Description:

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

   jwtAuthenticationFilter defined in file [C:\Users\admin\IdeaProjects\exercise_board\build\classes\java\main\org\board\exercise_board\user\Security\JwtAuthenticationFilter.class]
┌─────┐
|  jwtTokenProvider defined in file [C:\Users\admin\IdeaProjects\exercise_board\build\classes\java\main\org\board\exercise_board\user\Security\JwtTokenProvider.class]
↑     ↓
|  userService defined in file [C:\Users\admin\IdeaProjects\exercise_board\build\classes\java\main\org\board\exercise_board\user\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.


Process finished with exit code 1

 

 

순환참조가 발생해서 process가 종료되는 문제가 발생했다.

 

⚡문제 발생 코드

JwtTokenProvider.java

@Component
@Slf4j
public class JwtTokenProvider {

/**
  생략
**/

  private final Key key;
  private final UserService userService;  // 문제 발생하는 객체

  public JwtTokenProvider(
  @Value("${jwt.secretKey}") String secretKey
      ,UserService userService
  ) {
    this.userService = userService;
    byte[] keyBytes = Decoders.BASE64.decode(secretKey);
    this.key = Keys.hmacShaKeyFor(keyBytes);
  }


  // Jwt 토큰을 복호화하여 토큰에 들어있는 인증 정보를 꺼내는 메서드
  public Authentication getAuthentication(String accessToken) {
    // Jwt 토큰 복호화
    Claims claims = parseClaims(accessToken);

    if (claims.get(AUTHORITIES_KEY) == null) {
      throw new RuntimeException("권한 정보가 없는 토큰입니다.");
    }

    // 해당 사용자의 권한 정보 가져오기
    Collection<? extends GrantedAuthority> authorities =
        Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
            .map(SimpleGrantedAuthority::new)
            .collect(Collectors.toList());

    // 문제 발생 !!!!!
    // UserDetails 객체 생성해서 Authentication 리턴
    UserDetails principal = userService.loadUserByUsername(claims.getSubject());
    return new UsernamePasswordAuthenticationToken(
        principal, accessToken, authorities);
  }


/**
  생략
**/
}

 

 

UserService.java

package org.board.exercise_board.user.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.board.exercise_board.post.exception.PostCustomException;
import org.board.exercise_board.post.exception.PostErrorCode;
import org.board.exercise_board.user.Security.JwtTokenProvider;
import org.board.exercise_board.user.domain.Form.SignUpForm;
import org.board.exercise_board.user.domain.model.JwtToken;
import org.board.exercise_board.user.domain.model.User;
import org.board.exercise_board.user.domain.repository.UserRepository;
import org.board.exercise_board.user.exception.CustomException;
import org.board.exercise_board.user.exception.ErrorCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
public class UserService implements UserDetailsService {


  /** 
    생략
  **/
  
  // 문제 발생 !!!
  private final JwtTokenProvider jwtTokenProvider;


  // 문제 발생 !!!
  public JwtToken signin(String loginId, String password) {
    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
        loginId, password);

    Authentication authentication = authenticationManagerBuilder.getObject()
        .authenticate(authenticationToken);

    JwtToken token = jwtTokenProvider.createToken(authentication);

    return token;
  }
  
  /**
    생략
  **/

  @Override
  public UserDetails loadUserByUsername(String loginId) throws UsernameNotFoundException {
    log.info(" --- 회원 정보 찾기, {} --- ", loginId);
    return userRepository.findByLoginId(loginId)
            .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
  }
}

 

 

👌 문제 원인

JwtTokenProvider 에서는 UserService를 참조하고 UserService에서는 JwtTokenProvider을 참조하고 있어서 무한 순환이 되고 있었다.

 

따라서 JwtTokenProvider에서 UserService 사용을 하지 않도록 했더니 해결이 되었다.

 

 

😁 해결 코드

@Component
@Slf4j
public class JwtTokenProvider {

/**
  생략
**/

  private final Key key;

  public JwtTokenProvider(
  @Value("${jwt.secretKey}") String secretKey
//      ,UserService userService  -> 삭제
  ) {
    this.userService = userService;    //  -> 삭제
    byte[] keyBytes = Decoders.BASE64.decode(secretKey);
    this.key = Keys.hmacShaKeyFor(keyBytes);
  }


  // Jwt 토큰을 복호화하여 토큰에 들어있는 인증 정보를 꺼내는 메서드
  public Authentication getAuthentication(String accessToken) {
    // Jwt 토큰 복호화
    Claims claims = parseClaims(accessToken);

    if (claims.get(AUTHORITIES_KEY) == null) {
      throw new RuntimeException("권한 정보가 없는 토큰입니다.");
    }

    // 해당 사용자의 권한 정보 가져오기
    Collection<? extends GrantedAuthority> authorities =
        Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
            .map(SimpleGrantedAuthority::new)
            .collect(Collectors.toList());

    // UserDetails 객체 생성해서 Authentication 리턴
    UserDetails principal = userService.loadUserByUsername(claims.getSubject());    // -> 삭제
    return new UsernamePasswordAuthenticationToken(
        principal,  // -> 삭제
        accessToken, authorities);
  }


/**
  생략
**/
}
반응형