Solucionado (ver solução)
Solucionado
(ver solução)
11
respostas

Sprint Boot Admin com a regra de segurança

Gostaria de uma sugestão para implementar segurança no endpoint do actuator (tira-lo do .antMatchers) para que somente o projeto do sprint-boot-admin possa consulta-lo.

11 respostas

Oi Igor,

Desculpe a demora em responder.

Para fazer o que vocé disse, vai precisar das seguintes alterações no projeto:

Na classe SecurityConfigurations remover o antMatchers do actuator:

http.authorizeRequests()
        .antMatchers(HttpMethod.GET, "/topicos").permitAll()
        .antMatchers(HttpMethod.GET, "/topicos/*").permitAll()
        .antMatchers(HttpMethod.POST, "/auth").permitAll()
        .anyRequest().authenticated()
        .and().csrf().disable()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and().addFilterBefore(new AutenticacaoViaTokenFilter(tokenService, usuarioRepository), UsernamePasswordAuthenticationFilter.class);

Mas é necessário que os endpoints do actuator tenham segurança, portanto podemos criar outra classe com as regras de segurança especificas do actuator:

@Configuration
@Order(1)
public class ActuatorSecurityConfigurations extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
        .withUser("actuator").password(passwordEncoder().encode("actuator123"))
        .authorities("ROLE_ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
        .antMatchers("/actuator/**").hasRole("ADMIN")
        .and().httpBasic();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }    
}

Repare que em cima da classe tem o @Order(1). Isso é para indicar ao Spring que essa classe deve ser carregada antes da classe SecurityConfigurations, que vai precisar ter @Order(2):

@EnableWebSecurity
@Configuration
@Order(2)
public class SecurityConfigurations extends WebSecurityConfigurerAdapter {
    //resto do codigo...

No application.properties do projeto monitoramento é necessário também ter segurança:

server.port=8081

spring.security.user.name=admin
spring.security.user.password=admin123

E para conseguir fazer login no projeto de monitoramento é necessário adicionar mais 2 dependências no pom.xml do projeto monitoramento:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-server-ui-login</artifactId>
    <version>1.5.7</version>
</dependency>

E no projeto monitoramento criar a classe SecurityConfigurations:

@Configuration
public class SecurityConfigurations extends WebSecurityConfigurerAdapter {

    private final String adminContextPath;

