Spring-boot: Actuator Endpoints doesn't work properly with custom Spring Security Configuration on 1.4.0.M3

Created on 30 May 2016  路  17Comments  路  Source: spring-projects/spring-boot

Hello,

I am trying to upgrade my project to Spring Boot 1.4.0.M3, but I came up with this problem. When I am trying to access a sensitive endpoint like "autoconfig", basic authentication popup is coming but after sending username and password, I can't enter the realm.

I tried to debug my application and found that there is only one provider ("DaoAuthenticationProvider") in my provider hierarchy, so I could not enter the realm. I mean, Actuator configuration does not configure DefaultInMemoryUserDetailsManagerConfigurer properly, if this is the right point.

Here is the minimal sample project: https://github.com/okohub/actuatorproblem

Thank you.

duplicate

Most helpful comment

@okohub I faced the same problem after upgrading to spring-boot 1.4.0 M3. It has to do with the following feature that has been added in spring-security 4.1:

http://docs.spring.io/spring-security/site/docs/4.1.0.RELEASE/reference/htmlsingle/#jc-authentication-userdetailsservice

I had to make sure that I never expose a UserDetailService Bean (or a AuthenticationProvider Bean) to the ApplicationContext.

@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
class CustomSecurityConfigurer extends WebSecurityConfigurerAdapter{
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(new CustomUserDetailsService(.....));
    }
}

if I configure it like that, I'm still able login to the actuator endpoints based on in-memory-user-details-service since the DefaultInMemoryUserDetailsManagerConfigurer.configure(...) method is invoked:

DefaultInMemoryUserDetailsManagerConfigurer.

@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
    if (auth.isConfigured()) {
        // actuator config is not done if there is already an authenticationProvider (which is now the case if we expose a UserDetailsSerivice Bean
        return;
    }
    //  ...
}

Whereas in the "actual" application I must authenticate against the CustomUserDetailsService.

