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();
}
}
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?
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;
};
}
}