4

I am in the process of converting an existing and working Spring Security (version 3.2.10) XML-configuration to a Java-based configuration. The XML-configuration I am replacing has an authentication manager configured:

<authentication-manager alias="authenticationManager">  
    <authentication-provider ref="kerberosServiceAuthenticationProvider"/>
    <authentication-provider ref="samlAuthenticationProvider"/>
    <authentication-provider ref="pkiAuthenticationProvider"/>
    <authentication-provider ref="openIdConnectAuthenticationProvider"/>
</authentication-manager>

My Java configuration equivalent is:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        auth.authenticationProvider(kerberosServiceAuthenticationProvider())
        .authenticationProvider(samlAuthenticationProvider())
        .authenticationProvider(pkiAuthenticationProvider())
        .authenticationProvider(openIdConnectAuthenticationProvider());
    }
}

As the authentication manager is referred to by its alias in constructing other beans, I have overridden the authenticationmanagerbean like this:

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

As suggested e.g. in How To Inject AuthenticationManager using Java Configuration in a Custom Filter However, on creating of this bean, the following exception is thrown:

Caused by: java.lang.IllegalArgumentException: delegateBuilder cannot be null
at org.springframework.util.Assert.notNull(Assert.java:112)
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.<init>(WebSecurityConfigurerAdapter.java:426)
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.authenticationManagerBean(WebSecurityConfigurerAdapter.java:220)

The delegate builder is the authentication builder that is being used as first argument when overriding the bean (snippet is the implementation of super.authenticationManagerBean()), which is null.

public AuthenticationManager authenticationManagerBean() throws Exception {
        return new AuthenticationManagerDelegator(authenticationBuilder,   context);
}

So it seems something is missing when this bean is created. This delegate builder is only set by this method in the WebSecurityConfigurerAdapter:

@Autowired
public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) 
{...}

But it doesn't get called (and does not seem to be meant to override). I also noticed the configure method has not been called yet. I am obviously missing something but have no idea what it is.

DennisV
  • 128
  • 1
  • 10
  • You already expose a bean with `configure(AuthenticationManagerBuilder auth)`. Do you need two `AuthenticationManager` as bean? – dur Dec 12 '17 at 13:23
  • No, I don't :) So, then how should I expose the authentication manager from the builder as a bean? Can it just be autowired in my security config? Because re-using the auth object and calling getOrBuild() does not work, as the configure method is not called before the other beans requiring the authentication manager bean are created. – DennisV Dec 12 '17 at 13:48
  • See my [answer](https://stackoverflow.com/a/39259519/5277820) for a similar question. – dur Dec 12 '17 at 14:23
  • I fail to see how this solves my problem. I need the builder to register authentication providers but I need the authentication manager bean to wire other beans. In XML this seems to be same thing (see first code snippet). How should I do this in my situation if I should use either one only? – DennisV Dec 12 '17 at 14:38
  • If you can't use the global authentication manager (I'm not sure, because I don't see all of your code), you have to use and expose a local authentication manager. So you have to use the first, not the second solution in my answer. In the moment you use both solutions. – dur Dec 12 '17 at 14:46
  • Your linked question contains your answer, but it doesn't explain the difference between a local exposed authentication manager and the gobal authentication manager, because in the question the global authentication manager is not used. In your question the global authentication manager is used. – dur Dec 12 '17 at 14:49
  • Sorry, it is still not clear to me. Firstly, the javadoc of authenticationManagerBean() clearly says "Override this method to expose the AuthenticationManager from configure(AuthenticationManagerBuilder) to be exposed as a Bean." and vice versa. So I fail to see why my setup is wrong. But even if it is, please provide a code snippet to show how I should both use it configure the authentication providers _and_ expose it as a bean. Just autowiring the AuthenticationManagerBean everywhere does not work, i.e. the builder does not have an object (configure() has not been invoked yet). – DennisV Dec 12 '17 at 16:45
  • As I already said, you can expose a local authentication manager (see my answer, see Javadoc and see your link in your question). Or you can use the global authentication manager. But you can't do both in one configuration. Maybe one of my other [answers](https://stackoverflow.com/a/42326471/5277820) are more helpful to understand the difference between global and local authentication managers. – dur Dec 12 '17 at 18:43

3 Answers3

3

I have had the exact same error while migrating to Spring Boot 2 (in the process, I hade to migrate from Spring Security 4 to Spring Security 5.0.0 and converting my XML configuration to JavaConfig). So I'm not sure this applies to you. In Spring Security 5, that authenticationBuilder is initialized in WebSecurityConfigurerAdapter's method:

@Autowired
public void setApplicationContext(ApplicationContext context) {..}

It should therefore try to inject the context. However, my @Bean-annotated method which defines the AuthenticationManager bean is always invoked before that method and results in the same error than you have. I fixed that by simply implementing the ApplicationContextAware interface, which force the invocation of the method before everything else:

public class MySecurityConfigurerAdapter extends WebSecurityConfigurerAdapter implements ApplicationContextAware {
 /*...*/
 @Override
 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
            .authenticationProvider(dbAuthenticationProvider)
            .authenticationProvider(otherAuthProvider);
}

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

I can't explain the exact reason behind, I couldn't find a new "right" way to do it in the documentation, but at least it worked for me.

nimai
  • 2,083
  • 1
  • 18
  • 16
  • this works for me exactly I didn't have an enum and just used a string for the name on the Bean. Thank you! – howard Aug 04 '20 at 20:21
1

If you are having problems with AuthenticationManagerBuilder giving you an error that states IllegalArgumentException: delegateBuilder cannot be null you likely have a circular bean dependency that is not able to be resolved. This is easy to do with AuthenticationManagerBuilder. Instead, I'd recommend exposing an AuthenticationManager as a Bean and do not use AuthenticationManagerBuilder at all.

For example:

@Bean
public ProviderManager authenticationManager() {
    return new ProviderManager(Arrays.asList(
        kerberosServiceAuthenticationProvider(),
        samlAuthenticationProvider(),
        pkiAuthenticationProvider(),
        openIdConnectAuthenticationProvider());
}

If you are still having issues, you can also try making the methods defining Spring Security's authentication (i.e. AuthenticationManager and AuthenticationProvider, PasswordEncoder, etc) static which allows the definition to be loaded without initializing the entire class. If problems persist, I'd recommend moving all of your AuthenticationManager and AuthenticationProvider configuration to a separate configuration class.

Rob Winch
  • 21,440
  • 2
  • 59
  • 76
-1

I also encountered this problem and submitted a bug in GitHub. But they asked me to stack overflow.

The reason for the problem is the bean loading order.

Fortunately, the solution I found requires adding an additional configuration class to create the AuthenticationManager bean separately.

Examples are as follows:

/**
* Original configuration
*/
@Configuration
@EnableWebSecurity
@ConfigurationProperties(prefix ="security")
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    ......

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling()
                .authenticationEntryPoint(new UnAuthenticationEntryPoint())
          ......
    }
    ......
}
/**
* New configuration class
* Use only to create AuthenticationManager bean
*/
@Order
@Configuration
public class AuthenticationManagerConfig extends WebSecurityConfigurerAdapter {

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

But this solution is not perfect. If there is a better way, please share it with me.