io.hawt.springboot.HawtioManagementConfiguration Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hawtio-springboot Show documentation
Show all versions of hawtio-springboot Show documentation
Hawtio :: Spring Boot 2.x starter
The newest version!
package io.hawt.springboot;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import io.hawt.web.auth.AuthConfigurationServlet;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.http.HttpServletRequest;
import io.hawt.system.ConfigManager;
import io.hawt.web.auth.AuthenticationConfiguration;
import io.hawt.web.auth.AuthenticationFilter;
import io.hawt.web.auth.ClientRouteRedirectFilter;
import io.hawt.web.auth.LoginServlet;
import io.hawt.web.auth.LogoutServlet;
import io.hawt.web.auth.Redirector;
import io.hawt.web.auth.SessionExpiryFilter;
import io.hawt.web.auth.keycloak.KeycloakServlet;
import io.hawt.web.auth.keycloak.KeycloakUserServlet;
import io.hawt.web.filters.BaseTagHrefFilter;
import io.hawt.web.filters.CORSFilter;
import io.hawt.web.filters.CacheHeadersFilter;
import io.hawt.web.filters.ContentSecurityPolicyFilter;
import io.hawt.web.filters.FlightRecordingDownloadFacade;
import io.hawt.web.filters.PublicKeyPinningFilter;
import io.hawt.web.filters.ReferrerPolicyFilter;
import io.hawt.web.filters.StrictTransportSecurityFilter;
import io.hawt.web.filters.XContentTypeOptionsFilter;
import io.hawt.web.filters.XFrameOptionsFilter;
import io.hawt.web.filters.XXSSProtectionFilter;
import io.hawt.web.proxy.ProxyServlet;
import jakarta.servlet.http.HttpServletResponse;
import org.jolokia.support.spring.actuator.JolokiaEndpoint;
import org.jolokia.support.spring.actuator.JolokiaEndpointAutoConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.AbstractController;
import org.springframework.web.servlet.mvc.AbstractUrlViewController;
import static io.hawt.web.filters.BaseTagHrefFilter.PARAM_APPLICATION_CONTEXT_PATH;
@Configuration
@AutoConfigureAfter(JolokiaEndpointAutoConfiguration.class)
@ConditionalOnBean(HawtioEndpoint.class)
public class HawtioManagementConfiguration {
// a path within Spring server or management server that's the "base" of hawtio actuator.
// By default it should be "/actuator/hawtio", but may be affected by application.properties settings
// (for example management.endpoints.web.base-path which defaults to "/actuator", but can be customized)
private final String hawtioPath;
public HawtioManagementConfiguration(final EndpointPathResolver pathResolver) {
this.hawtioPath = pathResolver.resolve("hawtio");
}
@Autowired
public void initializeHawtioPlugins(final HawtioEndpoint hawtioEndpoint, final Optional> plugins) {
hawtioEndpoint.setPlugins(plugins.orElse(Collections.emptyList()));
}
@Bean
public ConfigManager hawtioConfigManager(final HawtioProperties hawtioProperties) {
return new ConfigManager(hawtioProperties.get()::get);
}
@Bean
public SimpleUrlHandlerMapping hawtioWelcomeFiles(final EndpointPathResolver pathResolver) {
AbstractController abstractController = new AbstractController() {
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
return new ModelAndView("forward:" + pathResolver.resolve("hawtio") + "/index.html");
}
};
// https://github.com/hawtio/hawtio/issues/3382
// order=10 to handle "/actuator/hawtio/" URL as forward to "/actuator/hawtio/index.html" for consistency
return new SimpleUrlHandlerMapping(Map.of(pathResolver.resolve("hawtio") + "/", abstractController), 10);
}
@Bean
@ConditionalOnBean(JolokiaEndpoint.class)
@ConditionalOnExposedEndpoint(name = "jolokia")
public SimpleUrlHandlerMapping hawtioUrlMapping(final EndpointPathResolver pathResolver) {
final String jolokiaPath = pathResolver.resolve("jolokia");
final String hawtioPath = pathResolver.resolve("hawtio");
final SilentSimpleUrlHandlerMapping mapping = new SilentSimpleUrlHandlerMapping();
final Map urlMap = new HashMap<>();
if (!hawtioPath.isEmpty()) {
final String hawtioJolokiaPath = pathResolver.resolveUrlMapping("hawtio", "jolokia", "**");
urlMap.put(
hawtioJolokiaPath,
new JolokiaForwardingController(hawtioPath + "/jolokia", jolokiaPath));
mapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
} else {
urlMap.put(SilentSimpleUrlHandlerMapping.DUMMY, null);
}
mapping.setUrlMap(urlMap);
return mapping;
}
// -------------------------------------------------------------------------
// Redirect Helper
// -------------------------------------------------------------------------
@Bean
public Redirector redirector() {
final Redirector redirector = new Redirector();
redirector.setApplicationContextPath(hawtioPath);
return redirector;
}
// -------------------------------------------------------------------------
// Filters
// -------------------------------------------------------------------------
// use @Order annotation to ensure filter mapping as in web.xml. method invocation order _may_ be JVM dependent
/**
* Since Spring Boot 3.0, paths with trailing slash are not automatically processed
* and need to be explicitly configured for handling them. This Spring Boot
* specific filter used to redirect {@code /actuator/hawtio/} requests to {@code /actuator/hawtio/index.html},
* but now it used for consistency with WAR deployments - to redirect {@code /actuator/hawtio} to
* {@code /actuator/hawtio/}. Then the request is being processed by {@link ClientRouteRedirectFilter}.
*/
@Bean
@Order(0)
public FilterRegistrationBean trailingSlashFilter(final Redirector redirector) {
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
filter.setFilter(new TrailingSlashFilter(redirector));
filter.addUrlPatterns(hawtioPath);
return filter;
}
@Bean
@Order(1)
public FilterRegistrationBean sessionExpiryFilter() {
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
filter.setFilter(new SessionExpiryFilter());
filter.addUrlPatterns(hawtioPath + "/*");
return filter;
}
@Bean
@Order(2)
public FilterRegistrationBean cacheFilter() {
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
filter.setFilter(new CacheHeadersFilter());
filter.addUrlPatterns(hawtioPath + "/*");
return filter;
}
@Bean
@Order(3)
public FilterRegistrationBean hawtioCorsFilter() {
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
filter.setFilter(new CORSFilter());
filter.addUrlPatterns(hawtioPath + "/*");
return filter;
}
@Bean
@Order(4)
public FilterRegistrationBean xframeOptionsFilter() {
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
filter.setFilter(new XFrameOptionsFilter());
filter.addUrlPatterns(hawtioPath + "/*");
return filter;
}
@Bean
@Order(5)
public FilterRegistrationBean xxssProtectionFilter() {
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
filter.setFilter(new XXSSProtectionFilter());
filter.addUrlPatterns(hawtioPath + "/*");
return filter;
}
@Bean
@Order(6)
public FilterRegistrationBean xContentTypeOptionsFilter() {
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
filter.setFilter(new XContentTypeOptionsFilter());
filter.addUrlPatterns(hawtioPath + "/*");
return filter;
}
@Bean
@Order(7)
public FilterRegistrationBean contentSecurityPolicyFilter() {
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
filter.setFilter(new ContentSecurityPolicyFilter());
filter.addUrlPatterns(hawtioPath + "/*");
return filter;
}
@Bean
@Order(8)
public FilterRegistrationBean strictTransportSecurityFilter() {
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
filter.setFilter(new StrictTransportSecurityFilter());
filter.addUrlPatterns(hawtioPath + "/*");
return filter;
}
@Bean
@Order(9)
public FilterRegistrationBean publicKeyPinningFilter() {
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
filter.setFilter(new PublicKeyPinningFilter());
filter.addUrlPatterns(hawtioPath + "/*");
return filter;
}
@Bean
@Order(10)
public FilterRegistrationBean referrerPolicyFilter() {
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
filter.setFilter(new ReferrerPolicyFilter());
filter.addUrlPatterns(hawtioPath + "/*");
return filter;
}
// note on io.hawt.web.auth.AuthenticationFilter
// /actuator/hawtio/jolokia/* requests are forwarded to /actuator/jolokia/* using
// io.hawt.springboot.HawtioManagementConfiguration.JolokiaForwardingController which returns
// "forward:/actuator/jolokia" view name handled by DispatcherServlet. Such request (with original URI
// /actuator/hawtio/jolokia/*) is already handled by all the above Hawtio filters, so there's NO NEED
// to map "/hawtio/jolokia/*" pattern to AuthenticationFilter, because filters will be invoked by the forward
//
// when using /actuator/jolokia/* request, we invoke Jolokia Actuator endpoint directly and NO Hawtio filters
// will be invoked (which is fine), but we need AuthenticationFilter being mapped to "/actuator/jolokia/*"
/**
* {@link AuthenticationFilter} handling direct Jolokia Actuator endpoint requests ({@code /actuator/jolokia/*}).
* @param pathResolver
* @return
*/
@Bean
@Order(11)
@ConditionalOnBean(JolokiaEndpoint.class)
@ConditionalOnExposedEndpoint(name = "jolokia")
public FilterRegistrationBean jolokiaAuthenticationFilter(final EndpointPathResolver pathResolver) {
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
filter.setFilter(new AuthenticationFilter());
filter.addUrlPatterns(pathResolver.resolveUrlMapping("jolokia", "*"));
filter.setDispatcherTypes(DispatcherType.ERROR, DispatcherType.FORWARD, DispatcherType.REQUEST);
return filter;
}
/**
* {@link AuthenticationFilter} handling proxy requests ({@code /actuator/hawtio/proxy/*}). No need to declare
* {@code /actuator/hawtio/jolokia/*} mapping here, as it'd be a duplication of {@link #jolokiaAuthenticationFilter}
* mapping.
* @return
*/
@Bean
@Order(11)
public FilterRegistrationBean hawtioProxyAuthenticationFilter() {
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
filter.setFilter(new AuthenticationFilter());
filter.addUrlPatterns(hawtioPath + "/proxy/*");
filter.setDispatcherTypes(DispatcherType.ERROR, DispatcherType.FORWARD, DispatcherType.REQUEST);
return filter;
}
/**
* This filter was called {@code LoginRedirectFilter}, but now it also handles redirection/forwarding for
* client-side routes (React Router), so we no longer need this special RegExp mapped
* {@link org.springframework.web.bind.annotation.RequestMapping} annotated method in {@link HawtioEndpoint}.
*
* @param redirector
* @param pathResolver
* @return
*/
@Bean
@Order(12)
public FilterRegistrationBean clientRouteRedirectFilter(final Redirector redirector,
EndpointPathResolver pathResolver) {
final String[] unsecuredPaths = prependContextPath(AuthenticationConfiguration.UNSECURED_PATHS);
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
final ClientRouteRedirectFilter clientRouteRedirectFilter = new ClientRouteRedirectFilter(unsecuredPaths, pathResolver.resolve("hawtio"));
clientRouteRedirectFilter.setRedirector(redirector);
filter.setFilter(clientRouteRedirectFilter);
filter.addUrlPatterns(hawtioPath + "/*");
return filter;
}
@Bean
@Order(13)
public FilterRegistrationBean baseTagHrefFilter(final EndpointPathResolver pathResolver) {
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
final BaseTagHrefFilter baseTagHrefFilter = new BaseTagHrefFilter();
filter.setFilter(baseTagHrefFilter);
filter.addUrlPatterns(hawtioPath + "/");
filter.addUrlPatterns(hawtioPath + "/index.html");
filter.setDispatcherTypes(DispatcherType.ERROR, DispatcherType.FORWARD, DispatcherType.REQUEST);
filter.addInitParameter(PARAM_APPLICATION_CONTEXT_PATH, pathResolver.resolve("hawtio"));
return filter;
}
@Bean
@Order(14)
public FilterRegistrationBean flightRecorderDownloadFacade(final EndpointPathResolver pathResolver) {
final FilterRegistrationBean filter = new FilterRegistrationBean<>();
filter.setFilter(new FlightRecordingDownloadFacade());
filter.addUrlPatterns(hawtioPath + "/jolokia/*");
filter.addUrlPatterns(hawtioPath + "/proxy/*");
filter.setDispatcherTypes(DispatcherType.ERROR, DispatcherType.FORWARD, DispatcherType.REQUEST);
return filter;
}
// -------------------------------------------------------------------------
// Servlets
// -------------------------------------------------------------------------
// Jolokia agent servlet is provided by Spring Boot actuator
@Bean
public ServletRegistrationBean jolokiaProxyServlet() {
return new ServletRegistrationBean<>(
new ProxyServlet(),
hawtioPath + "/proxy/*");
}
@Bean
public ServletRegistrationBean userServlet() {
return new ServletRegistrationBean<>(
new KeycloakUserServlet(),
hawtioPath + "/user/*");
}
@Bean
public ServletRegistrationBean loginServlet(Redirector redirector) {
LoginServlet loginServlet = new LoginServlet();
loginServlet.setRedirector(redirector);
return new ServletRegistrationBean<>(loginServlet,
hawtioPath + "/auth/login");
}
@Bean
public ServletRegistrationBean logoutServlet(Redirector redirector) {
LogoutServlet logoutServlet = new LogoutServlet();
logoutServlet.setRedirector(redirector);
return new ServletRegistrationBean<>(logoutServlet,
hawtioPath + "/auth/logout");
}
@Bean
public ServletRegistrationBean keycloakServlet() {
return new ServletRegistrationBean<>(
new KeycloakServlet(),
hawtioPath + "/keycloak/*");
}
@Bean
public ServletRegistrationBean oidcServlet() {
return new ServletRegistrationBean<>(
new AuthConfigurationServlet(),
hawtioPath + "/auth/config/*");
}
// -------------------------------------------------------------------------
// Listeners
// -------------------------------------------------------------------------
@Bean
public ServletListenerRegistrationBean hawtioContextListener(final ConfigManager configManager) {
return new ServletListenerRegistrationBean<>(
new SpringHawtioContextListener(configManager, hawtioPath));
}
// -------------------------------------------------------------------------
// Session Config
// -------------------------------------------------------------------------
@Bean
public ServletContextInitializer servletContextInitializer() {
return servletContext -> servletContext.getSessionCookieConfig().setHttpOnly(true);
}
// -------------------------------------------------------------------------
// Utilities
// -------------------------------------------------------------------------
private String[] prependContextPath(String[] paths) {
return Arrays.stream(paths)
.map(path -> hawtioPath + path)
.toArray(String[]::new);
}
private static class JolokiaForwardingController extends AbstractUrlViewController {
private final String hawtioJolokiaPath;
private final String jolokiaPath;
JolokiaForwardingController(final String hawtioJolokiaPath, final String jolokiaPath) {
this.hawtioJolokiaPath = hawtioJolokiaPath;
this.jolokiaPath = jolokiaPath;
}
@Override
protected String getViewNameForRequest(final HttpServletRequest request) {
// Forward requests from hawtio/jolokia to the Spring Boot Jolokia actuator endpoint
final StringBuilder b = new StringBuilder();
b.append("forward:");
b.append(jolokiaPath);
final String pathQuery = request.getRequestURI().substring(
request.getContextPath().length() + hawtioJolokiaPath.length());
b.append(pathQuery);
if (request.getQueryString() != null) {
b.append('?').append(request.getQueryString());
}
return b.toString();
}
}
// Does not warn when no mappings are present
private static class SilentSimpleUrlHandlerMapping extends SimpleUrlHandlerMapping {
private static final String DUMMY = "/";
@Override
protected void registerHandler(final String urlPath, final Object handler) {
if (!DUMMY.equals(urlPath)) {
super.registerHandler(urlPath, handler);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy