Search code examples
javaspringspring-bootspring-boot-actuator

Exposing Spring Boot actuator endpoints via Spring Security


I have an Spring Boot App with this configuration running on the port 8081:

@Configuration
@EnableMethodSecurity
public class WebSecurityConfig { // extends WebSecurityConfigurerAdapter {

    private final JwtUtils jwtUtils;
    private final UserDetailsServiceImpl userDetailsService;

    private final AuthEntryPointJwt unauthorizedHandler;

    public WebSecurityConfig(JwtUtils jwtUtils,
                             UserDetailsServiceImpl userDetailsService,
                             AuthEntryPointJwt unauthorizedHandler) {

        this.jwtUtils = jwtUtils;
        this.userDetailsService = userDetailsService;
        this.unauthorizedHandler = unauthorizedHandler;
    }

    @Bean
    public AuthTokenFilter authenticationJwtTokenFilter() {
        return new AuthTokenFilter(jwtUtils, userDetailsService);
    }



    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();

        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());

        return authProvider;
 }


    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
                .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(auth ->
                        auth.requestMatchers("/api/auth/**").permitAll()
                                .requestMatchers("/actuator/**").permitAll()
                                .requestMatchers("/actuator/metrics").permitAll()
                                .requestMatchers("/api/languages").permitAll()
                                .requestMatchers("/api/actuator/health").permitAll()
                                .requestMatchers("/api/auth/sendMailOTP").permitAll()
                                .requestMatchers("/api/messages/**").permitAll()
                                .requestMatchers("/api/auth/socialSignin").permitAll()
                                .requestMatchers("/api/geoDetails").permitAll()
                                .requestMatchers("/api/legalinfo/**").permitAll()
                                .requestMatchers("/api/legalinfo/*").permitAll()
                                .requestMatchers("/api/legalinfo/eula").permitAll()
                                .requestMatchers("/api/users/uploadFile/**").permitAll()
                                .requestMatchers("/api/users/sendEmail").permitAll()
                                .requestMatchers("/api/legalinfo/privacy").permitAll()
                                .requestMatchers("/api/changeuserpassword").permitAll()
                                .requestMatchers("/api/forgotmypassword").permitAll()
                                .requestMatchers("/api/validateEmail").permitAll()
                                .requestMatchers("/api/checkToken").permitAll()
                                .requestMatchers("/api/checkOTPToken").permitAll()
                                .anyRequest().authenticated()
                );

        http.authenticationProvider(authenticationProvider());
        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

doing: http://172.105.90.17:8081/actuator/health

I get :

{
  "status": "UP"
}

but accessing : http://172.105.90.17:8081/actuator/metrics

I receive:

{
  "message": "Full authentication is required to access this resource"
}

in the console:

2024-01-24 06:47:28.884 DEBUG [] o.s.b.f.s.DefaultListableBeanFactory@registerDependentBeans(952) - Autowiring by type from bean name 'webEndpointServletHandlerMapping' via factory method to bean named 'management.endpoints.web-org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties'
2024-01-24 06:47:28.884 DEBUG [] o.s.b.f.s.DefaultListableBeanFactory@registerDependentBeans(952) - Autowiring by type from bean name 'webEndpointServletHandlerMapping' via factory method to bean named 'environment'
2024-01-24 06:47:28.891 INFO  [] o.s.b.a.e.web.EndpointLinksResolver@<init>(58) - Exposing 1 endpoint(s) beneath base path '/actuator'
2024-01-24 06:47:28.905 DEBUG [] o.s.b.f.s.DefaultListableBeanFactory@getSingleton(225) - Creating shared instance of singleton bean 'controllerEndpointHandlerMapping'

Solution

  • Your question can be divided into this two problems:

    1. How to expose actuator endpoints and make them remotely accessible over HTTP?
    2. How to make them accessible without need to login?

    Solution of problem 1.

    By default only the health endpoint is enabled and exposed over HTTP. To include all available endpoints add this to application.properties:

    management.endpoints.web.exposure.include=*
    

    If you want the shutdown endpoint to be available as well add this:

    management.endpoint.shutdown.enabled=true
    

    If you want to change the default prefix which is actuator you can add this:

    management.endpoints.web.base-path=/manage
    

    Whith the above settings the URL for displaying all Spring beans will look like:

    http://localhost:8081/manage/beans
    

    Solution of problem 2.

    If the Spring Boot Security is enabled (as in your case) then only health endpoint will be accessible without login. You can override this default behavior by adding /manage/** to the requestMatchers block. Something like this:

        .authorizeHttpRequests((requests) -> requests
            .requestMatchers("/manage/**").permitAll()
            .anyRequest().authenticated()
        )
    

    The above is not very nice. The prefix manage is hard coded here. What if somebody change the prefix in the application.properties later on?

    It is better to use a dedicated factory class EndpointRequest instead. Leave the application specific SecurityFilterChange bean as it is and add another one just for the actuator:

        @Order(Ordered.HIGHEST_PRECEDENCE)
        @Bean
        public SecurityFilterChain actuatorFilterChain(HttpSecurity http) throws Exception {
            http.securityMatcher(EndpointRequest.toAnyEndpoint().excluding(ShutdownEndpoint.class));
            http.authorizeHttpRequests((requests) -> requests.anyRequest().permitAll());
            return http.build();
        }
    

    In the above example all endpoints but the shutdown are accessible for everyone.

    Please mind the @Order annotation. It is important because of use of SecurityMatcher.

    On the server log you will see something similar to this:

    EndpointLinksResolver      : Exposing 14 endpoint(s) beneath base path '/manage'
    DefaultSecurityFilterChain : Will secure EndpointRequestMatcher includes=[*], excludes=[shutdown]...
    DefaultSecurityFilterChain : Will secure any request with...
    

    Based on Spring Boot Documentation.