I use a HandlerInterceptor to secure access to the regular api endpoints of my app, running on 2.0.0-M7 (using custom security, not based on spring-security).
For a reason that I can't explain (and that the doc doesn't seem to explain), the interceptor is not called for actuator endpoints. And I haven't found any way to add an interceptor for actuator endpoints.
It would be nice to provide a way to do so, in order to be able to secure (or intercept for any other reason) these endpoints the same way we secure other endpoints. Or at least, if it's already possible, to document how to do so. I'm also curious to know why the endpoints can't be intercepted the same way as any other MVC endpoints.
Comment From: wilkinsona
I'm not sure that this should be the long-term solution, but you can configure the interceptors used for MVC-based actuator endpoints by calling setInterceptors on the auto-configured WebMvcEndpointHandlerMapping bean.
Comment From: jnizet
@wilkinsona Thanks for answering. I tried that, by creating an "ActuatorConfigurer" bean configuring the mapping, from a @Configuration-annotated class. I receive the WebMvcEndpointHandlerMapping bean without problem, call setInterceptors() to add my interceptor, but it seems this is done too late: the interceptors have alredy been initialized at that point.
Here's a complete minimal demo project reproducing the issue, with a README explaining the steps to see the problem: https://github.com/jnizet/demo-actuator. The interesting source file in the project being https://github.com/jnizet/demo-actuator/blob/master/src/main/java/com/example/demo/ActuatorConfig.java
Comment From: wilkinsona
Thanks for the sample. I had assumed that calling setInterceptors would work as long as it was called prior to the handler being initialized, specifically by using a bean post-processor. However, having tried that based on your sample, it doesn't work as I'd hoped. The interceptors are initialized when the application context is set on the handler mapping. That's done by an internal bean post-processor. As far as I can tell there's no way to get another bean post-processor to go before that internal post-processor, even by implementing Ordered. In short, if we're going to support this, we'll have to come up with another approach. Apologies for the bad suggestion, @jnizet.
Out of interest, is your interceptor something that worked in Boot 1.5 and has stopped working in 2.0?
Comment From: jnizet
No apology needed @wilkinsona. Thanks again for looking into this issue.
No, I have never tried doing that in Boot 1.5. But while researching how to solve this issue, I found this stackoverflow answer, that you wrote regarding the same question, and which shows that this was a supported feature in 1.5.
Comment From: wilkinsona
Your searching is clearly better than my memory!
The customiser was added in https://github.com/spring-projects/spring-boot/issues/1933 for exactly this use case. It was then removed in https://github.com/spring-projects/spring-boot/issues/10186 in the belief that @ManagementContextConfiguration provided a more general-purpose solution. That's technically true, but would require you to copy most, if not all, of WebMvcEndpointManagementContextConfiguration. I'm now not sure that's a good idea.
Comment From: jnizet
Thanks!
That works fine indeed, but I think we can agree that it's far from being intuitive, and quite cumbersome. Another option is to provide a configuration class extending WebMvcEndpointManagementContextConfiguration, and overriding its method:
@Configuration
public class ActuatorConfig extends WebMvcEndpointManagementContextConfiguration {
@Autowired
private AuthenticationInterceptor authenticationInterceptor;
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebAnnotationEndpointDiscoverer endpointDiscoverer,
EndpointMediaTypes endpointMediaTypes,
CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties) {
WebMvcEndpointHandlerMapping mapping = super.webEndpointServletHandlerMapping(
endpointDiscoverer,
endpointMediaTypes,
corsProperties,
webEndpointProperties);
mapping.setInterceptors(authenticationInterceptor);
return mapping;
}
}
But it feels hacky, and is still not as easy as it could be, IMHO.
I feel like this should be easier, and is not such an uncommon use-case to secure the actuator endpoints in a custom way. Especially since it was possible before.
Would it be possible to resurrect the customizer?
Comment From: snicoll
@jnizet I was looking at various options there, in particular anything that prevents us to add a customizer for this. Is there a reason why you need to add HandlerInterceptor for this? Why can't you use a regular Filter?
Comment From: jnizet
I think I could, but I've always found HandlerInterceptor better integrated with Spring MVC than filters.
For example, throwing an exception annotated with @ResponseStatus(HttpStatus.UNAUTHORIZED) from a HandlerInterceptor sends back a 401 error, as expected. But throwing the same exception from a filter sends back a 500 error.
Not a big deal, but still, it doesn't behave the same way as it does in usual MVC code.
Comment From: spencergibb
In a filter wouldn't you just set the status to 401 directly?
Comment From: jnizet
Sure, I can do that. But then the response body will not be consistent with the other error response bodies anymore.
I have a workaround that I can live with for this problem, so it's not a big issue, and I would understand if it wasn't fixed. But I find it odd that handler interceptors work in some circumstances but not others, that exceptions work in some circumstances but not others.
I have a working infrastructure to handle authentication and authorization, and the moment I simply add the actuator, I have to find workarounds or change the whole infrastructure to deal with these inconsistencies.
Comment From: rstoyanchev
But then the response body will not be consistent
I could be wrong but I don't see why. All that the ResponseStatusExceptionResolver does is set the response status. If you do the same from a Filter it should make no difference. I'd expect the ErrorController to work just the same.
I get your point about HandlerInterceptor being slightly more integrated with Spring and historically Spring MVC users have had to choose between the convenience of using a HandlerInterceptor vs the extra power that a Filter provides. That said I find that Boot minimized that difference by detecting Filter beans in Spring configuration.
FWIW in Spring WebFlux we did not provide a HandlerInterceptor to avoid this sort of "which should I use" type of ambiguity. I'd encourage sticking to filters unless that brings extra inconvenience.
Comment From: jnizet
@rstoyanchev You're right, my bad. Calling sendError() on the response indeed sends the expected JSON response. Too bad exceptions are not handled the same way.
I take from your comment that Spring recommends the use of filters rather than interceptors. I've always felt the reverse was true, but will try using them instead of handlers and see how it goes.
Comment From: rstoyanchev
Your feeling isn't wrong. It's just that Spring Boot has changed the equation somewhat by eliminating a major reason for preferring HandlerInterceptor, i.e. easy setup in Spring configuration.
There is nothing wrong with using HandlerInterceptor still which is invoked at a different level so it does provide more context to the selected handler. That said generally in a Spring Boot application, I'd use a Filter.
Comment From: jnizet
Thanks Rossen. I've refactored everything to use a filter.
What I gained: - no hack needed to filter requests to the actuator - a single filter for api and actuator requests instead of two separate handler interceptors
What I lost: - no way to use annotated exceptions to send error responses. I had to use HttpServletResponse.sendError() instead - less flexible way to map paths to the filter. AFAIK, only standard servlet patterns are supported whereas ant patterns are supported for interceptors. And, still AFAIK, there is no way to exclude paths, whereas it's possible with interceptors.
Comment From: snicoll
Thanks @jnizet - For the reasons exposed above, I am going to decline this request. Maybe @rstoyanchev has some additional context about what you lost or that discussion can move on StackOverflow for a better visibility. Thanks!
Comment From: rstoyanchev
@jnizet thanks for the feedback.
Indeed there isn't anything built-in for matching paths from a Filter. The code for mapping an interceptor is quite straight forward, so I can imagine some such potential solution for a Filter if we decided to do that at some point.
Comment From: Sarvesh-D
@jnizet Thanks for your code snippet, It worked exactly the way i wanted (to add fine grained access to actuaor endpoints). I had to made some changes to the since the signature for webEndpointServletHandlerMapping has changed i guess in 2.0.2 Release. But yes its a bit hacky and is not intuitive. I see this issue as closed, so wanted to ask do we have a better approach to configure interceptors for actuator endpoints? @wilkinsona I saw sour answer to my comment on issue, but it would be nice to have interceptor based approach instead of filter based approch, IMHO.
Comment From: Sarvesh-D
@jnizet can you share your refactored code using Filter. I still believe interceptor approach is better, but i would prefer code simplicity over webEndpointServletHandlerMapping hack.
Comment From: wilkinsona
We already considered adding the interceptor approach and declined it (that's what this issue is about). I haven't seen anything since then that would change our minds. Another benefit of the filter-based approach that isn't mentioned above is that it works with either the Spring MVC-based or Jersey-based actuator.
Comment From: jnizet
@Sarvesh-D Here's the commit where I moved from interceptor to filter: https://github.com/Ninja-Squad/globe42/commit/69958d24f99906999a8a329f5fb612dbc2c09770 AFAIR, I haven't changed it much since, except I migrated it to Kotlin.
Comment From: JigarJoshi
When I have different management port setup, spring boot actuator is setup on different port). this setup doesn't work.
# actuator listens on 8088 by default
management.server.port=8088
# disable regular HTTP server, only expose management web server
server.port=-1
Comment From: wilkinsona
@JigarJoshi I'm not sure I understand. What's the connection between this issue and the port configuration you've shared above? What do you mean by "this setup"?
Comment From: JigarJoshi
Apologize for not being clear earlier. When I meant "this setup" I was referring to the one @jnizet has it working.
Based on my debugging and understanding, when the management and server port are different it creates another sub application context for management here
When that is the case if I had a @Configuration
@Configuration
// Tried this as well, it didn't wire the filter in the chain @ManagementContextConfiguration
public class ActuatorConfiguration {
@Bean
public ActuatorMetricsFilter actuatorMetricsFilter() {
return new ...;
}
}
And
public class ActuatorMetricsFilter implements javax.servlet.Filter {
// code
}
It did not invoke the filter when I hit any actuator web endpoint. I had to add this special configuration in spring.factories
org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration=\
com.foo.bar.zoo.ActuatorConfiguration
@wilkinsona
Comment From: wilkinsona
Thanks for the explanation, @JigarJoshi. Your understanding of what happens when the management server is running on a separate port is correct. Using @ManagementContextConfiguration and a corresponding entry in spring.factories is the right way to contribute beans to the management context.
Comment From: OlegKuts
We already considered adding the interceptor approach and declined it (that's what this issue is about). I haven't seen anything since then that would change our minds.
Hello @wilkinsona. As to benefits interceptors gives versus filter I can see some:
- Interceptor gives access to Object handler. This gives more flexibility, for example access to method or class level annotations.
- Interceptors are being called after filters, meaning it can be used after Authorization. While u can add custom filter that will be called after Authorization filter, it still needs additional efforts.
My case is I am using Interceptor to check every request my application received. I need do different check depended if user is logged in or not, as well as check annotations values above my Controller's methods. Interceptors fits perfectly for this, except when it comes to Actuators.
Comment From: RaviTeja51
My case is I am using Interceptor to check every request my application received. I need do different check depended if user is logged in or not, as well as check annotations values above my Controller's methods. Interceptors fits perfectly for this, except when it comes to Actuators.
Would you please provide a sample code showing how you used interceptor