Skip to content

Commit

Permalink
Rework security request matchers
Browse files Browse the repository at this point in the history
Update the security request matchers so that a bean is no longer needed
when the matcher is used. Matchers can now be build by starting from
the `EndpointRequest` or `StaticResourceRequest` classes. For example:

http.authorizeRequests()
  .requestMatchers(EndpointRequest.to("status", "info")).permitAll()
  .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR")
  .requestMatchers(StaticResourceRequest.toCommonLocations()).permitAll()

Closes gh-7958
  • Loading branch information
philwebb committed Sep 12, 2017
1 parent 2e51b48 commit 46dfe38
Show file tree
Hide file tree
Showing 20 changed files with 1,294 additions and 29 deletions.
Expand Up @@ -33,7 +33,7 @@
* @author Stephane Nicoll
* @since 2.0.0
*/
public final class EndpointProvider<T extends Operation> {
public class EndpointProvider<T extends Operation> {

private final EndpointDiscoverer<T> discoverer;

Expand Down
@@ -0,0 +1,63 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.endpoint.web;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
import org.springframework.boot.actuate.endpoint.EndpointInfo;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
import org.springframework.util.Assert;

/**
* Default {@link EndpointPathProvider} implementation.
*
* @author Phillip Webb
* @since 2.0.0
*/
public class DefaultEndpointPathProvider implements EndpointPathProvider {

private final Collection<EndpointInfo<WebEndpointOperation>> endpoints;

private final String contextPath;

public DefaultEndpointPathProvider(EndpointProvider<WebEndpointOperation> provider,
ManagementServerProperties managementServerProperties) {
this.endpoints = provider.getEndpoints();
this.contextPath = managementServerProperties.getContextPath();
}

@Override
public List<String> getPaths() {
return this.endpoints.stream().map(this::getPath).collect(Collectors.toList());
}

@Override
public String getPath(String id) {
Assert.notNull(id, "ID must not be null");
return this.endpoints.stream().filter((info) -> id.equals(info.getId()))
.findFirst().map(this::getPath).orElse(null);
}

private String getPath(EndpointInfo<WebEndpointOperation> endpointInfo) {
return this.contextPath + "/" + endpointInfo.getId();
}

}
@@ -0,0 +1,42 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.endpoint.web;

import java.util.List;

/**
* Interface that provides path information for web mapped endpints.
*
* @author Phillip Webb
* @since 2.0.0
*/
public interface EndpointPathProvider {

/**
* Return all mapped endpoint paths.
* @return all paths
*/
List<String> getPaths();

/**
* Return the path for the endpoint with the specified ID.
* @param id the endpoint ID
* @return the path of the endpoint or {@code null}
*/
String getPath(String id);

}
Expand Up @@ -21,6 +21,8 @@
import org.glassfish.jersey.server.ResourceConfig;

import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.DefaultEndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
Expand Down Expand Up @@ -60,4 +62,12 @@ public ResourceConfigCustomizer webEndpointRegistrar(
provider.getEndpoints())));
}

@Bean
@ConditionalOnMissingBean
public EndpointPathProvider endpointPathProvider(
EndpointProvider<WebEndpointOperation> provider,
ManagementServerProperties managementServerProperties) {
return new DefaultEndpointPathProvider(provider, managementServerProperties);
}

}
Expand Up @@ -17,6 +17,8 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.web.reactive;

import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.DefaultEndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
Expand Down Expand Up @@ -49,4 +51,12 @@ public WebFluxEndpointHandlerMapping webEndpointReactiveHandlerMapping(
provider.getEndpoints());
}

@Bean
@ConditionalOnMissingBean
public EndpointPathProvider endpointPathProvider(
EndpointProvider<WebEndpointOperation> provider,
ManagementServerProperties managementServerProperties) {
return new DefaultEndpointPathProvider(provider, managementServerProperties);
}

}
Expand Up @@ -17,6 +17,8 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet;

import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.DefaultEndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
Expand Down Expand Up @@ -61,6 +63,14 @@ public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(
return handlerMapping;
}

@Bean
@ConditionalOnMissingBean
public EndpointPathProvider endpointPathProvider(
EndpointProvider<WebEndpointOperation> provider,
ManagementServerProperties managementServerProperties) {
return new DefaultEndpointPathProvider(provider, managementServerProperties);
}

