Я не могу ввести класс PasswordEncoder из-за JWTTokenProvider

#java #spring-boot #spring-security #dependency-injection #jwt

#java #весенняя загрузка #spring-безопасность #внедрение зависимостей #jwt

Вопрос:

Я пишу приложение REST MVC. Я использую Spring Boot и спящий режим. Для защиты я решил добавить Spring Security и JWT в проект.

Я использую BCryptEncoder для кодирования пароля. Соответственно, он у меня есть в JWTTokenProvider классе как компонент. Мне нужно внедрить PasswordEncoder в UserService класс, но я не могу. Я понимаю причину, но я не знаю, как это исправить.

UserSevice :

 @RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepository userRepository;

    private final UserMapper userMapper;

    private final PasswordEncoder passwordEncoder;


    public UserDTO registration (RegistrationUserDTO registrationUserDTO){

        User user = new User();

        user.setName(registrationUserDTO.getName());
        user.setLastName(registrationUserDTO.getLastName());
        user.setLogin(registrationUserDTO.getLogin());
        user.setMail(registrationUserDTO.getMail());
        user.setPassword(passwordEncoder.encode(registrationUserDTO.getPassword()));
        user.setRole(Role.CUSTOMER);

        userRepository.save(user);

        return userMapper.userToUserDTO(user);
    }
}
  

JwtTokenProvider :

 @RequiredArgsConstructor
@Component
public class JwtTokenProvider {

    // Fields
    //
    private final UserDetailsService userDetailsService;

    @Value("${jwt.token.secret}")
    private String secret;

    @Value("${jwt.token.expired}")
    private Long validityInMilliSeconds;


    // METHODS
    //

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(8);
    }


    @PostConstruct
    protected void init() {
        secret = Base64.getEncoder().encodeToString(secret.getBytes());
    }

    /**
     *
     * @param login
     * @param role
     * @return ТОКЕН
     */
    public String createToken(String login, Role role) {

        Claims claims = Jwts.claims().setSubject(login);
        claims.put("roles", role.name());

        Date now = new Date();
        Date validity = new Date(now.getTime()   validityInMilliSeconds);

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(validity)
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }


    public Authentication getAuthentication(String token) {
        UserDetails userDetails = this.userDetailsService.loadUserByUsername(getLogin(token));
        return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
    }


    public String getLogin(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
    }


    public boolean validateToken(String token) {
        try {
            Jws<Claims> claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token);

            return !claims.getBody().getExpiration().before(new Date());

        } catch (JwtException | IllegalArgumentException e) {
            throw new JwtAuthenticationException("JWT token is expired or invalid");
        }
    }


    /**
     *
     * @param req
     * @return bearerToken
     */
    public String resolveToken(HttpServletRequest req) {
        String bearerToken = req.getHeader("Authorization");
        if (bearerToken != null amp;amp; bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}
  

JwtUserDetailsService :

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

    private final UserService userService;

    @Override
    public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {

        User user = userService.findByLogin(login);

        JwtUser jwtUser = JwtUserFactory.create(user);
        log.info("IN loadUserByUsername - user with login: {} successfully loaded", login);
        return jwtUser;
    }
}
  

Журналы:

 2020-08-20 11:59:44.964  WARN 15396 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityConfig' defined in file [D:JetBrainsProjectsCoffeeteareabuildclassesjavamainrucoffeeteareaconfigSecurityConfig.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'jwtTokenProvider' defined in file [D:JetBrainsProjectsCoffeeteareabuildclassesjavamainrucoffeeteareasecurityjwtJwtTokenProvider.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'jwtUserDetailsService' defined in file [D:JetBrainsProjectsCoffeeteareabuildclassesjavamainrucoffeeteareasecurityJwtUserDetailsService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService' defined in file [D:JetBrainsProjectsCoffeeteareabuildclassesjavamainrucoffeeteareaserviceUserService.class]: Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'jwtTokenProvider': Requested bean is currently in creation: Is there an unresolvable circular reference?
