I have a spring boot admin server application running which is secured by using spring security basic auth. When I am trying to register a spring boot admin client it's not able to register itself with spring boot admin. Here are the detail about both the application.
Spring Boot Admin Server
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.12</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<spring-boot-admin.version>2.7.4</spring-boot-admin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
application.yml
spring:
security:
user:
name: admin
password: admin@123
application:
name: spring-boot-admin
server:
port: 8081
Main class
@SpringBootApplication
@EnableAdminServer
public class AdminServer {
public static void main(String[] args) {
SpringApplication.run(AdminServer.class, args);
}
}
With the above configuration Spring boot admin UI is secured with basic auth.
Spring Boot Admin Client
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.12</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<spring-boot-admin.version>2.7.4</spring-boot-admin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
</dependencies>
application.yml
spring:
boot:
admin:
client:
url: http://localhost:8081
username: admin
password: admin@123
management:
endpoints:
web:
exposure:
include: loggers, health, info
endpoint:
loggers:
enabled: true
health:
show-details: always
After turning on the debug log on spring boot admin client, I see the following error
2023-06-09 16:57:55.116 DEBUG 23402 --- [gistrationTask1] o.s.web.client.RestTemplate : HTTP POST http://localhost:8081/instances
2023-06-09 16:57:55.117 DEBUG 23402 --- [gistrationTask1] o.s.web.client.RestTemplate : Accept=[application/json, application/*+json]
2023-06-09 16:57:55.117 DEBUG 23402 --- [gistrationTask1] o.s.web.client.RestTemplate : Writing [Application(name=admin-client, managementUrl=http://localhost:8080/actuator, healthUrl=http://localhost:8080/actuator/health, serviceUrl=http://localhost:8080/)] as "application/json"
2023-06-09 16:57:55.118 DEBUG 23402 --- [gistrationTask1] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader@6f223fc18 pairs: {POST /instances HTTP/1.1: null}{Authorization: Basic YWRtaW46YWRtaW5AMTIz}{Accept: application/json}{Content-Type: application/json}{User-Agent: Java/17.0.6}{Host: localhost:8081}{Connection: keep-alive}{Content-Length: 220}
2023-06-09 16:57:55.121 DEBUG 23402 --- [gistrationTask1] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader@4f08eaf016 pairs: {null: HTTP/1.1 401}{Vary: Origin}{Vary: Access-Control-Request-Method}{Vary: Access-Control-Request-Headers}{Set-Cookie: JSESSIONID=F1EE36B57198F3F2D2DDBB3DDE33A403; Path=/; HttpOnly}{X-Content-Type-Options: nosniff}{X-XSS-Protection: 1; mode=block}{Cache-Control: no-cache, no-store, max-age=0, must-revalidate}{Pragma: no-cache}{Expires: 0}{X-Frame-Options: DENY}{WWW-Authenticate: Basic realm="Realm"}{Content-Length: 0}{Date: Fri, 09 Jun 2023 11:27:55 GMT}{Keep-Alive: timeout=60}{Connection: keep-alive}
2023-06-09 16:57:55.121 DEBUG 23402 --- [gistrationTask1] o.s.web.client.RestTemplate : Response 401 UNAUTHORIZED
I am not able to figure out what am I missing. Without auth the spring boot admin client is able to register with spring boot admin.
This is resolved now. Followed spring boot admin reference http://docs.spring-boot-admin.com/2.7.4/#_securing_spring_boot_admin_server. After adding SecurityConfig it's working as expected.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final AdminServerProperties adminServer;
private final SecurityProperties security;
@Value("${spring.security.user.name}")
private String username;
@Value("${spring.security.user.password}")
private String password;
public SecurityConfig(AdminServerProperties adminServer, SecurityProperties security) {
this.adminServer = adminServer;
this.security = security;
}
@Bean
protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl(this.adminServer.path("/"));
http.authorizeRequests(
(authorizeRequests) -> authorizeRequests.antMatchers(this.adminServer.path("/assets/**")).permitAll()
.antMatchers(this.adminServer.path("/actuator/info")).permitAll()
.antMatchers(this.adminServer.path("/actuator/health")).permitAll()
.antMatchers(this.adminServer.path("/login")).permitAll().anyRequest().authenticated()
).formLogin(
(formLogin) -> formLogin.loginPage(this.adminServer.path("/login")).successHandler(successHandler).and()
).logout((logout) -> logout.logoutUrl(this.adminServer.path("/logout")))
.httpBasic(Customizer.withDefaults())
.csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers(
new AntPathRequestMatcher(this.adminServer.path("/instances"),
POST.toString()),
new AntPathRequestMatcher(this.adminServer.path("/instances/*"),
DELETE.toString()),
new AntPathRequestMatcher(this.adminServer.path("/actuator/**"))
)); // <4>
return http.build();
}
@Bean
public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {
UserDetails user = User.withUsername(username).password(passwordEncoder.encode(password)).roles("USER").build();
return new InMemoryUserDetailsManager(user);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}