private CorsConfiguration getCorsConfiguration(CorsEndpointProperties properties) {
if (CollectionUtils.isEmpty(properties.getAllowedOrigins())) {
return null;
Expand Down
@@ -0,0 +1,193 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.security;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.security.ApplicationContextRequestMatcher;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;

/**
* Factory that can be used to create a {@link RequestMatcher} for actuator endpoint
* locations.
*
* @author Madhura Bhave
* @author Phillip Webb
* @since 2.0.0
*/
public final class EndpointRequest {

private EndpointRequest() {
}

/**
* Returns a matcher that includes all {@link Endpoint actuator endpoints}. The
* {@link EndpointRequestMatcher#excluding(Class...) excluding} method can be used to
* further remove specific endpoints if required. For example: <pre class="code">
* EndpointRequestMatcher.toAnyEndpoint().excluding(ShutdownEndpoint.class)
* </pre>
* @return the configured {@link RequestMatcher}
*/
public static EndpointRequestMatcher toAnyEndpoint() {
return new EndpointRequestMatcher();
}

/**
* Returns a matcher that includes the specified {@link Endpoint actuator endpoints}.
* For example: <pre class="code">
* EndpointRequestMatcher.to(ShutdownEndpoint.class, HealthEndpoint.class)
* </pre>
* @param endpoints the endpoints to include
* @return the configured {@link RequestMatcher}
*/
public static EndpointRequestMatcher to(Class<?>... endpoints) {
return new EndpointRequestMatcher(endpoints);
}

/**
* Returns a matcher that includes the specified {@link Endpoint actuator endpoints}.
* For example: <pre class="code">
* EndpointRequestMatcher.to("shutdown", "health")
* </pre>
* @param endpoints the endpoints to include
* @return the configured {@link RequestMatcher}
*/
public static EndpointRequestMatcher to(String... endpoints) {
return new EndpointRequestMatcher(endpoints);
}

/**
* The request matcher used to match against {@link Endpoint actuator endpoints}.
*/
public final static class EndpointRequestMatcher
extends ApplicationContextRequestMatcher<EndpointPathProvider> {

private final List<Object> includes;

private final List<Object> excludes;

private RequestMatcher delegate;

private EndpointRequestMatcher() {
super(EndpointPathProvider.class);
this.includes = Collections.emptyList();
this.excludes = Collections.emptyList();
}

private EndpointRequestMatcher(Class<?>[] endpoints) {
super(EndpointPathProvider.class);
this.includes = Arrays.asList((Object[]) endpoints);
this.excludes = Collections.emptyList();
}

private EndpointRequestMatcher(String[] endpoints) {
super(EndpointPathProvider.class);
this.includes = Arrays.asList((Object[]) endpoints);
this.excludes = Collections.emptyList();
}

private EndpointRequestMatcher(List<Object> includes, List<Object> excludes) {
super(EndpointPathProvider.class);
this.includes = includes;
this.excludes = excludes;
}

EndpointRequestMatcher excluding(Class<?>... endpoints) {
List<Object> excludes = new ArrayList<>(this.excludes);
excludes.addAll(Arrays.asList((Object[]) endpoints));
return new EndpointRequestMatcher(this.includes, excludes);
}

EndpointRequestMatcher excluding(String... endpoints) {
List<Object> excludes = new ArrayList<>(this.excludes);
excludes.addAll(Arrays.asList((Object[]) endpoints));
return new EndpointRequestMatcher(this.includes, excludes);
}

@Override
protected void initialized(EndpointPathProvider endpointPathProvider) {
Set<String> paths = new LinkedHashSet<>(this.includes.isEmpty()
? endpointPathProvider.getPaths() : Collections.emptyList());
streamPaths(this.includes, endpointPathProvider).forEach(paths::add);
streamPaths(this.excludes, endpointPathProvider).forEach(paths::remove);
this.delegate = new OrRequestMatcher(getDelegateMatchers(paths));
}

private Stream<String> streamPaths(List<Object> source,
EndpointPathProvider endpointPathProvider) {
return source.stream().filter(Objects::nonNull).map(this::getPathId)
.map(endpointPathProvider::getPath);
}

private String getPathId(Object source) {
if (source instanceof String) {
return (String) source;
}
if (source instanceof Class) {
return getPathId((Class<?>) source);
}
throw new IllegalStateException("Unsupported source " + source);
}

private String getPathId(Class<?> source) {
Endpoint annotation = AnnotationUtils.findAnnotation(source, Endpoint.class);
Assert.state(annotation != null,
"Class " + source + " is not annotated with @Endpoint");
return annotation.id();
}

private List<RequestMatcher> getDelegateMatchers(Set<String> paths) {
return paths.stream().map((path) -> new AntPathRequestMatcher(path + "/**"))
.collect(Collectors.toList());
}

@Override
public boolean matches(HttpServletRequest request) {
try {
return super.matches(request);
}
catch (BeanCreationException ex) {
return false;
}
}

@Override
protected boolean matches(HttpServletRequest request,
EndpointPathProvider context) {
return this.delegate.matches(request);
}

}

}
@@ -0,0 +1,20 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Auto-configuration for security.
*/
package org.springframework.boot.actuate.autoconfigure.security;

0 comments on commit 46dfe38

Please sign in to comment.