IMHO: This behavior change should be mentioned in the spring-boot 1.4 migration guidelines. Or maybe I did not complete understand how I could configure two authentication providers (one for the actuator's endpoint and one for the other parts of the application) which seems like a common problem/requirement.

All 17 comments

@rwinch Anything obvious with the sample before we start digging?

@okohub Thanks for the report.

Can you provide exact steps to produce the problem? I am unable to reproduce the issue by performing the following steps:

  • Start the application using mvn spring-boot:run
  • Open a new Incognito window with Chrome
  • Visit http://localhost:8080/autoconfig
  • Enter the username _security_ and the password _password_

I then see the result of the auto configuration endpoint:

{"positiveMatches":{"AuditAutoConfiguration#auditListener":[{"condition":"OnBeanCondition","message":"...

@philwebb @rwinch

Hello again,

Also referring to the the current documentation:
http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#production-ready-monitoring

I am expecting that I should login to endpoint realm with credentials given in application.properties file.

security.user.name=spring
security.user.password=problem

Actually, I am bit confused after your attempt. MyUserDetailsService bean holds my own custom logic for the system. Why does endpoint security care about this? So if your attempt is the right way, I have to add some custom dummy users to access endpoints, or deal with role mechanism to decide which user can access to endpoints. I think this is wrong, am I missing something?

Maybe there should be a problem with in my SecurityConfig, I'll be also happy if you show the right way to configure this.

By exposing your UserDetailsService as a @Bean you have overridden the authentication mechanism provided by Spring Boot which only uses the application.yml if no authentication mechanism is configured.

You can always configure multiple authentication mechanisms (i.e. support in memory authentication) so that you can keep your admin users separate.

Even if you have multiple authentication mechanisms, you must ensure that you have authorization configured for the actuator endpoints so that only users that are allowed to access the system are authorized to make the requests. Spring Security does not care which data store the user authenticates from. The only thing that can prevent an authenticated user from being able to access the actuator endpoints is some sort of authorization logic.

If you want you can even do this by username. For example, you could specify management.context-path=/manage and then configure authorization to be:

http
    .authorizeRequests()
        .antMatchers("/manage/**").access("principal?.name == 'spring'")
        ...

If you do this, you MUST ensure that you ensure that no user in your system can create a user named spring to bypass your authorization logic. For this reason, it is preferred to use proper roles.

@rwinch thank you for your explanation. Correct me if I am wrong, so everytime I started a new Spring Boot project with custom security configurations (nearly all the time people customize security), I need to configure manually my management endpoint security, for access control. This isn't bit hard?

Actually this problem is not happening on 1.2.8.RELEASE. And application behaves like what I said.
You can check it from here: https://github.com/okohub/actuatorproblem/tree/problem-1.2.8

But starting from 1.3.0+, application behaves like what you explained. Main difference is, 1.3.0+ using Spring Security 4+ but 1.2.8 is using 3.2.9

Is there any documentation or explanation about this behaviour change on migration guides?

@okohub I faced the same problem after upgrading to spring-boot 1.4.0 M3. It has to do with the following feature that has been added in spring-security 4.1:

http://docs.spring.io/spring-security/site/docs/4.1.0.RELEASE/reference/htmlsingle/#jc-authentication-userdetailsservice

I had to make sure that I never expose a UserDetailService Bean (or a AuthenticationProvider Bean) to the ApplicationContext.

@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
class CustomSecurityConfigurer extends WebSecurityConfigurerAdapter{
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(new CustomUserDetailsService(.....));
    }
}

if I configure it like that, I'm still able login to the actuator endpoints based on in-memory-user-details-service since the DefaultInMemoryUserDetailsManagerConfigurer.configure(...) method is invoked:

DefaultInMemoryUserDetailsManagerConfigurer.

@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
    if (auth.isConfigured()) {
        // actuator config is not done if there is already an authenticationProvider (which is now the case if we expose a UserDetailsSerivice Bean
        return;
    }
    //  ...
}

Whereas in the "actual" application I must authenticate against the CustomUserDetailsService.

IMHO: This behavior change should be mentioned in the spring-boot 1.4 migration guidelines. Or maybe I did not complete understand how I could configure two authentication providers (one for the actuator's endpoint and one for the other parts of the application) which seems like a common problem/requirement.

@rwinch don't you think that this is a regression?

Ping @rwinch

@rwinch any suggestion?

Hi, are we sure this is not a bug? I have the same problem:

If, i expose my own AuthenticationProvider via '@Bean' then this switches off the actuator in memory provider and the actuator then uses my own provider (via Basic Auth).

@EnableWebSecurity
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

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

 @Bean//this annotation switches off the default actuator in memory dao provider
 public DaveProvider createDaveProvider(){
    return new DaveProvider();
 }

If I remove @Bean above then everything works fine i.e. the actuator end point is protected by Basic Auth using security.user.name and security.user.password and my own security end points is protected by DaveProvider.

My problem is that my custom AuthenticationProviders have many injected dependencies so I have to use @Bean

Thanks

Related #6888

I have a similar problem with a custom Token filter authentication/authorization(X-Auth-Token header).

Spring security works fine for all non actuator endpoint.

For all actuator endpoints I only receive non sensible information, like the simple UP from health

{ "status": "UP" }

even if the token is correct and I result fully authenticated.

thanks in advance

@rwinch Can we have your input on this one again please?

If you do not want Boot to use the UserDetailsService do not expose it as a @Bean. From my perspective this is close works as designed. The only possible improvement might be to document the change in Boot. As pointed to earlier, the Spring Security reference already documents this change.

I have pretty much the same issue https://github.com/spring-projects/spring-boot/issues/8255 In a previous versions(1.4.x) everything was working fine.

Hello again everybody, currently we are handling situation like this:

@Autowired
  protected void configure(AuthenticationManagerBuilder auth) {
    auth.authenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher));
    //... add dao authentication provider, even UserDetailsService exposed as bean
    // then add InMemory auth here
    addInMemoryAuthenticationProvider(auth);
  }

  private void addInMemoryAuthenticationProvider(AuthenticationManagerBuilder auth) {
    try {
      auth.inMemoryAuthentication()
          .withUser(securityProperties.getUser().getName())
          .password(securityProperties.getUser().getPassword())
          .roles(securityProperties.getUser().getRole().stream().toArray(String[]::new));
    } catch (Exception ex) {
      throw new IllegalStateException("Cannot add InMemory users!", ex);
    }
  }

With this approach, endpoints are still available for basic authentication with user which is defined in security properties.

@rwinch may be you should check our approach, looks like this is much easier.

I think this has been taken care of by the simplified security in 2.0

Was this page helpful?
0 / 5 - 0 ratings