2
votes

I have below security config class (at the bottom), recently upgraded to;

  • Spring Security (3.2.6.RELEASE)
  • Spring Security OAuth2 (2.0.6.RELEASE from 2.0.3.RELEASE)

and I started seeing this error messages, now all my integration tests fail with the same error;

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'securityConfig': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.springframework.security.oauth2.provider.ClientDetailsService com.xyz.package.config.SecurityConfig.clientDetailsService; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'clientDetailsService': Requested bean is currently in creation: Is there an unresolvable circular reference?

..............

..............

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'clientDetailsService': Requested bean is currently in creation: Is there an unresolvable circular reference?

This is definitely caused by upgrading to Spring Security OAuth2 (2.0.6.RELEASE), I didn't have this on 2.0.3.RELEASE. I appreciate if anyone can give me an insight on this one.

Thanks in advance.

@Configuration 
public class SecurityConfig {

@Autowired
private DataSource dataSource;

@Autowired
private ClientDetailsService clientDetailsService;

@Autowired
private RedisConnectionFactory redisConnectionFactory;

@Bean
public TokenStore tokenStore() {
    return new PipelinedRedisTokenStore(redisConnectionFactory);
}

@Bean
public DefaultTokenServices tokenServices() throws Exception {
    DefaultTokenServices tokenServices = new DefaultTokenServices();
    tokenServices.setAccessTokenValiditySeconds(6000);
    tokenServices.setClientDetailsService(clientDetailsService);
    tokenServices.setTokenEnhancer(new MyTokenEnhancer());
    tokenServices.setSupportRefreshToken(true);
    tokenServices.setTokenStore(tokenStore());
    return tokenServices;
}

@Bean
public UserApprovalHandler userApprovalHandler() throws Exception {
    UserApprovalHandler handler = new MyUserApprovalHandler();
    handler.setApprovalStore(approvalStore());
    handler.setClientDetailsService(clientDetailsService);
    handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
    handler.setUseApprovalStore(true);
    return handler;
}

@Bean
public ApprovalStore approvalStore() {
    TokenApprovalStore store = new TokenApprovalStore();
    store.setTokenStore(tokenStore());
    return store;
}

@Bean
public OAuth2AccessDeniedHandler oAuth2AccessDeniedHandler() {
    return new OAuth2AccessDeniedHandler();
}

@Configuration
@Priority(2000)
@EnableWebSecurity
protected static class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${baseUrl}") 
    private String baseUrl;

    @Autowired
    private DataSource dataSource;
    
    @Resource
    private PasswordEncoder passwordEncoder;

    @Autowired
    private OAuth2AccessDeniedHandler oAuth2AccessDeniedHandler;

    @Bean
    public ClientDetailsService clientDetailsService() throws Exception {
        ClientDetailsServiceConfiguration serviceConfig = new ClientDetailsServiceConfiguration();
            
        serviceConfig.clientDetailsServiceConfigurer().inMemory()
            .withClient("xxxx")
            .secret("some-secret")
            .authorizedGrantTypes("password", "authorization_code", "refresh_token", "client_credentials")
            .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
            .scopes("play", "trust");

        return serviceConfig.clientDetailsService();
    }
    
    @Bean
    UserDetailsService clientDetailsUserDetailsService() throws Exception {
        return new ClientDetailsUserDetailsService(clientDetailsService());
    }
    
    @Bean
    public ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter() throws Exception {
        ClientCredentialsTokenEndpointFilter filter = new ClientCredentialsTokenEndpointFilter();
        filter.setAuthenticationManager(authenticationManagerBean());
        filter.afterPropertiesSet();
        return filter;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        
        JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcUserDetail = new JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder>();
        jdbcUserDetail.dataSource(dataSource);
        jdbcUserDetail.passwordEncoder(passwordEncoder);
        jdbcUserDetail.authoritiesByUsernameQuery("select a.username, r.role_name from account a, role r, account_role ar where a.id = ar.account_id and r.id = ar.role_id and a.username = ?");
        jdbcUserDetail.usersByUsernameQuery("select a.username, a.password, a.enabled, a.email from account a where a.username = ?");
        auth.apply(jdbcUserDetail);
        
        auth.userDetailsService(clientDetailsUserDetailsService());
        

    }
    
