spring-bootspring-boot-actuator

spring actuator/health access denied after upgrading spring boot 3


I was updating current spring boot application version to spring boot 3, 3.4.4 to be precise, after updating the dependencies and checking out rest endpoints everything seems to be working fine, but actuator/health endpoint is giving me access denied 401. I tried to configuring securityFilter chain to permit health endpoint, but still no luck. What would be the issue here


import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.annotation.Order;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
@ComponentScan("com.xxxx.nes.**")
@EntityScan("com.xxxx.nes.model.**")
@EnableJpaRepositories("com.xxxx.nes.model.**")
public class ResourceServeConfig {

    @Bean
    @Order(1)
    public SecurityFilterChain actuatorEndpoints(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/nes/actuator/health")
            .authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
            .csrf(AbstractHttpConfigurer::disable);
        return http.build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain securedEndpoints(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.ignoringRequestMatchers("/**/notifications/**"))
            .authorizeHttpRequests(
                auth ->
                    auth
                        .requestMatchers("/**/notifications/**")
                        .hasAuthority("SCOPE_notifications")
                        .anyRequest()
                        .authenticated()
            )
            .oauth2ResourceServer(
                oauth -> oauth.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
            );
        return http.build();
    }

    private JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
        converter.setAuthorityPrefix("SCOPE_");
        converter.setAuthoritiesClaimName("scope");

        JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
        jwtConverter.setJwtGrantedAuthoritiesConverter(converter);
        return jwtConverter;
    }
}

and application.yaml

  application:
    name: nes
  datasource:
    url: ${nes.db.jdbc.url}
    username: ${nes.db.username}
    password: ${nes.db.password}
    driverClassName: ${nes.db.driverClassName:com.mysql.cj.jdbc.Driver}
  jpa:
    hibernate:
      ddl-auto: none
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    database: mysql
    database-platform: ${nes.jpa.hibernate.dialect}
  liquibase:
    enabled: ${nes.db.liquibase.enabled:true}
    contexts: ${nes.db.liquibase.contexts:none}
    user: ${nes.db.liquibase.user}
    password: ${nes.db.liquibase.password}
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: ${nes.security.oauth2.resourceserver.jwt.issuer-uri}
server:
  port: ${nes.server.port}
  servlet:
    context-path: ${nes.server.context-path:/nes}
  compression:
    enabled: ${nes.server.compression.enabled:true}
management:
  endpoints:
    web:
      exposure:
        include: health
  endpoint:
    health:
      show-details: never
      show-components: never
security:
  aws:
    region: ${nes.security.aws.region}
ses:
  atp:
    queue:
      url: ${nes.ses.atp.queue.url}
logging:
  level:
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE


Solution

  • Needed to add @Configuration annotation then matching started to hit to custom filterchain

    @EnableWebSecurity
    @ComponentScan("com.xxxx.nes.**")
    @EntityScan("com.xxxx.nes.model.**")
    @EnableJpaRepositories("com.xxxx.nes.model.**")
    @Configuration <---
    public class ResourceServeConfig {
    
        @Bean
        @Order(1)
        public SecurityFilterChain actuatorEndpoints(HttpSecurity http) throws Exception {
            http
                    .securityMatcher("/actuator/health")
                    .authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
                    .csrf(AbstractHttpConfigurer::disable);
            return http.build();
        }
    
        @Bean
        @Order(2)
        public SecurityFilterChain securedEndpoints(HttpSecurity http) throws Exception {
            http
                    .csrf(csrf -> csrf.ignoringRequestMatchers("/**/notifications/**"))
                    .authorizeHttpRequests(
                            auth ->
                                    auth
                                            .requestMatchers("/**/notifications/**")
                                            .hasAuthority("SCOPE_notifications")
                                            .anyRequest()
                                            .authenticated()
                    )
                    .oauth2ResourceServer(
                            oauth -> oauth
                                    .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
                    );
            return http.build();
        }
    
        private JwtAuthenticationConverter jwtAuthenticationConverter() {
            JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
            converter.setAuthorityPrefix("SCOPE_");
            converter.setAuthoritiesClaimName("scope");
    
            JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
            jwtConverter.setJwtGrantedAuthoritiesConverter(converter);
            return jwtConverter;
        }
    }