Rework security autoconfiguration
This commit combines security autoconfigurations for management endpoints and the rest of the application. By default, if Spring Security is on the classpath, it turns on @EnableWebSecurity. In the presence of another WebSecurityConfigurerAdapter this backs off completely. A default AuthenticationManager is also provided with a user and generated password. This can be turned off by specifying a bean of type AuthenticationManager, AuthenticationProvider or UserDetailsService. Closes gh-7958
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties;
|
||||
import org.springframework.boot.endpoint.EndpointPathResolver;
|
||||
|
||||
/**
|
||||
* {@link EndpointPathResolver} implementation for resolving
|
||||
* actuator endpoint paths based on the endpoint id and management.context-path.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class ManagementEndpointPathResolver implements EndpointPathResolver {
|
||||
|
||||
private final String contextPath;
|
||||
|
||||
public ManagementEndpointPathResolver(ManagementServerProperties properties) {
|
||||
this.contextPath = properties.getContextPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolvePath(String endpointId) {
|
||||
return this.contextPath + "/" + endpointId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,340 +16,32 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.security;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.ManagementContextResolver;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.ManagementEndpointPathResolver;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.boot.autoconfigure.security.AuthenticationManagerConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.IgnoredRequestCustomizer;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityPrerequisite;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityProperties;
|
||||
import org.springframework.boot.autoconfigure.security.SpringBootWebSecurityConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.endpoint.web.mvc.WebEndpointServletHandlerMapping;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.boot.endpoint.EndpointPathResolver;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for security of framework endpoints.
|
||||
* Many aspects of the behavior can be controller with {@link ManagementServerProperties}
|
||||
* via externalized application properties (or via an bean definition of that type to set
|
||||
* the defaults)..
|
||||
* Security configuration for management endpoints.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication(type = Type.SERVLET)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
@ConditionalOnClass({ EnableWebSecurity.class })
|
||||
@AutoConfigureAfter(SecurityAutoConfiguration.class)
|
||||
@AutoConfigureBefore(FallbackWebSecurityAutoConfiguration.class)
|
||||
@EnableConfigurationProperties(ManagementServerProperties.class)
|
||||
@AutoConfigureBefore(SecurityAutoConfiguration.class)
|
||||
public class ManagementWebSecurityAutoConfiguration {
|
||||
|
||||
private static final String[] NO_PATHS = new String[0];
|
||||
|
||||
private static final RequestMatcher MATCH_NONE = new NegatedRequestMatcher(
|
||||
AnyRequestMatcher.INSTANCE);
|
||||
|
||||
@Bean
|
||||
public IgnoredRequestCustomizer managementIgnoredRequestCustomizer(
|
||||
ManagementServerProperties management,
|
||||
ObjectProvider<ManagementContextResolver> contextResolver) {
|
||||
return new ManagementIgnoredRequestCustomizer(management,
|
||||
contextResolver.getIfAvailable());
|
||||
}
|
||||
|
||||
private class ManagementIgnoredRequestCustomizer implements IgnoredRequestCustomizer {
|
||||
|
||||
private final ManagementServerProperties management;
|
||||
|
||||
private final ManagementContextResolver contextResolver;
|
||||
|
||||
ManagementIgnoredRequestCustomizer(ManagementServerProperties management,
|
||||
ManagementContextResolver contextResolver) {
|
||||
this.management = management;
|
||||
this.contextResolver = contextResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(IgnoredRequestConfigurer configurer) {
|
||||
if (!this.management.getSecurity().isEnabled()) {
|
||||
RequestMatcher requestMatcher = LazyEndpointPathRequestMatcher
|
||||
.getRequestMatcher(this.contextResolver);
|
||||
configurer.requestMatchers(requestMatcher);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class ManagementSecurityPropertiesConfiguration
|
||||
implements SecurityPrerequisite {
|
||||
|
||||
private final SecurityProperties securityProperties;
|
||||
|
||||
private final ManagementServerProperties managementServerProperties;
|
||||
|
||||
public ManagementSecurityPropertiesConfiguration(
|
||||
ObjectProvider<SecurityProperties> securityProperties,
|
||||
ObjectProvider<ManagementServerProperties> managementServerProperties) {
|
||||
this.securityProperties = securityProperties.getIfAvailable();
|
||||
this.managementServerProperties = managementServerProperties.getIfAvailable();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
if (this.managementServerProperties != null
|
||||
&& this.securityProperties != null) {
|
||||
this.securityProperties.getUser().getRole()
|
||||
.addAll(this.managementServerProperties.getSecurity().getRoles());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(WebSecurityConfiguration.class)
|
||||
@Conditional(WebSecurityEnablerCondition.class)
|
||||
@EnableWebSecurity
|
||||
protected static class WebSecurityEnabler extends AuthenticationManagerConfiguration {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSecurityEnabler condition.
|
||||
*/
|
||||
static class WebSecurityEnablerCondition extends SpringBootCondition {
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context,
|
||||
AnnotatedTypeMetadata metadata) {
|
||||
String managementEnabled = context.getEnvironment()
|
||||
.getProperty("management.security.enabled", "true");
|
||||
String basicEnabled = context.getEnvironment()
|
||||
.getProperty("security.basic.enabled", "true");
|
||||
ConditionMessage.Builder message = ConditionMessage
|
||||
.forCondition("WebSecurityEnabled");
|
||||
if ("true".equalsIgnoreCase(managementEnabled)
|
||||
&& !"true".equalsIgnoreCase(basicEnabled)) {
|
||||
return ConditionOutcome.match(message.because("security enabled"));
|
||||
}
|
||||
return ConditionOutcome.noMatch(message.because("security disabled"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean({ ManagementWebSecurityConfigurerAdapter.class })
|
||||
@ConditionalOnProperty(prefix = "management.security", name = "enabled", matchIfMissing = true)
|
||||
@EnableConfigurationProperties(SecurityProperties.class)
|
||||
@Order(ManagementServerProperties.BASIC_AUTH_ORDER)
|
||||
protected static class ManagementWebSecurityConfigurerAdapter
|
||||
extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private final SecurityProperties security;
|
||||
|
||||
private final ManagementServerProperties management;
|
||||
|
||||
private final ManagementContextResolver contextResolver;
|
||||
|
||||
public ManagementWebSecurityConfigurerAdapter(SecurityProperties security,
|
||||
ManagementServerProperties management,
|
||||
ObjectProvider<ManagementContextResolver> contextResolver) {
|
||||
this.security = security;
|
||||
this.management = management;
|
||||
this.contextResolver = contextResolver.getIfAvailable();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// secure endpoints
|
||||
RequestMatcher matcher = getRequestMatcher();
|
||||
if (matcher != null) {
|
||||
// Always protect them if present
|
||||
if (this.security.isRequireSsl()) {
|
||||
http.requiresChannel().anyRequest().requiresSecure();
|
||||
}
|
||||
AuthenticationEntryPoint entryPoint = entryPoint();
|
||||
http.exceptionHandling().authenticationEntryPoint(entryPoint);
|
||||
// Match all the requests for actuator endpoints ...
|
||||
http.requestMatcher(matcher).authorizeRequests().anyRequest()
|
||||
.authenticated();
|
||||
http.httpBasic().authenticationEntryPoint(entryPoint).and().cors();
|
||||
// No cookies for management endpoints by default
|
||||
http.csrf().disable();
|
||||
http.sessionManagement()
|
||||
.sessionCreationPolicy(asSpringSecuritySessionCreationPolicy(
|
||||
this.management.getSecurity().getSessions()));
|
||||
SpringBootWebSecurityConfiguration.configureHeaders(http.headers(),
|
||||
this.security.getHeaders());
|
||||
}
|
||||
}
|
||||
|
||||
private SessionCreationPolicy asSpringSecuritySessionCreationPolicy(
|
||||
Enum<?> value) {
|
||||
if (value == null) {
|
||||
return SessionCreationPolicy.STATELESS;
|
||||
}
|
||||
return SessionCreationPolicy.valueOf(value.name());
|
||||
}
|
||||
|
||||
private RequestMatcher getRequestMatcher() {
|
||||
if (this.management.getSecurity().isEnabled()) {
|
||||
return LazyEndpointPathRequestMatcher
|
||||
.getRequestMatcher(this.contextResolver);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private AuthenticationEntryPoint entryPoint() {
|
||||
BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
|
||||
entryPoint.setRealmName(this.security.getBasic().getRealm());
|
||||
return entryPoint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class EndpointPaths {
|
||||
|
||||
public String[] getPaths(
|
||||
WebEndpointServletHandlerMapping endpointHandlerMapping) {
|
||||
if (endpointHandlerMapping == null) {
|
||||
return NO_PATHS;
|
||||
}
|
||||
Collection<EndpointInfo<WebEndpointOperation>> endpoints = endpointHandlerMapping
|
||||
.getEndpoints();
|
||||
Set<String> paths = new LinkedHashSet<>(endpoints.size());
|
||||
for (EndpointInfo<WebEndpointOperation> endpoint : endpoints) {
|
||||
String path = endpointHandlerMapping.getEndpointPath() + "/"
|
||||
+ endpoint.getId();
|
||||
paths.add(path);
|
||||
paths.add(path + "/**");
|
||||
// Add Spring MVC-generated additional paths
|
||||
paths.add(path + ".*");
|
||||
paths.add(path + "/");
|
||||
}
|
||||
return paths.toArray(new String[paths.size()]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class LazyEndpointPathRequestMatcher implements RequestMatcher {
|
||||
|
||||
private final EndpointPaths endpointPaths;
|
||||
|
||||
private final ManagementContextResolver contextResolver;
|
||||
|
||||
private RequestMatcher delegate;
|
||||
|
||||
public static RequestMatcher getRequestMatcher(
|
||||
ManagementContextResolver contextResolver) {
|
||||
if (contextResolver == null) {
|
||||
return null;
|
||||
}
|
||||
ManagementServerProperties management = contextResolver
|
||||
.getApplicationContext().getBean(ManagementServerProperties.class);
|
||||
ServerProperties server = contextResolver.getApplicationContext()
|
||||
.getBean(ServerProperties.class);
|
||||
String path = management.getContextPath();
|
||||
if (StringUtils.hasText(path)) {
|
||||
AntPathRequestMatcher matcher = new AntPathRequestMatcher(
|
||||
server.getServlet().getPath(path) + "/**");
|
||||
return matcher;
|
||||
}
|
||||
// Match all endpoint paths
|
||||
return new LazyEndpointPathRequestMatcher(contextResolver,
|
||||
new EndpointPaths());
|
||||
}
|
||||
|
||||
LazyEndpointPathRequestMatcher(ManagementContextResolver contextResolver,
|
||||
EndpointPaths endpointPaths) {
|
||||
this.contextResolver = contextResolver;
|
||||
this.endpointPaths = endpointPaths;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
if (this.delegate == null) {
|
||||
this.delegate = createDelegate();
|
||||
}
|
||||
return this.delegate.matches(request);
|
||||
}
|
||||
|
||||
private RequestMatcher createDelegate() {
|
||||
ServerProperties server = this.contextResolver.getApplicationContext()
|
||||
.getBean(ServerProperties.class);
|
||||
List<RequestMatcher> matchers = new ArrayList<>();
|
||||
WebEndpointServletHandlerMapping endpointHandlerMapping = getRequiredEndpointHandlerMapping();
|
||||
for (String path : this.endpointPaths.getPaths(endpointHandlerMapping)) {
|
||||
matchers.add(
|
||||
new AntPathRequestMatcher(server.getServlet().getPath(path)));
|
||||
}
|
||||
return (matchers.isEmpty() ? MATCH_NONE : new OrRequestMatcher(matchers));
|
||||
}
|
||||
|
||||
private WebEndpointServletHandlerMapping getRequiredEndpointHandlerMapping() {
|
||||
WebEndpointServletHandlerMapping endpointHandlerMapping = null;
|
||||
ApplicationContext context = this.contextResolver.getApplicationContext();
|
||||
if (context.getBeanNamesForType(
|
||||
WebEndpointServletHandlerMapping.class).length > 0) {
|
||||
endpointHandlerMapping = context
|
||||
.getBean(WebEndpointServletHandlerMapping.class);
|
||||
}
|
||||
if (endpointHandlerMapping == null) {
|
||||
// Maybe there are actually no endpoints (e.g. management.port=-1)
|
||||
endpointHandlerMapping = new WebEndpointServletHandlerMapping("",
|
||||
Collections.emptySet());
|
||||
}
|
||||
return endpointHandlerMapping;
|
||||
}
|
||||
|
||||
public EndpointPathResolver managementEndpointPathResolver(ManagementServerProperties properties) {
|
||||
return new ManagementEndpointPathResolver(properties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ManagementEndpointPathResolver}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class ManagementEndpointPathResolverTests {
|
||||
|
||||
private ManagementEndpointPathResolver resolver;
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
ManagementServerProperties properties = new ManagementServerProperties();
|
||||
properties.setContextPath("/test");
|
||||
this.resolver = new ManagementEndpointPathResolver(properties);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveShouldReturnPathBasedOnContextPath() throws Exception {
|
||||
String path = this.resolver.resolvePath("my-id");
|
||||
assertThat(path.equals("/test/my-id"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
/*
|
||||
* 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.web;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.security.ManagementWebSecurityAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.ProviderManager;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultMatcher;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ManagementWebSecurityAutoConfiguration}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class ManagementWebSecurityAutoConfigurationTests {
|
||||
|
||||
private AnnotationConfigWebApplicationContext context;
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebConfiguration() throws Exception {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.register(SecurityAutoConfiguration.class,
|
||||
WebMvcAutoConfiguration.class,
|
||||
ManagementWebSecurityAutoConfiguration.class,
|
||||
JacksonAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class, ServletEndpointAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class, AuditAutoConfiguration.class);
|
||||
TestPropertyValues.of("security.basic.enabled:false").applyTo(this.context);
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBean(AuthenticationManagerBuilder.class)).isNotNull();
|
||||
FilterChainProxy filterChainProxy = this.context.getBean(FilterChainProxy.class);
|
||||
// 1 for static resources, one for management endpoints and one for the rest
|
||||
assertThat(filterChainProxy.getFilterChains()).hasSize(3);
|
||||
assertThat(filterChainProxy.getFilters("/application/beans")).isNotEmpty();
|
||||
assertThat(filterChainProxy.getFilters("/application/beans/")).isNotEmpty();
|
||||
assertThat(filterChainProxy.getFilters("/application/beans.foo")).isNotEmpty();
|
||||
assertThat(filterChainProxy.getFilters("/application/beans/foo/bar"))
|
||||
.isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathNormalization() throws Exception {
|
||||
String path = "admin/./error";
|
||||
assertThat(StringUtils.cleanPath(path)).isEqualTo("admin/error");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebConfigurationWithExtraRole() throws Exception {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.register(WebConfiguration.class);
|
||||
this.context.refresh();
|
||||
UserDetails user = getUser();
|
||||
ArrayList<GrantedAuthority> authorities = new ArrayList<>(user.getAuthorities());
|
||||
assertThat(authorities).containsAll(AuthorityUtils
|
||||
.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_ACTUATOR"));
|
||||
}
|
||||
|
||||
private UserDetails getUser() {
|
||||
ProviderManager parent = (ProviderManager) this.context
|
||||
.getBean(AuthenticationManager.class);
|
||||
DaoAuthenticationProvider provider = (DaoAuthenticationProvider) parent
|
||||
.getProviders().get(0);
|
||||
UserDetailsService service = (UserDetailsService) ReflectionTestUtils
|
||||
.getField(provider, "userDetailsService");
|
||||
UserDetails user = service.loadUserByUsername("user");
|
||||
return user;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisableIgnoredStaticApplicationPaths() throws Exception {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.register(SecurityAutoConfiguration.class,
|
||||
ManagementWebSecurityAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class);
|
||||
TestPropertyValues.of("security.ignored:none").applyTo(this.context);
|
||||
this.context.refresh();
|
||||
// Just the application and management endpoints now
|
||||
assertThat(this.context.getBean(FilterChainProxy.class).getFilterChains())
|
||||
.hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisableBasicAuthOnApplicationPaths() throws Exception {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.register(WebConfiguration.class);
|
||||
TestPropertyValues.of("security.basic.enabled:false").applyTo(this.context);
|
||||
this.context.refresh();
|
||||
// Just the management endpoints (one filter) and ignores now plus the backup
|
||||
// filter on app endpoints
|
||||
assertThat(this.context.getBean(FilterChainProxy.class).getFilterChains())
|
||||
.hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverrideAuthenticationManager() throws Exception {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.register(TestConfiguration.class, SecurityAutoConfiguration.class,
|
||||
ManagementWebSecurityAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBean(AuthenticationManager.class)).isEqualTo(
|
||||
this.context.getBean(TestConfiguration.class).authenticationManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecurityPropertiesNotAvailable() throws Exception {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.register(TestConfiguration.class, SecurityAutoConfiguration.class,
|
||||
ManagementWebSecurityAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBean(AuthenticationManager.class)).isEqualTo(
|
||||
this.context.getBean(TestConfiguration.class).authenticationManager);
|
||||
}
|
||||
|
||||
// gh-2466
|
||||
@Test
|
||||
public void realmSameForManagement() throws Exception {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.register(AuthenticationConfig.class, SecurityAutoConfiguration.class,
|
||||
ManagementWebSecurityAutoConfiguration.class,
|
||||
JacksonAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class, ServletEndpointAutoConfiguration.class,
|
||||
WebMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class,
|
||||
AuditAutoConfiguration.class);
|
||||
this.context.refresh();
|
||||
|
||||
Filter filter = this.context.getBean("springSecurityFilterChain", Filter.class);
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.addFilters(filter).build();
|
||||
|
||||
// no user (Main)
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/home"))
|
||||
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
|
||||
.andExpect(springAuthenticateRealmHeader());
|
||||
|
||||
// invalid user (Main)
|
||||
mockMvc.perform(
|
||||
MockMvcRequestBuilders.get("/home").header("authorization", "Basic xxx"))
|
||||
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
|
||||
.andExpect(springAuthenticateRealmHeader());
|
||||
|
||||
// no user (Management)
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/beans"))
|
||||
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
|
||||
.andExpect(springAuthenticateRealmHeader());
|
||||
|
||||
// invalid user (Management)
|
||||
mockMvc.perform(
|
||||
MockMvcRequestBuilders.get("/beans").header("authorization", "Basic xxx"))
|
||||
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
|
||||
.andExpect(springAuthenticateRealmHeader());
|
||||
}
|
||||
|
||||
private ResultMatcher springAuthenticateRealmHeader() {
|
||||
return MockMvcResultMatchers.header().string("www-authenticate",
|
||||
Matchers.containsString("realm=\"Spring\""));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ImportAutoConfiguration({ SecurityAutoConfiguration.class,
|
||||
WebMvcAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
|
||||
JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class, ServletEndpointAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class, AuditAutoConfiguration.class,
|
||||
FallbackWebSecurityAutoConfiguration.class })
|
||||
static class WebConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@EnableGlobalAuthentication
|
||||
@Configuration
|
||||
static class AuthenticationConfig {
|
||||
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.inMemoryAuthentication().withUser("user").password("password")
|
||||
.roles("USER");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class TestConfiguration {
|
||||
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager myAuthenticationManager() {
|
||||
this.authenticationManager = (
|
||||
authentication) -> new TestingAuthenticationToken("foo", "bar");
|
||||
return this.authenticationManager;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,6 +35,8 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultActions;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
@@ -194,4 +196,13 @@ public class MvcEndpointCorsIntegrationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.cors();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfigurati
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.security.ManagementWebSecurityAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration;
|
||||
@@ -36,6 +35,7 @@ import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoC
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.test.context.TestSecurityContextHolder;
|
||||
@@ -69,7 +69,8 @@ public class MvcEndpointIntegrationTests {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.register(SecureConfiguration.class);
|
||||
MockMvc mockMvc = createSecureMockMvc();
|
||||
mockMvc.perform(get("/application/beans")).andExpect(status().isUnauthorized());
|
||||
mockMvc.perform(get("/application/beans")
|
||||
.accept(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -79,7 +80,8 @@ public class MvcEndpointIntegrationTests {
|
||||
TestPropertyValues.of("management.context-path:/management")
|
||||
.applyTo(this.context);
|
||||
MockMvc mockMvc = createSecureMockMvc();
|
||||
mockMvc.perform(get("/management/beans")).andExpect(status().isUnauthorized());
|
||||
mockMvc.perform(get("/management/beans")
|
||||
.accept(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -89,31 +91,13 @@ public class MvcEndpointIntegrationTests {
|
||||
new TestingAuthenticationToken("user", "N/A", "ROLE_ACTUATOR"));
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.register(SecureConfiguration.class);
|
||||
TestPropertyValues.of("management.context-path:/management")
|
||||
TestPropertyValues.of("management.context-path:/management",
|
||||
"endpoints.all.web.enabled=true")
|
||||
.applyTo(this.context);
|
||||
MockMvc mockMvc = createSecureMockMvc();
|
||||
mockMvc.perform(get("/management/beans")).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endpointSecurityCanBeDisabledWithCustomContextPath() throws Exception {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.register(SecureConfiguration.class);
|
||||
TestPropertyValues.of("management.context-path:/management",
|
||||
"management.security.enabled:false").applyTo(this.context);
|
||||
MockMvc mockMvc = createSecureMockMvc();
|
||||
mockMvc.perform(get("/management/beans")).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endpointSecurityCanBeDisabled() throws Exception {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.register(SecureConfiguration.class);
|
||||
TestPropertyValues.of("management.security.enabled:false").applyTo(this.context);
|
||||
MockMvc mockMvc = createSecureMockMvc();
|
||||
mockMvc.perform(get("/application/beans")).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
private MockMvc createSecureMockMvc() {
|
||||
return doCreateMockMvc(springSecurity());
|
||||
}
|
||||
@@ -153,8 +137,7 @@ public class MvcEndpointIntegrationTests {
|
||||
}
|
||||
|
||||
@Import(DefaultConfiguration.class)
|
||||
@ImportAutoConfiguration({ SecurityAutoConfiguration.class,
|
||||
ManagementWebSecurityAutoConfiguration.class })
|
||||
@ImportAutoConfiguration({ SecurityAutoConfiguration.class})
|
||||
static class SecureConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* 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.endpoint.mvc;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.ManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.boot.actuate.health.HealthIndicatorFactory;
|
||||
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
|
||||
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.boot.testsupport.runner.classpath.ClassPathExclusions;
|
||||
import org.springframework.boot.testsupport.runner.classpath.ModifiedClassPathRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Integration tests for the health endpoint when Spring Security is not available.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@RunWith(ModifiedClassPathRunner.class)
|
||||
@ClassPathExclusions("spring-security-*.jar")
|
||||
public class NoSpringSecurityHealthMvcEndpointIntegrationTests {
|
||||
|
||||
private AnnotationConfigWebApplicationContext context;
|
||||
|
||||
@After
|
||||
public void closeContext() {
|
||||
this.context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void healthDetailPresent() throws Exception {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.setServletContext(new MockServletContext());
|
||||
this.context.register(TestConfiguration.class);
|
||||
TestPropertyValues.of("management.security.enabled:false").applyTo(this.context);
|
||||
this.context.refresh();
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
|
||||
mockMvc.perform(get("/application/health")).andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString(
|
||||
"\"status\":\"UP\",\"test\":{\"status\":\"UP\",\"hello\":\"world\"}")));
|
||||
}
|
||||
|
||||
@ImportAutoConfiguration({ JacksonAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
DispatcherServletAutoConfiguration.class,
|
||||
EndpointInfrastructureAutoConfiguration.class,
|
||||
ManagementContextAutoConfiguration.class,
|
||||
ServletEndpointAutoConfiguration.class })
|
||||
@Configuration
|
||||
static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
public HealthIndicator testHealthIndicator() {
|
||||
return () -> Health.up().withDetail("hello", "world").build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HealthEndpoint healthEndpoint(
|
||||
Map<String, HealthIndicator> healthIndicators) {
|
||||
return new HealthEndpoint(new HealthIndicatorFactory().createHealthIndicator(
|
||||
new OrderedHealthAggregator(), healthIndicators));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HealthWebEndpointExtension healthWebEndpointExtension(
|
||||
HealthEndpoint delegate) {
|
||||
return new HealthWebEndpointExtension(delegate, new HealthStatusHttpMapper());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user