#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
. Модифицированный класс выполняет этот контракт.