2016-11-19 32 views
9

Aktualnie pracuję nad aplikacją wiosną Vaadin. Jedyne, co mogę powiedzieć, to uwierzytelnianie/autoryzacja użytkowników musi odbywać się poprzez zapytanie do bazy danych przez jdbcTemplate. Jak rozwiązać ten problem? Używam Spring Boot 1.4.2.RELEASE.Spring Security dependency bean bean

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) 
└─────┘ 

Oryginalny kod wygląda następująco:

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

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

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

PS Jeśli wersja downgrade do wiosny Boot [1.1.5,1.2.0), problem ten nie wystąpi (z powodu uzależnienia od innych, muszę używać najnowszej)

Odpowiedz

12

można zastąpić constructor-based dependency injection z setter-based dependency injection aby rozwiązać ten cykl, zobacz Spring Framework Reference Documentation:

wzajemnie od siebie zależnych

Jeśli używasz głównie iniekcji konstruktora, możliwe jest utworzenie nierozwiązywalnego scenariusza zależności cyklicznej.

Na przykład: Klasa A wymaga instancji klasy B poprzez wstrzyknięcie konstruktora, a klasa B wymaga instancji klasy A poprzez wstrzyknięcie konstruktora. Jeśli skonfigurujesz komponenty bean dla klas A i B, które będą wstrzykiwane do siebie, kontener Spring IoC wykryje to odwołanie cykliczne w czasie wykonywania i wygeneruje BeanCurrentlyInCreationException.

Jednym z możliwych rozwiązań jest edycja kodu źródłowego niektórych klas, które mają być konfigurowane przez ustawiające, a nie konstruktory. Ewentualnie unikaj wstrzykiwania konstruktora i używaj tylko zastrzyku ustawiającego. Innymi słowy, chociaż nie jest to zalecane, można skonfigurować zależności kołowe za pomocą zastrzyku ustawiającego.

W odróżnieniu od typowego przypadku (bez zależności kołowych), zależność cykliczna między fasolą A i fasolą B zmusza jedną z fasoli do wstrzyknięcia do drugiej, zanim zostanie całkowicie zainicjowana (klasyczny scenariusz z kurczakiem/jajkiem).

+0

https://ibb.co/d2x4j5 Hero udać się poniżej tego @Autowired jest polem –

+0

@ ВсеЕдно: Dzięki, ale zalecenie nie jest częścią JavaDoc. Czy jest to plugin twojego IDE, który go rekomenduje? Jednak Spring Security zmienił AFAIK dużo ziaren z wersją 4.x na wtrysk konstruktora. Więc nie ma spójnego sposobu. – dur

4

Preferuję metodę @Lazy. W ten sposób mogę trzymać się jednego wzoru.

Zobacz http://www.baeldung.com/circular-dependencies-in-spring

+0

Powinieneś wspomnieć w swojej odpowiedzi, że to rozwiązanie działa tylko wtedy, gdy możliwe jest utworzenie proxy (proxy Java lub proxy CGLIB) obiektu zależnego. – dur

Powiązane problemy