Search code examples
spring-bootspring-boot-admin

Spring Boot Admin Server can't get access to endpoints (Unauthorized 401)


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?


Solution

  • 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;
        };
      }
    }