DEV Community

Cover image for Configurando o Spring Boot Admin: Server e Client
Marks Duarte
Marks Duarte

Posted on

8

Configurando o Spring Boot Admin: Server e Client

Existem várias formas de se monitorar sistemas distribuídos e uma que me agrada bastante pela simplicidade de configuração e confiabilidade é o Spring Boot Admin.

Nesse tutorial criaremos duas aplicações utilizando o Spring Boot, uma que será o servidor de monitoramento e a outra, o cliente que deverá se registrar para ser monitorado.

Também vamos aproveitar para implementar uma camada de segurança utilizando o Spring Security e com o #Maven, poderemos fazer o build das aplicações para serem executadas individualmente.

Versões utilizadas

  • Spring Boot: 2.7.10
  • Spring Boot Admin Server: 2.7.10
  • Spring Boot Admin Client: 2.7.10

Configurando o Servidor

Crie um projeto utilizando o Spring Initilizr com as seguintes dependências:

  • Starter Web
  • Starter Security
  • Admin Starter Server

Image description

Confira se as dependências relacionadas ao Spring Boot Admin foram adicionadas:



...
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-server-ui</artifactId>
</dependency>
...


Enter fullscreen mode Exit fullscreen mode

Adicione a anotação @EnableAdminServer em alguma classe de configuração:



...

@EnableAdminServer
@SpringBootApplication
public class SpringBootAdminServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootAdminServerApplication.class, args);
    }

}



Enter fullscreen mode Exit fullscreen mode

Configure o comportamento da aplicação utilizando o arquivo application.yaml. Como adicionamos uma camada de segurança, devemos criar um nome de usuário e senha para logarmos no servidor e também será necessário configurar o login para comunicação entre servidor e cliente.



server:
  port: 8081
  servlet:
    context-path: /admin-console
spring:
  security:
    user:
      # Configura o login do servidor.
      name: ${SBA_SERVER_USERNAME}
      password: ${SBA_SERVER_PASSWORD}
  boot:
    admin:
      client:
        # Necessários para que o cliente possa se registrar na api do servidor protegido.
        username: ${SBA_SERVER_USERNAME}
        password: ${SBA_SERVER_PASSWORD}
        instance:
          metadata:
            user:
              # Necessários para que o servidor possa acessar os endpoints protegidos do cliente.
              name: ${SBA_CLIENT_USERNAME}
              password: ${SBA_CLIENT_PASSWORD}

# LOG
logging:
  file:
    name: ${user.home}/logs/admin/sba-server.log
  level:
    root: info
    web: info
    dev.marksduarte: info
    org.springframework: info
  charset:
    file: utf-8



Enter fullscreen mode Exit fullscreen mode

Agora vamos configurar o Spring Security criando uma classe e desabilitando o proxy dos beans na anotação @Configuration(proxyBeanMethods = false), pois como vamos trabalhar com @bean autocontido, podemos evitar o processamento da subclasse CGLIB.

Também vamos permitir os acessos às rotas de login e assets e desabilitar a proteção CSRF paras o métodos HTTP POST e HTTP DELETE nas instâncias dos clientes.



package dev.marksduarte.springbootadminserver;

import de.codecentric.boot.admin.server.config.AdminServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration(proxyBeanMethods = false)
public class SecurityConfig {

    private final AdminServerProperties adminServer;

    public SecurityConfig(AdminServerProperties adminServer) {
        this.adminServer = adminServer;
    }

    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        successHandler.setTargetUrlParameter("redirectTo");
        successHandler.setDefaultTargetUrl(this.adminServer.path("/"));

        http.authorizeHttpRequests(authorizeRequests -> authorizeRequests
                        .requestMatchers(new AntPathRequestMatcher(this.adminServer.path("/assets/**")))
                        .permitAll()
                        .requestMatchers(new AntPathRequestMatcher(this.adminServer.path("/login")))
                        .permitAll()
                        .anyRequest()
                        .authenticated())
                .formLogin(formLogin -> formLogin.loginPage(this.adminServer.path("/login"))
                        .successHandler(successHandler))
                .logout(logout -> logout.logoutUrl(this.adminServer.path("/logout")))
                .httpBasic(Customizer.withDefaults())
                .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                        .ignoringRequestMatchers(
                                new AntPathRequestMatcher(this.adminServer.path("/instances"), HttpMethod.POST.toString()),
                                new AntPathRequestMatcher(this.adminServer.path("/instances/*"), HttpMethod.DELETE.toString()),
                                new AntPathRequestMatcher(this.adminServer.path("/actuator/**"))));

        return http.build();
    }
}



Enter fullscreen mode Exit fullscreen mode

Pronto, agora é só rodar a aplicação e conferir se o Admin Server está acessível através do endereço http://localhost:8081/admin-console e logar com o usuário e senha informados na configuração.

Configurando o Cliente

Crie um projeto utilizando o Spring Initilizr com as seguintes dependências:

  • Starter Web
  • Starter Actuator
  • Starter Security
  • Admin Starter Client

Confira no seu arquivo pom.xml, se a dependência do Spring Boot Admin Client e Spring Actuator foram adicionadas:



