web-dev-qa-db-ja.com

Spring Securityの循環Beanの依存関係

現在、Vaadin springアプリケーションに取り組んでいます。私が言える唯一のことは、jdbcTemplateを介してデータベースにクエリを実行することで、ユーザーの認証/承認を行う必要があるということです。この問題を解決するには? Spring Boot 1.4.2.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)
└─────┘

元のコードは次のようになります。

AccountRepository:

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

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:

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

SecurityConfiguration:

@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)にダウングレードする場合、この問題は発生しません(他の依存関係のため、最新のものを使用する必要があります)

19
Reborn

コンストラクターベースの依存性注入セッターベースの依存性注入 に置き換えて、サイクルを解決できます。 Spring Framework Reference Documentation を参照してください:

循環依存関係

主にコンストラクター注入を使用する場合、解決できない循環依存シナリオを作成することができます。

たとえば、クラスAはコンストラクターの注入を通じてクラスBのインスタンスを必要とし、クラスBはコンストラクターの注入を通じてクラスAのインスタンスを必要とします。クラスAとクラスBのBeanを相互に注入するように構成すると、Spring IoCコンテナーは実行時にこの循環参照を検出し、BeanCurrentlyInCreationExceptionをスローします。

考えられる解決策の1つは、一部のクラスのソースコードを編集して、コンストラクターではなくセッターで構成することです。または、コンストラクター注入を避け、セッター注入のみを使用します。つまり、推奨されていませんが、セッターインジェクションを使用して循環依存関係を構成できます。

典型的なケース(循環依存関係なし)とは異なり、Bean AとBean Bの間の循環依存関係により、完全に初期化される前に一方のBeanが他方に強制的に注入されます(従来の鶏肉/卵シナリオ)。

26
dur

@Lazyメソッドを好みます。そうすれば、1つのパターンに固執することができます。

http://www.baeldung.com/circular-dependencies-in-spring を参照してください

20
Jacques Koorts