简体   繁体   中英

Bean Cycle Spring Security

I'm developing a Spring boot web application. The problem is in the login scenario, and ima stacked in loop. Writes that the dependencies are interconnected, and I can't figure out exactly how and why

Description:

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

┌─────┐
|  webSecurityConfig (field boot.service.UserDetailsServiceImpl boot.configs.WebSecurityConfig.userDetailsService)
↑     ↓
|  userDetailsServiceImpl (field org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder boot.service.UserDetailsServiceImpl.bCryptPasswordEncoder)
└─────┘

How can i resolve this, guys? My classes:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @PersistenceContext
    private EntityManager em;

    @Autowired
    UserRepository userRepository;

    @Autowired
    RoleRepository roleRepository;

    @Autowired
    BCryptPasswordEncoder bCryptPasswordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);

        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }

        return user;
    }

    public User findUserById(Long userId) {
        Optional<User> userFromDb = userRepository.findById(userId);
        return userFromDb.orElse(new User());
    }

    public List<User> allUsers() {
        return userRepository.findAll();
    }

    public boolean saveUser(User user) {
        User userFromDB = userRepository.findByUsername(user.getUsername());

        if (userFromDB != null) {
            return false;
        }

        user.setRoles(Collections.singleton(new Role(1L, "ROLE_User")));
        user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
        userRepository.save(user);
        return true;
    }

    public boolean deleteUser(Long userId) {
        if (userRepository.findById(userId).isPresent()) {
            userRepository.deleteById(userId);
            return true;
        }
        return false;
    }

    public List<User> usergtList(Long idMin) {
        return em.createQuery("SELECT u from User u WHERE u.id > :paramId", User.class)
                .setParameter("paramId", idMin).getResultList();
    }


}

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsServiceImpl userDetailsService;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf()
                    .disable()
                .authorizeRequests()
                    .antMatchers("/registration").not().fullyAuthenticated()
                    .antMatchers(("/admin/**")).hasRole("ADMIN")
                    .antMatchers("/", "/resources/**").permitAll()
                .anyRequest().authenticated()
                .and()
                    .formLogin()
                    .loginPage("/login")
                    .defaultSuccessUrl("/")
                    .permitAll()
                .and()
                    .logout()
                    .permitAll()
                    .logoutSuccessUrl("/");
    }

    @Autowired
    protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

Help me figure it out, I've already broken my whole head, Google does not help

If you read javadoc of the UserDetailsService interface from Spring - a responsibility of the implementation should be "loading of User data".

The problem with your implementation is that you intended it for much more than that, ie "saving user". To save a User, your UserDetailsService is also dependant on the PasswordEncoder bean which is supplied in the WebSecurityConfig , but in order to be suppplied a UserDetailsService is needed - this is that cycle.

This indicates an error in design and should be refactored somehow.

The easiest way to break that cycle is to allow a PasswordEncoder to be injected lazily, by annotating it with @Lazy in your UserDetailsServiceImpl class - but I don't recommend doing it. It will get you out of this situation, but the poor design choice will not be corrected.

I would recommend moving all logic other than loadUserByUsername implementation away from the UserDetailsServiceImpl into some other @Service (ie UserService ). - that way you will respect the contract defined in the Spring's UserDetailsService interface, and your UserDetailsServiceImpl will not be dependant on the PasswordEncoder bean - thus your cyclic dependency will be removed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM