DEV Community

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

Posted on

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! ;)

Top comments (0)