2020-08-20 11:59:44.964  INFO 15396 --- [           main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2020-08-20 11:59:49.500  INFO 15396 --- [         task-1] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
Exception in thread "task-2" org.springframework.beans.factory.BeanCreationNotAllowedException: Error creating bean with name 'delegatingApplicationListener': Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:212)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207)
    at org.springframework.context.event.AbstractApplicationEventMulticaster.retrieveApplicationListeners(AbstractApplicationEventMulticaster.java:245)
    at org.springframework.context.event.AbstractApplicationEventMulticaster.getApplicationListeners(AbstractApplicationEventMulticaster.java:197)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:134)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:404)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:361)
    at org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisher.publishEventIfRequired(DataSourceInitializedPublisher.java:99)
    at org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisher.access$100(DataSourceInitializedPublisher.java:50)
    at org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisher$DataSourceSchemaCreatedPublisher.lambda$postProcessEntityManagerFactory$0(DataSourceInitializedPublisher.java:200)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
2020-08-20 11:59:49.525  INFO 15396 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
2020-08-20 11:59:49.532  INFO 15396 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2020-08-20 11:59:49.589  INFO 15396 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
2020-08-20 11:59:49.598  INFO 15396 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2020-08-20 11:59:49.628  INFO 15396 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-08-20 11:59:49.659 ERROR 15396 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

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

Description:

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

   securityConfig defined in file [D:JetBrainsProjectsCoffeeteareabuildclassesjavamainrucoffeeteareaconfigSecurityConfig.class]
┌─────┐
|  jwtTokenProvider defined in file [D:JetBrainsProjectsCoffeeteareabuildclassesjavamainrucoffeeteareasecurityjwtJwtTokenProvider.class]
↑     ↓
|  jwtUserDetailsService defined in file [D:JetBrainsProjectsCoffeeteareabuildclassesjavamainrucoffeeteareasecurityJwtUserDetailsService.class]
↑     ↓
|  userService defined in file [D:JetBrainsProjectsCoffeeteareabuildclassesjavamainrucoffeeteareaserviceUserService.class]
└─────┘



Process finished with exit code 1
  

Ответ №1:

Проблема в том, что вы что-то вводите, JwtTokenProvider в @Configuration классе, который создает компонент, необходимый JwtTokenProvider . Таким образом, JwtTokenProvider требуется что-то, предоставляемое конфигурацией безопасности, в то время как SecurityConfig может быть создан только тогда, когда у него есть JwtTokenProvider , следовательно, циклическая зависимость.

Удалите @Component из JwtTokenProvider и создайте @Bean метод в SecurityConfig (или как бы он ни назывался), который создает JwtTokenProvider .

Также не рекомендуется использовать @Bean методы в @Components , поэтому перенесите это также в SecurityConfig .

 @Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{

  @Bean
  public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(8);
  }

  @Bean
  public JwtTokenProvider jwtTokenProvider(UserDetailsService userDetailsService) {
    return new JwtTokenProvider(userDetailsService);
  }

  // other config
}
  

Теперь, вероятно, все еще существует проблема, поскольку вам JwtUserDetailsService нужен UserService тот, который нуждается в PasswordEncoder , и это снова создает циклическую ссылку. Возможно, вам было бы лучше ввести UserRepository в свой JwtUserDetailsService и использовать это, чтобы получить пользователя по логину. Я предполагаю, что UserService просто делегирует метод UserRepository.findByLogin .

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

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {

        User user = userRepository.findByLogin(login);
        if (user != null) {
          JwtUser jwtUser = JwtUserFactory.create(user);
          log.info("IN loadUserByUsername - user with login: {} successfully loaded", login);
          return jwtUser;
        } else {
          throw new UsernameNotFoundException("Unknown user '" login "'");
        }
    }
}
  

ПРИМЕЧАНИЕ: Ваш UserDetailsService не выполнил контракт (ни один найденный пользователь не должен выдавать UsernameNotFoundException . Модифицированный класс выполняет этот контракт.