    @Bean(name="authenticationManager")
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    
    @Bean
    protected AuthenticationEntryPoint authenticationEntryPoint() {
        OAuth2AuthenticationEntryPoint entryPoint = new OAuth2AuthenticationEntryPoint();
        entryPoint.setTypeName("Basic");
        entryPoint.setRealmName("realm/client");
        return entryPoint;
    }
    
    @Override
    public void configure(WebSecurity webSecurity) throws Exception {
        webSecurity
            .ignoring()
            .antMatchers("/resources/**", "/swagger/**", "/copyright*", "/api-docs/**")
            .antMatchers(HttpMethod.POST, "/api/**/account")
        .and()
            .debug(false);
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        
        // @formatter:off
        http
            .anonymous().disable()
            .requiresChannel().anyRequest().requiresSecure();

        http
            .antMatcher("/oauth/token")
            .authorizeRequests().anyRequest().authenticated()
        .and()
            .httpBasic().authenticationEntryPoint(authenticationEntryPoint())
        .and()
            .csrf().requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/token")).disable()
            .exceptionHandling().accessDeniedHandler(oAuth2AccessDeniedHandler)
        .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http
            .addFilterBefore(clientCredentialsTokenEndpointFilter(), BasicAuthenticationFilter.class);
        // @formatter:on

    }
    
}

@Configuration
@EnableResourceServer
protected static class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
    @Autowired
    private ResourceServerTokenServices tokenServices;
    
    @Autowired
    private OAuth2AccessDeniedHandler oAuth2AccessDeniedHandler;
    
    @Autowired
    ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter;
    
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        //resources.tokenServices(tokenServices);
        resources.resourceId("resource");
    }
    
    @Override
    public void configure(HttpSecurity http) throws Exception {

        // @formatter:off
        http
            .requiresChannel().anyRequest().requiresSecure();

        // API calls
        http
            .anonymous().disable()
            .authorizeRequests()
            .antMatchers("/api/**")
            .access("#oauth2.hasScope('trust') and #oauth2.hasScope('play') and (hasRole('ROLE_USER'))")
        .and()
            .addFilterBefore(clientCredentialsTokenEndpointFilter, BasicAuthenticationFilter.class)
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.NEVER)
        .and()
            .exceptionHandling()
            .accessDeniedHandler(oAuth2AccessDeniedHandler);
            
        // @formatter:on
    }
    
}

@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private AuthorizationServerTokenServices tokenServices;
    
    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private UserApprovalHandler userApprovalHandler;
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    AuthenticationEntryPoint authenticationEntryPoint;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
        .tokenServices(tokenServices)
        .userApprovalHandler(userApprovalHandler)
        .authenticationManager(authenticationManager);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        clients.withClientDetails(clientDetailsService);
    }
    
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer)
            throws Exception {
        oauthServer.authenticationEntryPoint(authenticationEntryPoint)
                .realm("realm/clients");
    }
    
}

}

** Update (3/17) **

Created https://github.com/aug70/security-sample for this question. Without changing the configuration seems like things that worked in previous release (2.0.3) is not working anymore. See project readme.

1
I'm not sure like the way you protect the /token endpoint twice. I'm also not sure I really know what that error is about. Can you put together a complete project and paste a link? - Dave Syer
I took your code above and ran it with small modifications for the classes that were not defined (e.g. My*). It worked (i.e. the app started). - Dave Syer
I'm not sure how it worked for you? I'm still getting the same error. I played with the configuration, I could fix the error but then all my requests are denied with 401 and keys are denied with "invalid key". I'm running out of ideas, I already spent a lot of time on security which is not the focus of my project. Is there an easy way to fix this? - aug70co
If you can't define the problem a bit better there's no way to even know what's wrong, so "an easy way to fix it" is meaningless. Post a link to a minimal project that re-creates the error and doesn't require the whole universe to be compiled to support it and I might be able to help. - Dave Syer
I sent a pull request to your github repo. There were quite a lot of odd things in the security configuration. The client details service thing can be fixed by using the callback in the AuthorizationServerConfigurerAdapter to declare the service. - Dave Syer

1 Answers

0
votes

Solution posted here. Using Spring Security Oauth 2.0.9 release.