Difference between registerGlobal(), configure(), configureGlobal(),configureGlobalSecurity in Spring security

I have below three code snippets all doing the same thing: creating in-memory authentication. So how it impacts defining it in different method names?

  1. registerGlobal
  2. configure
  3. configureGlobal
  4. configureGlobalSecurity

First one:

public void registerGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth
      .inMemoryAuthentication()
        .withUser("user").password("password").roles("USER").and()
        .withUser("admin").password("password").roles("USER","ADMIN");
    }
}

Second one:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
         .inMemoryAuthentication()
              .withUser("user").password("password").roles("USER");
 }

Third one:

public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth
         .inMemoryAuthentication()
              .withUser("user").password("password").roles("USER");
}

Fourth:

@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth)     throws Exception {
    auth.inMemoryAuthentication().withUser("user").password("user").roles("USER");
}

UPDATE 1 : One more thing I would like to add:

configure() method is present in WebSecurityConfigurerAdapter class while others are not present.

UPDATE 2:

I renamed the method in my sample project to below and to my surprise it is working and authenticating the users.

you name it anything and it works

@Autowired
public void anyMethodName(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication().withUser("user").password("user").roles("USER");      
}

In fact, you only have 2 different options.

Option 1: using annotations only (it cover your example 1, 3 and 4 - note that you didn't include relevant annotations in your samples)

registerGlobal, configureGlobal, configureGlobalSecurity are exact same way of doing things. You can name the method according your tastes. The only constraints are :

  • annotate the method with @Autowired
  • the method MUST be in a class annotated with one of the following : @EnableWebSecurity, @EnableWebMvcSecurity, @EnableGlobalMethodSecurity, or @EnableGlobalAuthentication
  • (and of course the method have an argument of type AuthenticationManagerBuilder)

(as you can see the name of the method is not important, that is why you found so many different method name when googling for code samples)

Here is an example of how it looks like :

@EnableWebSecurity
public class MyConfiguration {

    @Autowired
    public void whatever(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
          .withUser("user").password("password").roles("USER").and()
          .withUser("admin").password("password").roles("USER", "ADMIN");
    }

    ...

}

Option 2: using annotations + method overriding (it cover your example 2)

Overriding configure is a convenient approach in a subclass of WebSecurityConfigurerAdapter (or any @Configuration class implementing WebSecurityConfigurer) but it have the same effect as the other option.


How to choose the correct approach?

It's only a question of taste/programming-style because both approachs have the same effect.

The first option make sense when you want/need to keep your configuration in a single class, but your @Configuration class already extends some other class (and you don't want to implement the whole WebSecurityConfigurer interface).


Let's explain my last point in more details. Spring provides many Adapter classes that you can extends to speed up the development of your Spring configuration.

As an example, let's take a commonly used Adapter : WebMvcConfigurerAdapter. You will start with a very simple configuration like this :

@EnableWebMvc
@Configuration
@ComponentScan({ "com.company.mypackage" })
public class SpringWebConfig extends WebMvcConfigurerAdapter {

}

What's important here : your class already extends an Adapter class, so you can't extends another one


Now, you need to add security configuration. You have the choice between including it in your existing SpringWebConfig configuration class or create a new security specific configuration class. Here is a sample of both approaches:

1) Single @Configuration class approach

What's important to note here : SpringWebConfig extends WebMvcConfigurerAdapter + @EnableWebSecurity

@EnableWebMvc
@Configuration
@ComponentScan({ "com.company.mypackage" })
@EnableWebSecurity
public class SpringWebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    public void whatever(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
          .withUser("user").password("password").roles("USER").and()
          .withUser("admin").password("password").roles("USER", "ADMIN");
    }     
}


2) Specific security @Configuration class

What's important to note here : MySecurityConfig extends WebSecurityConfigurerAdapter

Keep your SpringWebConfig as it was and create a new @Configuration class :

@Configuration
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Overide
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
          .withUser("user").password("password").roles("USER").and()
          .withUser("admin").password("password").roles("USER", "ADMIN");
    }
}

For the difference between: registerGlobal(AuthenticationManagerBuilder auth) and configureGlobal(AuthenticationManagerBuilder auth)

The name of the configureGlobal method is not important. However, it is important to only configure AuthenticationManagerBuilder in a class annotated with either @EnableWebSecurity, @EnableWebMvcSecurity, @EnableGlobalMethodSecurity, or @EnableGlobalAuthentication. Doing otherwise has unpredictable results.

Source:
Chapter "Creating your Spring Security configuration" from the "Hello Spring Security Java Config" guide.


protected void configure(AuthenticationManagerBuilder auth) is a method that is likely provided by WebSecurityConfigurer (and its interface WebSecurityConfigurer) - I would say that is just a more type save approach, but does not differ in its result.


Very good answers here. Lemme talk some vanilla spring (no boot) in this regard. When we extend GlobalMethodSecurityConfiguration in our security configurations class, the difference between:

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) 
throws Exception{...}

and:

@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {...}

is HUGE!

First of all, it's important to realize that the name of the method configureGlobal is meaningless. From the @Autowired JavaDoc we learn:

Config methods may have an arbitrary name and any number of arguments; each of those arguments will be autowired with a matching bean in the Spring container.

