Spring Security循环Bean依赖关系

piok6c0g  于 2022-12-17  发布在  Spring
关注(0)|答案(8)|浏览(170)

我目前正在开发一款Vaadin spring应用,根据应用规范,用户的认证授权必须通过jdbcTemplate查询数据库来完成,这个问题怎么解决?我使用的是Sping Boot 1.4.2.RELEASE。

UPDATE:此方法适用于Sping Boot 1.1.x.RELEASE,但在其最新版本上会产生以下错误消息。

Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
|  jdbcAccountRepository defined in file [repositories\JdbcAccountRepository.class]
↑     ↓
|  securityConfiguration.WebSecurityConfig (field services.JdbcUserDetailsServicessecurity.SecurityConfiguration$WebSecurityConfig.userDetailsService)
↑     ↓
|  jdbcUserDetailsServices (field repositories.JdbcAccountRepository services.JdbcUserDetailsServices.repository)
└─────┘

原始代码如下所示:
帐户存储库:

public interface AccountRepository {
    void createAccount(Account user) throws UsernameAlreadyInUseException;
    Account findAccountByUsername(String username);
}

JdbcAccount存储库:

@Repository
public class JdbcAccountRepository implements AccountRepository {

    private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());

    private final JdbcTemplate jdbcTemplate;
    private final PasswordEncoder passwordEncoder;

    @Autowired
    public JdbcAccountRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {
        this.jdbcTemplate = jdbcTemplate;
        this.passwordEncoder = passwordEncoder;
    }

    @Transactional
    @Override
    public void createAccount(Account user) throws UsernameAlreadyInUseException {
        try {
            jdbcTemplate.update(
                "insert into Account (firstName, lastName, username, password, role) values (?, ?, ?, ?, ?)",
                user.getFirstName(),
                user.getLastName(),
                user.getUsername(),
                passwordEncoder.encode(
                        user.getPassword()),
                        user.getRole()
            );
        } catch (DuplicateKeyException e) {
            throw new UsernameAlreadyInUseException(user.getUsername());
        }
    }

    @Override
    public Account findAccountByUsername(String username) {
        return jdbcTemplate.queryForObject(
            "select username, password, firstName, lastName, role from Account where username = ?",
            (rs, rowNum) -> new Account(
                    rs.getString("username"),
                    rs.getString("password"),
                    rs.getString("firstName"),
                    rs.getString("lastName"),
                    rs.getString("role")),
            username
        );
    }
}

Jdbc用户详细信息服务:

@Service
public class JdbcUserDetailsServices implements UserDetailsService {
    private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());

    @Autowired
    JdbcAccountRepository repository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        try {
            Account account = repository.findAccountByUsername(username);
            User user = new User(
                account.getUsername(),
                account.getPassword(),
                AuthorityUtils.createAuthorityList(
                        account.getRole()
                )
            );
            return user;
        } catch (DataAccessException e) {
            LOGGER.debug("Account not found", e);
            throw new UsernameNotFoundException("Username not found.");
        }
    }
}

安全配置:

@Configuration
@ComponentScan
public class SecurityConfiguration {

    @Autowired
    ApplicationContext context;

    @Autowired
    VaadinSecurity security;

    @Bean
    public PreAuthorizeSpringViewProviderAccessDelegate preAuthorizeSpringViewProviderAccessDelegate() {
        return new PreAuthorizeSpringViewProviderAccessDelegate(security, context);
    }

    @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
    public static class GlobalMethodSecurity extends GlobalMethodSecurityConfiguration {

        @Bean
        @Override
        protected AccessDecisionManager accessDecisionManager() {
            return super.accessDecisionManager();
        }
    }

    @Configuration
    @EnableWebSecurity
    public static class WebSecurityConfig extends WebSecurityConfigurerAdapter {

        @Autowired
        JdbcUserDetailsServices userDetailsService;

        @Autowired
        DataSource dataSource;

        @Bean
        public PasswordEncoder passwordEncoder() {
            return NoOpPasswordEncoder.getInstance();
        }

        @Bean
        public TextEncryptor textEncryptor() {
            return Encryptors.noOpText();
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
         * #configure(org.springframework.security.config.annotation.web.builders.WebSecurity)
         */
        @Override
        public void configure(WebSecurity web) throws Exception {
            //Ignoring static resources
            web.ignoring().antMatchers("/VAADIN/**");
        }

        @Override
        protected void configure(AuthenticationManagerBuilder auth)
            throws Exception {
            auth.userDetailsService(userDetailsService);
        }

        @Bean(name="authenticationManager")
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }

        /*
         * (non-Javadoc)
         * @see org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
         * #configure(org.springframework.security.config.annotation.web.builders.HttpSecurity)
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {

            http
                .exceptionHandling()
                    .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"))
                    .and()
                .authorizeRequests()
                    .antMatchers("/**").permitAll()
                    .and()
                .csrf().disable();
        }
    }
}

如果将Sping Boot 版本降级到[1.1.5,1.2.0),这个问题就不会发生(由于其他依赖关系,我必须使用最新版本)

nhn9ugyo

nhn9ugyo1#

您可以将基于构造函数的依赖注入替换为基于设置函数的依赖注入来解决循环问题,请参阅Spring Framework Reference Documentation:

循环依赖项

如果您主要使用构造函数注入,则有可能创建无法解析的循环依赖场景。
例如:类A通过构造函数注入需要类B的示例,类B通过构造函数注入需要类A的示例,如果你配置类A和B的bean互相注入,Spring IoC容器在运行时检测到这个循环引用,并抛出BeanCurrentlyInCreationException
一个可能的解决方案是编辑某些类的源代码,使其由setter而不是构造函数来配置。或者,避免构造函数注入,而只使用setter注入。换句话说,尽管不建议使用setter注入,但您可以使用setter注入来配置循环依赖项。
与典型情况(没有循环依赖关系)不同,bean A和bean B之间的循环依赖关系强制其中一个bean在完全初始化自身之前注入到另一个bean中(典型的先有鸡还是先有蛋的场景)。

qnzebej0

qnzebej02#

我更喜欢@Lazy方法。这样我就可以坚持一种模式。
参见http://www.baeldung.com/circular-dependencies-in-spring

oug3syen

oug3syen3#

您的PasswordEncoder bean定义在WebSecurityConfig中,而WebSecurityConfig需要JdbcUserDetailsServicesJdbcUserDetailsServices又依赖于JdbcAccountRepository,而JdbcAccountRepository需要PasswordEncoder。因此,循环形成。一个简单的解决方案是将PasswordEncoder bean定义从WebSecurityConfig中取出。即使在SecurityConfiguration类内部也将解决循环问题。

kb5ga3dv

kb5ga3dv4#

我在一个类的构造函数中使用了@Lazy,它解决了我的问题:

public class AService {  
    private BService b;   
    public ApplicantService(@NonNull @Lazy BService b) {    
        this.b = b;  
    }
}  

public class BService {  
    private AService a;   
    public ApplicantService(@NonNull BService a) {  
        this.a = a;  
    }

}

piv4azn7

piv4azn75#

从Zeeshan回答:
您的PasswordEncoder bean定义位于需要JdbcUserDetailsServices的WebSecurityConfig中。JdbcUserDetailsServices又依赖于需要PasswordEncoder的JdbcAccountRepository。因此形成了循环。一个简单的解决方案是将PasswordEncoder bean定义从WebSecurityConfig中取出。即使在SecurityConfiguration类内部也将解决循环问题。
另一个简单的建议是将PasswordEncoder定义从public only更改为public static:

@Bean(name = "passwordEncoder")
public PasswordEncoder passwordencoder() {
    return new CustomPasswordEncoder();
}

收件人:

@Bean(name = "passwordEncoder")
public static PasswordEncoder passwordencoder() {
    return new CustomPasswordEncoder();
}
rekjcdws

rekjcdws6#

@Zeeshan Adnan是对的,从WebSecurityConfig中取出PasswordEncoder解决了循环依赖问题

shyt4zoc

shyt4zoc7#

我为PasswordEncoder创建了一个单独的配置类,然后将其导入securityconfig类,它解决了我的问题。源代码在这里与Spring Boot 最新-〉https://github.com/juyelhushen/JWT-springboot-3-Latest.git

yyhrrdl8

yyhrrdl88#

解决方法之一是不使用构造函数。例如:

private final JdbcTemplate jdbcTemplate;
private final PasswordEncoder passwordEncoder;

@Autowired
public JdbcAccountRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {
    this.jdbcTemplate = jdbcTemplate;
    this.passwordEncoder = passwordEncoder;
}

您可以用途:

@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private PasswordEncoder passwordEncoder;

相关问题