Solucionado (ver solução)

Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

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 :)