...
<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>
    <version>2.7.10</version>
</dependency>
...


Enter fullscreen mode Exit fullscreen mode

Agora vamos configurar o sistema começando pelo arquivo application.yaml:



## INFO ENDPOINT
## Aqui configuramos as informações sobre o sistema, como nome, descrição, versão e etc.
info:
  name: Spring Boot Admin Client
  description: Sistema Cliente
  version: @project.version@

server:
  port: 8080
  servlet:
    context-path: /admin-client

spring:
  # Configuração básica do Spring Security.
  security:
    user:
      name: ${SBA_CLIENT_USERNAME}
      password: ${SBA_CLIENT_PASSWORD}
  boot:
    admin:
      client:
        enabled: true
        # URL do servidor que o cliente deve se registrar.
        url: http://localhost:8081/admin-console
        username: ${SBA_SERVER_USERNAME}
        password: ${SBA_SERVER_PASSWORD}
        instance:
          # URL base para calcular o service-url com o qual se registrar. O caminho é inferido em tempo de execução e anexado à url base.
          service-base-url: http://localhost:8080
          # Essas informações são passadas ao servidor para que ele possa fazer o acesso aos endpoints do sistema cliente.
          metadata:
            user:
              name: ${SBA_SERVER_USERNAME}
              password: ${SBA_SERVER_PASSWORD}
        auto-deregistration: true

## APP
app:
  cors-origins:
    - http://localhost
  cors-methods:
    - GET
    - POST
    - PUT
    - DELETE
    - OPTIONS
  cors-headers:
    - Authorization
    - Content-Type
    - Content-Length
    - X-Requested-With

## ACTUATOR
management:
  info:
    env:
      # Desde o Spring Boot 2.6, o env info é desabilitado por padrão.
      enabled: true
  endpoint:
    health:
      show-details: ALWAYS
      enabled: true
    shutdown:
      enabled: true
    logfile:
      enabled: true
      external-file: logs/sba-client.log
  endpoints:
    web:
      exposure:
        # Liberamos todos os endpoints, mas lembre-se, em produção não se deve fazer isso.
        include: "*"
      cors:
        allowed-headers: ${app.cors-headers}
        allowed-methods: ${app.cors-methods}
        allowed-origins: ${app.cors-origins}

## LOG
logging:
  file:
    name: logs/sba-client.log
    path: logs
  level:
    root: info
    web: info
    dev.marksduarte: info
  charset:
    file: utf-8
  logback:
    rollingpolicy:
      clean-history-on-start: true
      max-file-size: 10MB



Enter fullscreen mode Exit fullscreen mode

Para simplificar, vamos habilitar todas as requisições para os endpoints do "/actuator/**":



...

@EnableWebSecurity
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf()
                .disable()
                .authorizeHttpRequests()
                .antMatchers("/actuator/**")
                .permitAll();
        return http.build();
    }
}


Enter fullscreen mode Exit fullscreen mode

Bom, isso já é suficiente para registar nossa aplicação cliente no servidor.

Mas caso aconteça alguma exceção do tipo: HttpMessageNotWritableException ou um response error HTTP 416 ao tentar acessar o arquivo de log, não se assuste, isso pode acontecer caso seu sistema tenha alguma classe de configuração do Jackson que estenda WebMvcConfigurationSupport.

Nesse caso, essa classe pode estar desativando a instanciação dos Beans com as configurações padrões do Spring Boot.

Para corrigir esse tipo de problema, podemos criar um Bean customizado e substituir a configuração padrão criada na inicialização do sistema.



@Configuration
public class JacksonConfig extends WebMvcConfigurationSupport {

    private final Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();

    private static final List<MediaType> MEDIA_TYPE_LIST = List.of(
            MediaType.ALL,
            MediaType.parseMediaType("application/vnd.spring-boot.actuator.v2+json")
    );

    @Bean
    public MappingJackson2HttpMessageConverter customMappingJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter converter = actuatorConverter();
        converter.setSupportedMediaTypes(MEDIA_TYPE_LIST);
        return converter;
    }

    private MappingJackson2HttpMessageConverter actuatorConverter() {
        return new MappingJackson2HttpMessageConverter(builder.build()) {
            @Override
            protected boolean canWrite(MediaType mediaType) {
                // O método super, retorna true se for null.
                // Assim evitamos a exceção _HttpMessageNotWritableException_ caso o Content-Type 'null' seja enviado.
                return mediaType != null && super.canWrite(mediaType);
            }
        };
    }

    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        /*
        Remove somente o MappingJackson2HttpMessageConverter
padrão e substitui pelo customMappingJackson2HttpMessageConverter.
         */
        var defaultHttpConverterOpt = converters.stream()
                .filter(MappingJackson2HttpMessageConverter.class::isInstance)
                .findFirst();

        defaultHttpConverterOpt.ifPresent(converters::remove);
        converters.add(customMappingJackson2HttpMessageConverter());
    }
}


Enter fullscreen mode Exit fullscreen mode

Image description

Image description

Código disponível em GitHub Marks Duarte

Por enquanto é só. Até mais! ;)

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (0)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up