因为JWTTokenProvider,我无法注入PasswordEncoder类。

huangapple go评论75阅读模式
英文:

I cannot inject the PasswordEncoder class because of the JWTTokenProvider

问题

以下是你提供的代码部分的翻译:

@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);
    }
}
@Component
public class JwtTokenProvider {

    private final UserDetailsService userDetailsService;
    private String secret;
    private Long validityInMilliSeconds;

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

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

    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");
        }
    }

    public String resolveToken(HttpServletRequest req) {
        String bearerToken = req.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}
@Service
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:\JetBrainsProjects\Coffeetearea\build\classes\java\main\ru\coffeetearea\config\SecurityConfig.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:\JetBrainsProjects\Coffeetearea\build\classes\java\main\ru\coffeetearea\security\jwt\JwtTokenProvider.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:\JetBrainsProjects\Coffeetearea\build\classes\java\main\ru\coffeetearea\security\JwtUserDetailsService.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:\JetBrainsProjects\Coffeetearea\build\classes\java\main\ru\coffeetearea\service\UserService.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      

<details>
<summary>英文:</summary>

I am writing a REST MVC application. I&#39;m using Spring Boot and Hibernate. For protection, I decided to add Spring Security and JWT to the project. 

I use `BCryptEncoder` to encode the password. Accordingly, I have it in the `JWTTokenProvider` class as a bean. I need to inject `PasswordEncoder` into the `UserService` class, but I can&#39;t. I understand the reason, but I dont know how to fix it.

`UserSevice`:
```java
@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(&quot;${jwt.token.secret}&quot;)
    private String secret;

    @Value(&quot;${jwt.token.expired}&quot;)
    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(&quot;roles&quot;, 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, &quot;&quot;, userDetails.getAuthorities());
    }


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


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

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

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


    /**
     *
     * @param req
     * @return bearerToken
     */
    public String resolveToken(HttpServletRequest req) {
        String bearerToken = req.getHeader(&quot;Authorization&quot;);
        if (bearerToken != null &amp;&amp; bearerToken.startsWith(&quot;Bearer &quot;)) {
            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(&quot;IN loadUserByUsername - user with login: {} successfully loaded&quot;, login);
        return jwtUser;
    }
}

Logs:

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 &#39;securityConfig&#39; defined in file [D:\JetBrainsProjects\Coffeetearea\build\classes\java\main\ru\coffeetearea\config\SecurityConfig.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name &#39;jwtTokenProvider&#39; defined in file [D:\JetBrainsProjects\Coffeetearea\build\classes\java\main\ru\coffeetearea\security\jwt\JwtTokenProvider.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name &#39;jwtUserDetailsService&#39; defined in file [D:\JetBrainsProjects\Coffeetearea\build\classes\java\main\ru\coffeetearea\security\JwtUserDetailsService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name &#39;userService&#39; defined in file [D:\JetBrainsProjects\Coffeetearea\build\classes\java\main\ru\coffeetearea\service\UserService.class]: Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name &#39;jwtTokenProvider&#39;: 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 &#39;default&#39;
2020-08-20 11:59:49.500  INFO 15396 --- [         task-1] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit &#39;default&#39;
Exception in thread &quot;task-2&quot; org.springframework.beans.factory.BeanCreationNotAllowedException: Error creating bean with name &#39;delegatingApplicationListener&#39;: 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 &#39;applicationTaskExecutor&#39;
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 &#39;debug&#39; 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:\JetBrainsProjects\Coffeetearea\build\classes\java\main\ru\coffeetearea\config\SecurityConfig.class]
┌─────┐
|  jwtTokenProvider defined in file [D:\JetBrainsProjects\Coffeetearea\build\classes\java\main\ru\coffeetearea\security\jwt\JwtTokenProvider.class]
     
|  jwtUserDetailsService defined in file [D:\JetBrainsProjects\Coffeetearea\build\classes\java\main\ru\coffeetearea\security\JwtUserDetailsService.class]
     
|  userService defined in file [D:\JetBrainsProjects\Coffeetearea\build\classes\java\main\ru\coffeetearea\service\UserService.class]
└─────┘



Process finished with exit code 1

答案1

得分: 1

问题在于你在一个 @Configuration 类中注入了 JwtTokenProvider,而这个类又产生了一个 JwtTokenProvider 需要的 bean。因此,JwtTokenProvider 需要安全配置提供的某些内容,而 SecurityConfig 只有在拥有 JwtTokenProvider 时才能创建,从而形成了循环依赖。

JwtTokenProvider 中移除 @Component,并在 SecurityConfig(或其他命名)中创建一个 @Bean 方法,用于创建 JwtTokenProvider

同时,不建议在 @Component 中使用 @Bean 方法,所以也将其移至 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);
  }

  // 其他配置
}

现在可能仍然存在问题,因为你的 JwtUserDetailsService 需要 UserService,而 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)。修改后的代码遵循了该合同。

英文:

The problem is you are injecting something, the JwtTokenProvider in an @Configuration class that produces a bean that is needed by the JwtTokenProvider. So the JwtTokenProvider needs something provided by the security configuration while the SecurityConfig can only be created when it has a JwtTokenProvider hence a circular dependency.

Remove @Component from the JwtTokenProvider and create an @Bean method in the SecurityConfig (or whatever it is called) which creates the JwtTokenProvider.

It is also not recommended to use @Bean methods in @Components so move that to the SecurityConfig as well.

@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
}

Now there probably still is an issue as your JwtUserDetailsService needs the UserService which needs the PasswordEncoder and this again creates a circular reference. You might be better of injecting the UserRepository into your JwtUserDetailsService and use that to get the user by the login. I'm assuming that the UserService simply delegates to the UserRepository.findByLogin method.

@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(&quot;IN loadUserByUsername - user with login: {} successfully loaded&quot;, login);
          return jwtUser;
        } else {
          throw new UsernameNotFoundException(&quot;Unknown user &#39;&quot;+login+&quot;&#39;&quot;);
        }
    }
}

NOTE: Your UserDetailsService didn't honour the contract (no user found should throw an UsernameNotFoundException. The modified one does honour that contract.

huangapple
  • 本文由 发表于 2020年8月20日 17:10:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/63501886.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定