    public SecurityConfigurations(AdminServerProperties adminServerProperties) {
        this.adminContextPath = adminServerProperties.getContextPath();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
        successHandler.setTargetUrlParameter("redirectTo");
        successHandler.setDefaultTargetUrl(adminContextPath + "/");

        http.authorizeRequests()
            .antMatchers(adminContextPath + "/assets/**").permitAll()
            .antMatchers(adminContextPath + "/login").permitAll()
            .anyRequest().authenticated()
            .and().formLogin()
            .loginPage(adminContextPath + "/login").successHandler(successHandler)
            .and().logout().logoutUrl(adminContextPath + "/logout")
            .and().httpBasic()
            .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            .ignoringAntMatchers(adminContextPath + "/instances", adminContextPath + "/actuator/**");
    }    
}

E por fim, no application properties do projeto forum, adicionar essas configurações:

# spring boot admin server
spring.boot.admin.client.url=http://localhost:8081

#login/senha do admin-server:
spring.boot.admin.client.username=admin
spring.boot.admin.client.password=admin123

#login/senha do client:
spring.boot.admin.client.instance.metadata.user.name=actuator
spring.boot.admin.client.instance.metadata.user.password=actuator123

Agora ao tentar acessar o endpoint do actuator (http://localhost:8080/actuator) será exigido login/senha: admin/admin123

E ao tentar entrar na tela de monitoramento(http://localhost:8081) também será necessário fazer login: actuator/actuator123

Tudo isso está na documentação do Spring Boot Admin: http://codecentric.github.io/spring-boot-admin/2.1.4/#securing-spring-boot-admin

Bons estudos!

Boa noite Rodrigo... eu segui a documentação porém quando eu coloco o spring security no projeto do SBA eu recebo o seguinte erro quando eu subo o SBA:

Failed to register application as Application: 401 null. Further attempts are logged on DEBUG level

Seguindo a sua dica aqui também parei no mesmo erro :/

Oi Igor,

Eu testei aqui e realmente deu esse mesmo erro =/

Pesquisei e testei várias coisas, mas também não rolou.

Acredito que possa ser então algum problema de compatibilidade entre as versões do Spring Boot, Spring Boot Admin Server e Spring Boot Admin Client.

Teria que testar usando outras versoes mais antigas pra ver.

Então pensei a mesma coisa... O que eu tentei fazer que também falava na documentação foi criar meu proprio header. Eu crieium JwtProvider no SBA com a minha secret key. Sobreescrevi o bean HttpHeaderProvider colocando o meu jwt nos headers. Eu vi que era possível sobreescrever porque na classe AdminServerAutoConfiguration temos esses métodos:

    @Bean
    @Primary
    @ConditionalOnMissingBean
    public CompositeHttpHeadersProvider httpHeadersProvider(Collection<HttpHeadersProvider> delegates) {
        return new CompositeHttpHeadersProvider(delegates);
    }

    @Bean
    @Order(0)
    @ConditionalOnMissingBean
    public BasicAuthHttpHeaderProvider basicAuthHttpHeadersProvider() {
        return new BasicAuthHttpHeaderProvider();
    }

A minha idéia com base em algumas pesquisas foi na classe Main criar um método com @Bean (pensei que desse para sobreescrever pelo fato dos providers do autoconfiguration conterem a anotação @ConditionalOnMissingBean) mais ou menos assim:

   @Bean
    public HttpHeaderProvider httpHeadersProvider() {
        return new JwtAuthHttpHeaderProvider();
    }

Onde o JwtAuthHttpHeaderProvider é uma classe minha que implementa a interface HttpHeaderProvider e coloca com header.add() meu JWT no header. Porém ao subir o SBA em debug e colocar o breakpoint no método "instanceWebClient" da classe AdminServerAutoConfiguration vi que o HttpProvider que ele ta pegando é o CompositeHttpProvider e não o que eu configurei na classe main como sendo o meu bean... Você teria alguma idéia de porque não está pegando meu "custom" bean? Obs: para poder ter na classe main o bean com mesmo nome eu tive que colocar no application.properties a configuracao:

spring.main.allow-bean-definition-overriding=true

Oi Igor,

Bom, se a sobrescrita não funcionou, você pode tentar usar a anotação @AutoConfigureBefore, pois pode ter sido algum conflito em relação a ordem de carregamento das classes.

Testei aqui e não funcionou... eu abri um "issue" la no git referente a esse nosso problema... Você consegue imaginar algum outro jeito em que possamos "injetar" na collection delegates do método

public CompositeHttpHeadersProvider httpHeadersProvider(Collection<HttpHeadersProvider> delegates) 

Ja que sabemos que no auto-configuration ele chama esse método, se conseguíssemos adicionar nessa collection o nosso próprio HttpHeaderProvider acredito que resolveria o nosso problema..

Já que ele sempre está chamando o CompositeHttpHeadersProvider default, não rola de você apenas adicionar o seu na lista?

Algo como:

@Bean
public CompositeHttpHeadersProvider httpHeadersProvider(Collection<HttpHeadersProvider> delegates) {
    delegates.add(new JwtAuthHttpHeaderProvider());
    return new CompositeHttpHeadersProvider(delegates);
}

Tentei aqui mas não funcionou..... tentei algumas outras coisas também mas nada de conseguir fazer o sba usar esse meu bean customizado :/

solução!

Rodrigo consegui fazer funcionar. O problema estava na classe Main que usamos pra rodar o spring boot, junto das anotações de @Configuration, @EnableAutoConfiguration e @EnableAdminServer. Tive que adicionar a anotação

@Import({SecurityConfigurations.class})

Ficando então:

@Configuration
@EnableAutoConfiguration
@EnableAdminServer
@Import({SecurityConfigurations.class})
public class MonitoramentoApplication {

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

}

Lendo a documentação dessa anotação ela diz "Indicates one or more @Configuration classes to import.

A unica coisa que eu não consegui entender é quando precisamos usar essa anotação ou não, pois no outro projeto que está sendo monitorado (o client) eu não precisei configurar minha classe main com a anotação @Import importando a minha classe de configuração do Spring Security...

Opa Igor,

Que bom que resolveu! Foi tenso esse problema :D

Pois é, o Spring Boot já deveria carregar as classes de configuração automaticamente...

Só se o SBA faz alguma coisa que desabilita o carregamento dessas classes por algum motivo.

Bons estudos!

Hmm entendi, obrigado :)