英文:
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'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't. I understand the reason, but I don’t 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("${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 && 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;
}
}
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 '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 : 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:\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("IN loadUserByUsername - user with login: {} successfully loaded", login);
return jwtUser;
} else {
throw new UsernameNotFoundException("Unknown user '"+login+"'");
}
}
}
NOTE: Your UserDetailsService
didn't honour the contract (no user found should throw an UsernameNotFoundException
. The modified one does honour that contract.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论