Sandeepan Nath
Sandeepan Nath

Reputation: 10294

Unable to make actuator endpoints working in a legacy Spring boot project - getting authentication page

Spring boot actuator is already included in the POM, and it shows in the startup logs. However, when I try to access /actuator or even the base url of my project, I get the following -

{
    "timestamp": 1577096144986,
    "status": 401,
    "error": "Unauthorized",
    "message": "Full authentication is required to access this resource",
    "path": "/actuator"
}

I read that there could be basic HTTP authentication set for the end points. I looked for spring.security.user.name, password in the config properties, but could not find any.

If I hit http://localhost:8083/actuator, or even http://localhost:8083/ or any URL other the mapped API end points, it seems, I get this password prompt on browser -

enter image description here

On application logs, I get this -

2019-12-23 19:30:54,489 75773 [XNIO-3 task-3] INFO  c.c.common.web.LoggerInterceptor [LoggerInterceptor.java:42] - Visitor [okp91Dj1NzT2KPPUjaUvhqEg4oOhwPQ49I9LTR2z] [GET] [/error] [1ms] [OK]
2019-12-23 19:30:54,493 75777 [XNIO-3 task-3] ERROR org.apache.velocity [CommonsLogLogChute.java:96] - ResourceManager : unable to find resource 'error.vm' in any resource loader.

On entering credentials, it fails and the password prompt appears again, with the same logs as above repeated.

Updates

Spring boot version - 1.5.2.RELEASE.

There is a web.xml inside /src/main/resources. It has following -

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath*:META-INF/spring/applicationContext*.xml
        classpath*:com.packagename
        /WEB-INF/spring/oauth-security.xml      
        /WEB-INF/spring/security-config.xml
        classpath*:META-INF/gateway/*.xml   
    </param-value>
</context-param>

I could locate the oauth-security.xml and security.xml files in this microservice component.

oauth-security.xml has oauth scopes based definitions for API paths -

 <sec:http pattern="/service/v2/**"
      create-session="never"
      entry-point-ref="oauthAuthenticationEntryPoint"
      access-decision-manager-ref="accessDecisionManager">
    <sec:intercept-url pattern="/some/path/v2/profile/**" access="ROLE_USER,SCOPE_PROFILE" method="GET"/>

I see security-config.xml which imports webmvc-config.xml.

I see some of these in security-config.xml -

<sec:http pattern="/somepath/**">
        <sec:intercept-url pattern="/somepath/**" access="ROLE_USER"/>
        <sec:http-basic/>
    </sec:http>

but I don't see any configurations for /** paths anywhere.

Update 2

I checked that there seem to be auth configurations in a file called customscopes.properties as well, which seems to be a custom file, added to webmvc-config.xml like this -

<bean id="propertyConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
        <property name="ignoreResourceNotFound" value="true" />
        <property name="locations">
            <list>
                <value>classpath:/spring/application.properties</value>
                <value>classpath:/spring/local.properties</value>
                <value>classpath:/spring/customscopes.properties</value>
                <value> file:${project.config.dir}/application.properties   </value>
                    <value>file:${project.config.dir}/customscopes.properties</value>
            </list>
        </property>
    </bean>

customscopes.properties has urls like these -

service/v2/path/**=SCOPE_SOMETHING;

At the same, time, there is the same url in oauth-security.xml -

<sec:intercept-url pattern="/service/v2/path/**" access="SCOPE_SOMETHING"/>

I am not sure why there are two sets of configs. I tried changing those one by one, while keeping the other with authentication. I observed that only changing the customscopes.properties affected -

service/v2/path/**=IS_AUTHENTICATED_ANONYMOUSLY;

I am new to Spring Security as well. I checked this video tutorial but could not find those mentioned config methods in my project. There is no mention of WebSecurityConfigurerAdapter.

However, adding the path for actuator/** on both these files, with IS_AUTHENTICATED_ANONYMOUSLY did not work - 401 error as shown in the beginning.

Update 3

Oh, another thing - security-config.xml contains

<sec:http pattern="/favicon.ico" security="none"/>

And I see a difference in the logs when I access http://localhost:8083/actuator/ and http://localhost:8083/somethingelse/

http://localhost:8083/actuator/

Step 1 - Hit this url - log -

2019-12-24 12:31:03,051 590999 [XNIO-3 task-16] INFO  c.c.common.web.LoggerInterceptor [LoggerInterceptor.java:42] - Visitor [OICBz6CqYzI58UqobnBYNEXsZUNErjBkv6wEUUkX] [GET] [/error] [2ms] [OK]
2019-12-24 12:31:03,054 591002 [XNIO-3 task-16] ERROR org.apache.velocity [CommonsLogLogChute.java:96] - ResourceManager : unable to find resource 'error.vm' in any resource loader.

Step 2 - cancel sign in form - get favicon.ico instead of /error -

2019-12-24 12:31:18,641 606589 [XNIO-3 task-20] ERROR org.apache.velocity [CommonsLogLogChute.java:96] - ResourceManager : unable to find resource 'error.vm' in any resource loader.
2019-12-24 12:31:18,912 606860 [XNIO-3 task-21] INFO  c.c.common.web.LoggerInterceptor [LoggerInterceptor.java:42] - Visitor [OICBz6CqYzI58UqobnBYNEXsZUNErjBkv6wEUUkX] [GET] [/favicon.ico] [2ms] [OK]

http://localhost:8083/somethingelse

Step 1 - Hit this url - log -

2019-12-24 12:31:03,051 590999 [XNIO-3 task-16] INFO  c.c.common.web.LoggerInterceptor [LoggerInterceptor.java:42] - Visitor [OICBz6CqYzI58UqobnBYNEXsZUNErjBkv6wEUUkX] [GET] [/error] [2ms] [OK]
2019-12-24 12:31:03,054 591002 [XNIO-3 task-16] ERROR org.apache.velocity [CommonsLogLogChute.java:96] - ResourceManager : unable to find resource 'error.vm' in any resource loader.

Step 2 - cancel sign in form - Same log as above, again

Update 4

If I add a class to extend WebSecurityConfigurerAdapter, and just add a permitAll() against my required paths -

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.
                authorizeRequests().antMatchers("/service/trace/**").permitAll()
                .antMatchers("/service/actuator/**").permitAll()
                .antMatchers("/actuator/**").permitAll()
                .antMatchers("/trace").permitAll()
                .antMatchers("/actuator").permitAll();
    }

I get different errors this time (404 Not found) -

{
    "timestamp": 1577181851520,
    "status": 404,
    "error": "Not Found",
    "message": "Not Found",
    "path": "/service/trace"
}

Note - I have a doubt about which are the available actuator end points, so I am trying to ensure for all these combinations. See the application startup logs below if you can confirm on the basis of that.

I get 404 errors against all these URLs -

http://localhost:8083/service/actuator/beans
http://localhost:8083/actuator/beans
http://localhost:8083/beans

And my other authenticated API endpoints start giving this error -

{
    "timestamp": 1577181062281,
    "status": 403,
    "error": "Forbidden",
    "message": "Could not verify the provided CSRF token because your session was not found.",
    "path": "/service/v2/some/end/point"
}

ALso, I found that we have these filters as well defined in the web.xml. So, it seems there is Spring configuration, as well as Spring boot addition. Correct me if my understanding is wrong.

<filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

So, the problem comes down to this -

I can't access the actuator end points. I see the following in application startup logs, containing actuator, but I can't seem to load them either. I guess Spring security is coming in between but not able to prevent the same.

2019-12-24 14:14:10,769 14209 [main] INFO  o.s.b.a.e.m.EndpointHandlerMapping [AbstractHandlerMethodMapping.java:543] - Mapped "{[/beans || /beans.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2019-12-24 14:14:10,770 14210 [main] INFO  o.s.b.a.e.m.EndpointHandlerMapping [AbstractHandlerMethodMapping.java:543] - Mapped "{[/health || /health.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(javax.servlet.http.HttpServletRequest,java.security.Principal)
2019-12-24 14:14:10,771 14211 [main] INFO  o.s.b.a.e.m.EndpointHandlerMapping [AbstractHandlerMethodMapping.java:543] - Mapped "{[/autoconfig || /autoconfig.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2019-12-24 14:14:10,772 14212 [main] INFO  o.s.b.a.e.m.EndpointHandlerMapping [AbstractHandlerMethodMapping.java:543] - Mapped "{[/metrics/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)
2019-12-24 14:14:10,772 14212 [main] INFO  o.s.b.a.e.m.EndpointHandlerMapping [AbstractHandlerMethodMapping.java:543] - Mapped "{[/metrics || /metrics.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()

Note -

Upvotes: 2

Views: 3955

Answers (5)

Depanker Sharma
Depanker Sharma

Reputation: 72

One way of running the actuator is assigning a different port to the actuator service this could be done by adding following property to application.properties

management.server.port=8084

This way you can run and access actuator on a different port and can create rules on the gateway as to how it can be accessed.

Click here for detail

Another way is to disable security from actuator to add following property to the application.properties

management.endpoints.web.exposure.include=*

Click here for detail

Another way of bypassing all the security is following

@Configuration(proxyBeanMethods = false)
public class ActuatorSecurity extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests((requests) ->
            requests.anyRequest().permitAll());
    }

}

Click here for detail

Hope it helps.

Upvotes: 0

RUARO Thibault
RUARO Thibault

Reputation: 2850

Dealing with Legacy application is never easy. Indeed, it seems like you are mixing Spring and Spring-boot configuration altogether. I suggest you to proces step by step. Go back to a working, stable state and then move on:

  1. Make sure you are using the right dependencies

I've been doing some tests with spring-boot-starter-actuator with spring-boot-starter-parent.
Here is the content of my pom.xml:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
    </parent>

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

Please notice that spring-boot-starter-actuator:2.2.2.RELEASE works in different ways that spring-boot-starter-actuator:1.5.2.RELEASE, mostly regarding the security. Also note that we use the starterof every dependency. If not, you won't all spring boot autoconfiguration enabled.

  1. Configure properly spring-boot-starter-actuator

In your application.properties, add the following lines :

# Make sure every actuator endpoints are located under the same root URL
management.context-path=/actuator
# Disable default actuator security rules to manage everything with your Java configuration
management.security.enabled=false
  1. Try to manage only the access to /actuator

In your java configuration class, the one that extends WebSecurityConfigurerAdapter, apply the following changes:

@EnableWebSecurity // Enable spring security configuration
@Configuration // Is a Spring Configuration class
@Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER) // To override the default actuator security configuration
public class WebSecurity extends WebSecurityConfigurerAdapter {
    /**
     * We try to make sure you can easily manage spring actuator endpoints
    */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .mvcMatchers("/actuator/**").authenticated() // To restrict access to authenticated user on actuator endpoints
                .anyRequest().permitAll()
                .and()
                .csrf().disable(); // If you want to POST data, you have to disable CSRF check. Otherwise, you always get an error when POSTing data on an unsecured URL.
    }
}

For more information about CSRF, please check: https://fr.wikipedia.org/wiki/Cross-site_request_forgery

  1. In the console, you're supposed to see something like this during the server start up:
019-12-30 12:13:34.767  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/beans || /actuator/beans.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2019-12-30 12:13:34.768  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/trace || /actuator/trace.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2019-12-30 12:13:34.769  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/configprops || /actuator/configprops.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2019-12-30 12:13:34.773  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/metrics/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)
2019-12-30 12:13:34.773  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/metrics || /actuator/metrics.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2019-12-30 12:13:34.774  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/autoconfig || /actuator/autoconfig.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2019-12-30 12:13:34.775  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/health || /actuator/health.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(javax.servlet.http.HttpServletRequest,java.security.Principal)
2019-12-30 12:13:34.776  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/dump || /actuator/dump.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2019-12-30 12:13:34.778  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/heapdump || /actuator/heapdump.json],methods=[GET],produces=[application/octet-stream]}" onto public void org.springframework.boot.actuate.endpoint.mvc.HeapdumpMvcEndpoint.invoke(boolean,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException,javax.servlet.ServletException
2019-12-30 12:13:34.779  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/mappings || /actuator/mappings.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2019-12-30 12:13:34.782  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/loggers/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.get(java.lang.String)
2019-12-30 12:13:34.783  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/loggers/{name:.*}],methods=[POST],consumes=[application/vnd.spring-boot.actuator.v1+json || application/json],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.set(java.lang.String,java.util.Map<java.lang.String, java.lang.String>)
2019-12-30 12:13:34.784  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/loggers || /actuator/loggers.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2019-12-30 12:13:34.785  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/auditevents || /actuator/auditevents.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public org.springframework.http.ResponseEntity<?> org.springframework.boot.actuate.endpoint.mvc.AuditEventsMvcEndpoint.findByPrincipalAndAfterAndType(java.lang.String,java.util.Date,java.lang.String)
2019-12-30 12:13:34.786  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/info || /actuator/info.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2019-12-30 12:13:34.788  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/env/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint.value(java.lang.String)
2019-12-30 12:13:34.789  INFO 13172 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator/env || /actuator/env.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()

Let me know if you have any difficulties, or you're console is printing different output. Please also share your pom.xml if any of this works.

Upvotes: 0

alexej K
alexej K

Reputation: 152

1)I tested the code with spring boot 1.5.2 and discovered that in this version the actuator endpoints are available in the root('/') path and not at /actuator path. Your logs confirm it also:

2019-12-24 14:14:10,769 14209 [main] INFO  o.s.b.a.e.m.EndpointHandlerMapping [AbstractHandlerMethodMapping.java:543] - Mapped "{[/beans || /beans.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()

As you see in log, endpoint for example for beans is /beans. In your case http://localhost:8083/beans In addition you need also as Prerak Jain wrote following:

management.security.enabled=false

2) To your problem with HTTP 403:

{
    "timestamp": 1577181062281,
    "status": 403,
    "error": "Forbidden",
    "message": "Could not verify the provided CSRF token because your session was not found.",
    "path": "/service/v2/some/end/point"
}

To fix it you need to add following to your configuration "and().csrf().disable()", so for example:

  http.authorizeRequests().antMatchers("/service/trace/**").permitAll().and().csrf().disable()

That disables csrf token stuff.

Upvotes: 0

InvincibleCoder
InvincibleCoder

Reputation: 123

I have faced the similar error and added this configuration in application.properties :

management.endpoints.web.base-path=/

This would allow you to access all the actuator endpoints.

Upvotes: 2

Prerak Jain
Prerak Jain

Reputation: 29

The error occurs as security is not enabled on the endpoint. For a locally deployed app, add the following configuration to your application.properties file -

management.security.enabled=false

On a production app, more careful configurations would need to be made.

Upvotes: 1

Related Questions