2

I have configured my own in-memory authentication config and my application can register itself at Spring Boot Admin Server and the server gets the right credentials, but it still gets an unauthorized response from my application. If I enter the credentials in my browser then it works.

@Configuration
@Order(2)
public class ActuatorSecurity extends WebSecurityConfigurerAdapter {
  @Value("${spring.boot.admin.client.instance.metadata.user.name:actuator}")
  private String actuatorName;
  @Value("${spring.boot.admin.client.instance.metadata.user.password:secret}")
  private String actuatorPassword;

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().withUser(actuatorName).password("{noop}" + actuatorPassword).authorities("ACTUATOR");
  }

  @Override
  public void configure(HttpSecurity http) throws Exception {
    http
            .antMatcher("/actuator/**")
            .authorizeRequests()
            .anyRequest().hasAuthority("ACTUATOR")
            .and()
            .httpBasic();
  }
}

spring-boot-admin-dashboard

The difference between browser request that works and the Spring-Boot-Admin request that results in 401 is that BasicAuthenticationFilter gets a header in the browser attempt but in Spring-Boot-Admin attempt BasicAuthenticationFilter don't read any header and results in an anonymous user.

Any ideas?

Juliano Macedo
  • 668
  • 8
  • 21
qwer1993
  • 39
  • 6

2 Answers2

2

I had a similar problem, and was solved using the below example (from documentation).

@Bean
public HttpHeadersProvider customHttpHeadersProvider() {
    return  instance -> {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Authorization", "My Custom Value");
        return httpHeaders;
    };
}

In case you need to inject custom HTTP headers into the requests made to the monitored application’s actuator endpoints you can easily add a HttpHeadersProvider

Reference: Injecting Custom HTTP Headers

Juliano Macedo
  • 668
  • 8
  • 21
-1

A workaround is you build your own filter for /actuator/** path with custom headers, something like this:

On application side:

@Configuration
@Order(2)
public class ActuatorSecurity extends WebSecurityConfigurerAdapter {

  public static String name = "actuator-admin";
  public static String pw = "actuator-pw";

  public static String headerName = "ACTUATOR_HEADER_NAME";
  public static String headerPw = "ACTUATOR_HEADER_PW";


  protected String getActuatorFilterUrl() {
    return "/actuator/" + "**";
  }

  @Override
  protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity
            .cors()
            .and()
            // we don't need CSRF because our token is invulnerable
            .csrf().disable()
            // All urls must be authenticated (filter for token always fires (/**)
            .antMatcher(getActuatorFilterUrl())

            .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS).permitAll()
            .antMatchers(getActuatorFilterUrl()).authenticated()

            .and()
            .addFilterBefore(new ActuatorSecurityFilter(getActuatorFilterUrl(), name, pw, headerName, headerPw),
                    UsernamePasswordAuthenticationFilter.class)
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
  }
}

public class ActuatorSecurityFilter extends AbstractAuthenticationProcessingFilter {

  private String name;
  private String pw;

  private String headerName;
  private String headerPw;

  public ActuatorSecurityFilter(String filterUrl, String name, String pw, String headerName, String headerPw) {
    super(filterUrl);
    this.name = name;
    this.pw = pw;

    this.headerName = headerName;
    this.headerPw = headerPw;
  }

  @Override
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
    final String name = request.getHeader(headerName);
    final String pw = request.getHeader(headerPw);

    if (name.equals(this.name) && pw.equals(this.pw)) {
      return new Authentication() {
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
          return new ArrayList<>();
        }

        @Override
        public Object getCredentials() {
          return null;
        }

        @Override
        public Object getDetails() {
          return null;
        }

        @Override
        public Object getPrincipal() {
          return null;
        }

        @Override
        public boolean isAuthenticated() {
          return true;
        }

        @Override
        public void setAuthenticated(boolean b) throws IllegalArgumentException {

        }

        @Override
        public String getName() {
          return null;
        }
      };
    }
    throw new IllegalStateException("name or pw wrong");
  }

  @Override
  protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult)
          throws IOException, ServletException {

    SecurityContextHolder.getContext().setAuthentication(authResult);

    // As this authentication is in HTTP header, after success we need to continue the request normally
    // and return the response as if the resource was not secured at all
    chain.doFilter(request, response);
  }
}

On Spring-Admin side:

@Configuration
public class CUstomHeaderConf {

  @Bean
  public HttpHeadersProvider customHttpHeadersProvider() {
    return  instance -> {
      HttpHeaders httpHeaders = new HttpHeaders();
      httpHeaders.add("ACTUATOR_HEADER_NAME", "actuator-admin");
      httpHeaders.add("ACTUATOR_HEADER_PW", "actuator-pw");
      return httpHeaders;
    };
  }
}
Juliano Macedo
  • 668
  • 8
  • 21
qwer1993
  • 39
  • 6