Spring Security 循环 bean 依赖 [英] Spring Security circular bean dependency

查看:104
本文介绍了Spring Security 循环 bean 依赖的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在开发 Vaadin spring 应用程序.根据应用规范,用户的认证/授权必须通过jdbcTemplate查询数据库来完成.如何解决这个问题?我使用的是 Spring Boot 1.4.2.RELEASE.

I'm currently working on a Vaadin spring application. According to the app specifications, authentication/authorization of the users must be completed by querying database via jdbcTemplate. How to solve this issue? I'm using Spring Boot 1.4.2.RELEASE.

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

UPDATE: This approach works with Spring Boot 1.1.x.RELEASE, however on the latest versions of it produces following error message.

Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
|  jdbcAccountRepository defined in file [repositoriesJdbcAccountRepository.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);
}

JdbcAccountRepository:

JdbcAccountRepository:

@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
        );
    }
}

JdbcUserDetailsS​​ervices:

JdbcUserDetailsServices:

@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();
        }
    }
}

P.S 如果将 Spring Boot 版本降级到 [1.1.5,1.2.0) ,则不会出现这个问题(由于其他依赖,我必须使用最新的)

P.S If downgrade Spring Boot version to [1.1.5,1.2.0) , this problem will not occur ( due to other dependency, I must to use the latest)

推荐答案

你可以替换 基于构造函数的依赖注入基于setter 的依赖注入 解决循环问题,请参阅Spring 框架参考文档:

You could replace constructor-based dependency injection with setter-based dependency injection to resolve the cycle, see Spring Framework Reference Documentation:

循环依赖

如果您主要使用构造函数注入,则可能会创建无法解决的循环依赖场景.

If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.

例如:A类通过构造函数注入需要B类的实例,B类通过构造函数注入需要A类的实例.如果将类 A 和 B 的 bean 配置为相互注入,则 Spring IoC 容器在运行时检测到此循环引用,并抛出 BeanCurrentlyInCreationException.

For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a BeanCurrentlyInCreationException.

一种可能的解决方案是编辑一些类的源代码,以便由 setter 而不是构造函数来配置.或者,避免构造函数注入并仅使用 setter 注入.也就是说,虽然不推荐,但是可以通过setter注入来配置循环依赖.

One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.

与典型情况(没有循环依赖)不同,bean A 和 bean B 之间的循环依赖迫使其中一个 bean 在完全初始化之前注入另一个 bean(经典的鸡/蛋场景).

Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken/egg scenario).

这篇关于Spring Security 循环 bean 依赖的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