Spring Security после запуска вылезает ошибка с цикличностью
Текст ошибки, который выходит после запуска программы:
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. 2024-01-09T10:23:14.857+03:00 ERROR 9776 --- [ 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:
┌─────┐ | jwtUserDetailsServices defined in file[com.example.course_paper_backend\security\JwtUserDetailsServices.class] ↑ ↓ | jwtTokenProvider (field private org.springframework.security.core.userdetails.UserDetailsService com.example.course_paper_backend.security.jwt.JwtTokenProvider.userDetailsService) └─────┘
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.
Код программы:
JwtTokenProvider.class:
@Component
public class JwtTokenProvider {
@Value("${jwt.token.secret}")
private String secret;
@Value("${jwt.token.expired}")
private long expiration;
private SecretKey secretKey;
@Autowired
private UserDetailsService userDetailsService;
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@PostConstruct
protected void init() {
secret = Base64.getEncoder().encodeToString(secret.getBytes());
secretKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
}
public String createToken(String username, List<RoleEntity> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", getRoleNames(roles));
Date now = new Date();
Date validity = new Date(now.getTime() + expiration);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(secretKey, SignatureAlgorithm.HS256)
.compact();
}
public Authentication getAuthentication(String token) {
UserDetails userDetails = userDetailsService.loadUserByUsername(getUsername(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
public String getUsername(String token) {
return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJwt(token).getBody().getSubject();
}
public String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authentication");
if (bearerToken != null && bearerToken.startsWith("Bearer_")) {
return bearerToken.substring(7);
}
return null;
}
public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token);
return !claims.getBody().getExpiration().before(new Date());
} catch (JwtException | IllegalArgumentException e) {
throw new JwtAuthenticationException("JWT token is expired or invalid");
}
}
private List<String> getRoleNames(List<RoleEntity> userRoles) {
return userRoles.stream()
.map(RoleEntity::getName)
.collect(Collectors.toList());
}
}
JwtUserDetailsServices.class:
@Service
public class JwtUserDetailsServices implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
private final BCryptPasswordEncoder passwordEncoder;
@Autowired
public JwtUserDetailsServices(BCryptPasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("Пользователь с указанным username: " + username + " не найден!");
}
return JwtUserFactory.create(user);
}
public UserEntity register(UserEntity user) {
List<RoleEntity> userRoles = Collections.singletonList(roleRepository.findByName("ROLE_USER"));
user.setRoles(userRoles);
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setStatus(UserStatus.ACTIVE);
return userRepository.save(user);
}
public List<UserEntity> getAll() {
List<UserEntity> users = new ArrayList<>();
userRepository.findAll().forEach(users::add);
return users;
}
public List<RoleEntity> getUserRolesByUsername(String username) throws UsernameNotFoundException {
UserEntity user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("Пользователь с указанным username: " + username + " не найден!");
}
return user.getRoles();
}
public UserEntity findByUsername(String username) {
return userRepository.findByUsername(username);
}
public UserEntity findById(Long id) throws NotFoundException {
return userRepository.findById(id)
.orElseThrow(() -> new NotFoundException("Пользователь с указанным id не найден!"));
}
public void delete(Long id) throws NotFoundException {
UserEntity user = findById(id);
userRepository.delete(user);
}
}
Ответы (1 шт):
Круговая зависимость образовалась из-за того, что класс JwtTokenProvider зависит от сервиса UserDetailsService и "предоставляет" бин BCryptPasswordEncoder, который требуется для создания экземпляра класса JwtUserDetailsServices.
Для устранения этой зависимости по идее достаточно перенести создание бина BCryptPasswordEncoder из класса JwtTokenProvider в отдельный класс конфигурации:
@Configuration
public class MyConfig {
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Component
public class JwtTokenProvider {
@Value("${jwt.token.secret}")
private String secret;
@Value("${jwt.token.expired}")
private long expiration;
private SecretKey secretKey;
@Autowired
private UserDetailsService userDetailsService;
@PostConstruct
protected void init() {
secret = Base64.getEncoder().encodeToString(secret.getBytes());
secretKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
}
// ...
}