What's important in this regard is that configureGlobal does not override any method of the GlobalMethodSecurityConfiguration super class and that it's annotated with @Autowired. If we ask what exactly is auto-wired in this method - the answer would be the AuthenticationManagerBuilder - 'auth' argument.

We could try to declare the method as:

protected void configureGlobal(@Autowired AuthenticationManagerBuilder auth) throws Exception {...}

but again, the @Autowired annotation JavaDocs says:

Although @Autowired can technically be declared on individual method or constructor parameters since Spring Framework 5.0, most parts of the framework ignore such declarations. The only part of the core Spring Framework that actively supports autowired parameters is the JUnit Jupiter support in the spring-test module (see the TestContext framework reference documentation for details).

In essence, we won't get any auto-wiring for the 'auth' argument, so there goes this wrong attempt down the drain.

We're left to declare auto-wiring on the method level, which auto-wires the single argument 'auth'.

(proper) Auto-wiring means that spring is able to find a bean of the type AuthenticationManagerBuilder in the context and inject it into the method argument. A bean of this type must be already instantiated in order for spring to inject it. I won't go into the question who instantiates this particular bean but it must exist. Anyhoo... If auto-wiring worked - it's there!

However, when we use the @Override protected void configure(AuthenticationManagerBuilder auth) and override a super-class method, it becomes responsible for instantiating the AuthenticationManagerBuilder object. Whether the super-class obtained it from the application context or instantiated it using the new operator, is none of our concern in this case.

These are just nuances and they are not so important as the big question - when does all of this happen? This brings me to the most important difference which is lifecycle.

When we override spring configuration class methods, we are unwittingly (or wittingly - depends...) taking an active part in the Hollywood Principal of 'don't call us...'.

The super class is responsible for calling our overridden methods in a predefined order. This goes hand in hand with GoF's Template Design Pattern and their concept of hooks, along with the Builder pattern. Overriding methods of a Spring configuration super-class is just fine-tuning the build process without changing the construction order.

So why is this so important?

Let me show you an example by which we want to configure a Spring Security InMemoryUserDetailsManager without resorting to coding our own bean (which is really not an issue, but not the demonstrated case here). We will need to keep a reference to the InMemoryUserDetailsManager that spring build for us, for a later custom MethodSecurityExpressionHandler. The expression handler augments the expression context with nifty little Pre/Post security expressions that are used, for example in methods such as:

@PreAuthorize("isFamilyMember(#username) or isCatOnMouse(#username)")
public void changePassword(String username) throws AuthenticationException;

Here goes option 2: Overriding method configure

@Configuration
@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)
public class ExampleSecurityConfig extends GlobalMethodSecurityConfiguration {

    private InMemoryUserDetailsManager inMemoryUserDetailsManager;

    @Override @Bean protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Bean public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override 
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        inMemoryUserDetailsManager // we want this reference!
         = (InMemoryUserDetailsManager) auth.inMemoryAuthentication()
                                                .passwordEncoder(passwordEncoder())
                                                .withUser("tom")
                                                .password("password")
                                                .authorities("ROLE_CAT")
                                            .and()
                                                .getUserDetailsService();
    }

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        CustomExpressionHandler expressionHandler =
                new CustomExpressionHandler(inMemoryUserDetailsManager); //cannot be null!
        return expressionHandler;
    }
}

On one hand, we want to avoid coding redundant beans - but on the other we need to be able to reference those beans/components/services that Spring builds for us with it's nice fluent (and too complex for my liking) API. But we fail miserably!

The Hollywood Principal bites us in the rear. When we use this configuration, the super-class calls the overridden methods in it's own algorithm and the result in this case is that method createExpressionHandler() is called before method configure(AuthenticationManagerBuilder auth). Naturally, this triggers a null reference to inMemoryUserDetailsManager and obviously we'll see a NullPointerException that will crash the container startup with a BeanInsantiationException or an IllegalState... and a stack trace that will ruin our day. Not a pretty console site!

option 1: Not overriding method configure:

@Configuration
@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)
public class ExampleSecurityConfig extends GlobalMethodSecurityConfiguration {

    @Autowired DataSource dataSource;
    private InMemoryUserDetailsManager inMemoryUserDetailsManager;

    @Override @Bean protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Bean public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Autowired 
    protected void configureBlah(AuthenticationManagerBuilder auth) throws Exception {
        inMemoryUserDetailsManager // we want this reference!
         = (InMemoryUserDetailsManager) auth.inMemoryAuthentication()
                                                .passwordEncoder(passwordEncoder())
                                                .withUser("tom").password("password").authorities("ROLE_CAT")
                                            .and()
                                                .getUserDetailsService();
    }

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        CustomExpressionHandler expressionHandler =
                new CustomExpressionHandler(inMemoryUserDetailsManager); // this arg cannot be null!
        return expressionHandler;
    }

}

In this case, the method configureBlah(...), call it whatever you want, belongs to to our own ExampleSecurityConfig class, it is @Autowired, so spring will inject the AuthenticationManagerBuilder argument and it will be invoked before any of the super-class methods that are not a @PostConstruct or defined in some XML beans file as an init-method. This time around, we get the desired reference to inMemoryUserDetailsManager and our application works as expected.

It's all about lifecycle.