diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java index 45d0163bc6..2c60e172b8 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/jersey/JerseyEndpointResourceFactory.java @@ -18,6 +18,7 @@ package org.springframework.boot.actuate.endpoint.web.jersey; import java.io.IOException; import java.io.InputStream; +import java.security.Principal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -150,6 +151,10 @@ public class JerseyEndpointResourceFactory { } arguments.putAll(extractPathParameters(data)); arguments.putAll(extractQueryParameters(data)); + Principal principal = data.getSecurityContext().getUserPrincipal(); + if (principal != null) { + arguments.put("principal", principal); + } try { Object response = this.operation.invoke(arguments); return convertToJaxRsResponse(response, data.getRequest().getMethod()); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java index 1bdcab9343..faaf176bc7 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.endpoint.web.reactive; import java.lang.reflect.Method; +import java.security.Principal; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @@ -251,24 +252,38 @@ public abstract class AbstractWebFluxEndpointHandlerMapping * Adapter class to convert an {@link OperationInvoker} into a * {@link ReactiveWebOperation}. */ - private class ReactiveWebOperationAdapter implements ReactiveWebOperation { + private static final class ReactiveWebOperationAdapter + implements ReactiveWebOperation { + + private static final Principal NO_PRINCIPAL = new Principal() { + + @Override + public String getName() { + throw new UnsupportedOperationException(); + } + + }; private final OperationInvoker invoker; - ReactiveWebOperationAdapter(OperationInvoker invoker) { + private ReactiveWebOperationAdapter(OperationInvoker invoker) { this.invoker = invoker; } @Override public Mono> handle(ServerWebExchange exchange, Map body) { - Map arguments = getArguments(exchange, body); - return handleResult((Publisher) this.invoker.invoke(arguments), - exchange.getRequest().getMethod()); + return exchange.getPrincipal().defaultIfEmpty(NO_PRINCIPAL) + .flatMap((principal) -> { + Map arguments = getArguments(exchange, principal, + body); + return handleResult((Publisher) this.invoker.invoke(arguments), + exchange.getRequest().getMethod()); + }); } private Map getArguments(ServerWebExchange exchange, - Map body) { + Principal principal, Map body) { Map arguments = new LinkedHashMap<>(); arguments.putAll(getTemplateVariables(exchange)); if (body != null) { @@ -276,6 +291,9 @@ public abstract class AbstractWebFluxEndpointHandlerMapping } exchange.getRequest().getQueryParams().forEach((name, values) -> arguments .put(name, values.size() == 1 ? values.get(0) : values)); + if (principal != null && principal != NO_PRINCIPAL) { + arguments.put("principal", principal); + } return arguments; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java index 0b6714d61a..74d0d8fee6 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.endpoint.web.servlet; import java.lang.reflect.Method; +import java.security.Principal; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; @@ -257,6 +258,10 @@ public abstract class AbstractWebMvcEndpointHandlerMapping } request.getParameterMap().forEach((name, values) -> arguments.put(name, values.length == 1 ? values[0] : Arrays.asList(values))); + Principal principal = request.getUserPrincipal(); + if (principal != null) { + arguments.put("principal", principal); + } return arguments; } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java index b11c119cb0..4c0c87fc1d 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.endpoint.web.annotation; import java.net.InetSocketAddress; +import java.security.Principal; import java.time.Duration; import java.util.Collections; import java.util.HashMap; @@ -326,6 +327,22 @@ public abstract class AbstractWebEndpointIntegrationTests client.get().uri("/principal") + .accept(MediaType.APPLICATION_JSON).exchange().expectStatus() + .isOk().expectBody(String.class).isEqualTo("None")); + } + + @Test + public void principalIsAvailableWhenRequestHasAPrincipal() { + load(getSecuredPrincipalEndpointConfiguration(), + (client) -> client.get().uri("/principal") + .accept(MediaType.APPLICATION_JSON).exchange().expectStatus() + .isOk().expectBody(String.class).isEqualTo("Alice")); + } + protected abstract T createApplicationContext(Class... config); protected abstract int getPort(T context); @@ -360,6 +377,8 @@ public abstract class AbstractWebEndpointIntegrationTests getSecuredPrincipalEndpointConfiguration(); + protected void load(Class configuration, Consumer clientConsumer) { load(configuration, "/endpoints", (context, client) -> clientConsumer.accept(client)); @@ -517,6 +536,17 @@ public abstract class AbstractWebEndpointIntegrationTests getSecuredPrincipalEndpointConfiguration() { + return SecuredPrincipalEndpointConfiguration.class; + } + @Configuration static class JerseyConfiguration { @@ -105,6 +120,43 @@ public class JerseyWebEndpointIntegrationTests extends } + @Configuration + @Import(PrincipalEndpointConfiguration.class) + static class SecuredPrincipalEndpointConfiguration { + + @Bean + public Filter securityFilter() { + return new OncePerRequestFilter() { + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + filterChain.doFilter(new HttpServletRequestWrapper(request) { + + @Override + public Principal getUserPrincipal() { + + return new Principal() { + + @Override + public String getName() { + return "Alice"; + } + + }; + + } + + }, response); + } + + }; + + } + + } + private static final class ObjectMapperContextResolver implements ContextResolver { diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointIntegrationTests.java index 3e935ba171..e277663e16 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/WebFluxEndpointIntegrationTests.java @@ -16,9 +16,11 @@ package org.springframework.boot.actuate.endpoint.web.reactive; +import java.security.Principal; import java.util.Arrays; import org.junit.Test; +import reactor.core.publisher.Mono; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.annotation.AbstractWebEndpointIntegrationTests; @@ -34,12 +36,17 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.reactive.config.EnableWebFlux; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.ServerWebExchangeDecorator; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import static org.assertj.core.api.Assertions.assertThat; @@ -95,6 +102,11 @@ public class WebFluxEndpointIntegrationTests return context.getBean(ReactiveConfiguration.class).port; } + @Override + protected Class getSecuredPrincipalEndpointConfiguration() { + return SecuredPrincipalEndpointConfiguration.class; + } + @Configuration @EnableWebFlux @ImportAutoConfiguration(ErrorWebFluxAutoConfiguration.class) @@ -132,4 +144,36 @@ public class WebFluxEndpointIntegrationTests } + @Import(PrincipalEndpointConfiguration.class) + static class SecuredPrincipalEndpointConfiguration { + + @Bean + public WebFilter webFilter() { + return new WebFilter() { + + @Override + public Mono filter(ServerWebExchange exchange, + WebFilterChain chain) { + return chain.filter(new ServerWebExchangeDecorator(exchange) { + + @Override + public Mono getPrincipal() { + return Mono.just(new Principal() { + + @Override + public String getName() { + return "Alice"; + } + + }); + } + + }); + } + + }; + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/MvcWebEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/MvcWebEndpointIntegrationTests.java index 66c72fe661..b056702bd2 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/MvcWebEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/MvcWebEndpointIntegrationTests.java @@ -16,8 +16,17 @@ package org.springframework.boot.actuate.endpoint.web.servlet; +import java.io.IOException; +import java.security.Principal; import java.util.Arrays; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; + import org.junit.Test; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; @@ -35,10 +44,12 @@ import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactor import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.filter.OncePerRequestFilter; import static org.assertj.core.api.Assertions.assertThat; @@ -93,6 +104,11 @@ public class MvcWebEndpointIntegrationTests extends return context.getWebServer().getPort(); } + @Override + protected Class getSecuredPrincipalEndpointConfiguration() { + return SecuredPrincipalEndpointConfiguration.class; + } + @Configuration @ImportAutoConfiguration({ JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, @@ -120,4 +136,41 @@ public class MvcWebEndpointIntegrationTests extends } + @Configuration + @Import(PrincipalEndpointConfiguration.class) + static class SecuredPrincipalEndpointConfiguration { + + @Bean + public Filter securityFilter() { + return new OncePerRequestFilter() { + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + filterChain.doFilter(new HttpServletRequestWrapper(request) { + + @Override + public Principal getUserPrincipal() { + + return new Principal() { + + @Override + public String getName() { + return "Alice"; + } + + }; + + } + + }, response); + } + + }; + + } + + } + }