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-7958pull/10101/head
parent
f60ad0df74
commit
e08ddbf838
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.autoconfigure.security;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
|
||||
|
||||
/**
|
||||
* {@link GlobalAuthenticationConfigurerAdapter} to trigger early initialization of
|
||||
* {@code @EnableAutoConfiguration} beans. This configuration is imported from
|
||||
* {@link AuthenticationConfiguration} to ensure that users are able to configure the
|
||||
* {@link AuthenticationManagerBuilder} from their {@code @EnableAutoConfiguration} or
|
||||
* {@code @SpringBootApplication} configuration class:
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Autowired
|
||||
* public void configureGlobal(AuthenticationManagerBuilder auth) {
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.1.11
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(GlobalAuthenticationConfigurerAdapter.class)
|
||||
public class BootGlobalAuthenticationConfiguration {
|
||||
|
||||
@Bean
|
||||
public static BootGlobalAuthenticationConfigurationAdapter bootGlobalAuthenticationConfigurationAdapter(
|
||||
ApplicationContext context) {
|
||||
return new BootGlobalAuthenticationConfigurationAdapter(context);
|
||||
}
|
||||
|
||||
private static class BootGlobalAuthenticationConfigurationAdapter
|
||||
extends GlobalAuthenticationConfigurerAdapter {
|
||||
|
||||
private static final Log logger = LogFactory
|
||||
.getLog(BootGlobalAuthenticationConfiguration.class);
|
||||
|
||||
private final ApplicationContext context;
|
||||
|
||||
BootGlobalAuthenticationConfigurationAdapter(ApplicationContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(AuthenticationManagerBuilder auth) {
|
||||
Map<String, Object> beansWithAnnotation = this.context
|
||||
.getBeansWithAnnotation(EnableAutoConfiguration.class);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Eagerly initializing " + beansWithAnnotation);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.autoconfigure.security;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorController;
|
||||
import org.springframework.boot.endpoint.Endpoint;
|
||||
import org.springframework.boot.endpoint.EndpointPathResolver;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
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;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
||||
/**
|
||||
* Provides request matchers that can be used to
|
||||
* configure security for static resources and the error controller path in a custom
|
||||
* {@link WebSecurityConfigurerAdapter}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public final class SpringBootSecurity {
|
||||
|
||||
/**
|
||||
* Used as a wildcard matcher for all endpoints.
|
||||
*/
|
||||
public final static String ALL_ENDPOINTS = "**";
|
||||
|
||||
private static String[] STATIC_RESOURCES = new String[]{"/css/**", "/js/**",
|
||||
"/images/**", "/webjars/**", "/**/favicon.ico"};
|
||||
|
||||
private final EndpointPathResolver endpointPathResolver;
|
||||
|
||||
private final ErrorController errorController;
|
||||
|
||||
SpringBootSecurity(EndpointPathResolver endpointPathResolver, ErrorController errorController) {
|
||||
this.endpointPathResolver = endpointPathResolver;
|
||||
this.errorController = errorController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link RequestMatcher} that matches on all endpoint paths with given ids.
|
||||
* @param ids the endpoint ids
|
||||
* @return the request matcher
|
||||
*/
|
||||
public RequestMatcher endpointIds(String... ids) {
|
||||
Assert.notEmpty(ids, "At least one endpoint id must be specified.");
|
||||
List<String> pathList = Arrays.asList(ids);
|
||||
if (pathList.contains(ALL_ENDPOINTS)) {
|
||||
return new AntPathRequestMatcher(this.endpointPathResolver.resolvePath(ALL_ENDPOINTS), null);
|
||||
}
|
||||
return getEndpointsRequestMatcher(pathList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link RequestMatcher} that matches on all endpoint paths for the given
|
||||
* classes with the {@link Endpoint} annotation.
|
||||
* @param endpoints the endpoint classes
|
||||
* @return the request matcher
|
||||
*/
|
||||
public RequestMatcher endpoints(Class<?>... endpoints) {
|
||||
Assert.notEmpty(endpoints, "At least one endpoint must be specified.");
|
||||
List<String> paths = Arrays.stream(endpoints).map(e -> {
|
||||
if (e.isAnnotationPresent(Endpoint.class)) {
|
||||
return e.getAnnotation(Endpoint.class).id();
|
||||
}
|
||||
throw new IllegalArgumentException("Only classes annotated with @Endpoint are supported.");
|
||||
}).collect(Collectors.toList());
|
||||
return getEndpointsRequestMatcher(paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link RequestMatcher} that matches on all static resources.
|
||||
* @return the request matcher
|
||||
*/
|
||||
public RequestMatcher staticResources() {
|
||||
return getRequestMatcher(STATIC_RESOURCES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link RequestMatcher} that matches on the {@link ErrorController} path,
|
||||
* if present.
|
||||
* @return the request matcher
|
||||
*/
|
||||
public RequestMatcher error() {
|
||||
if (this.errorController == null) {
|
||||
throw new IllegalStateException("Path for error controller could not be determined.");
|
||||
}
|
||||
String path = normalizePath(this.errorController.getErrorPath());
|
||||
return new AntPathRequestMatcher(path + "/**", null);
|
||||
}
|
||||
|
||||
private RequestMatcher getEndpointsRequestMatcher(List<String> ids) {
|
||||
List<RequestMatcher> matchers = new ArrayList<>();
|
||||
for (String id : ids) {
|
||||
String path = this.endpointPathResolver.resolvePath(id);
|
||||
matchers.add(new AntPathRequestMatcher(path + "/**", null));
|
||||
}
|
||||
return new OrRequestMatcher(matchers);
|
||||
}
|
||||
|
||||
private static RequestMatcher getRequestMatcher(String... paths) {
|
||||
List<RequestMatcher> matchers = new ArrayList<>();
|
||||
for (String path : paths) {
|
||||
matchers.add(new AntPathRequestMatcher(path, null));
|
||||
}
|
||||
return new OrRequestMatcher(matchers);
|
||||
}
|
||||
|
||||
private String normalizePath(String errorPath) {
|
||||
String result = StringUtils.cleanPath(errorPath);
|
||||
if (!result.startsWith("/")) {
|
||||
result = "/" + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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.autoconfigure.security;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorController;
|
||||
import org.springframework.boot.endpoint.Endpoint;
|
||||
import org.springframework.boot.endpoint.EndpointPathResolver;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
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 static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link SpringBootSecurity}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class SpringBootSecurityTests {
|
||||
|
||||
private SpringBootSecurity bootSecurity;
|
||||
|
||||
private EndpointPathResolver endpointPathResolver = new TestEndpointPathResolver();
|
||||
|
||||
private ErrorController errorController = new TestErrorController();
|
||||
|
||||
private MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
||||
private static String[] STATIC_RESOURCES = new String[]{"/css/**", "/js/**",
|
||||
"/images/**", "/webjars/**", "/**/favicon.ico"};
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
this.bootSecurity = new SpringBootSecurity(this.endpointPathResolver, this.errorController);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endpointIdsShouldThrowIfNoEndpointPaths() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("At least one endpoint id must be specified.");
|
||||
this.bootSecurity.endpointIds();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endpointIdsShouldReturnRequestMatcherWithEndpointPaths() throws Exception {
|
||||
RequestMatcher requestMatcher = this.bootSecurity.endpointIds("id-1", "id-2");
|
||||
assertThat(requestMatcher).isInstanceOf(OrRequestMatcher.class);
|
||||
this.request.setServletPath("/test/id-1");
|
||||
assertThat(requestMatcher.matches(this.request)).isTrue();
|
||||
this.request.setServletPath("/test/id-2");
|
||||
assertThat(requestMatcher.matches(this.request)).isTrue();
|
||||
this.request.setServletPath("/test/other-id");
|
||||
assertThat(requestMatcher.matches(this.request)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endpointIdsShouldReturnRequestMatcherWithAllEndpointPaths() throws Exception {
|
||||
RequestMatcher requestMatcher = this.bootSecurity.endpointIds(SpringBootSecurity.ALL_ENDPOINTS);
|
||||
this.request.setServletPath("/test/id-1");
|
||||
assertThat(requestMatcher.matches(this.request)).isTrue();
|
||||
this.request.setServletPath("/test/id-2");
|
||||
assertThat(requestMatcher.matches(this.request)).isTrue();
|
||||
this.request.setServletPath("/test/other-id");
|
||||
assertThat(requestMatcher.matches(this.request)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endpointsShouldReturnRequestMatcherWithEndpointPaths() throws Exception {
|
||||
RequestMatcher requestMatcher = this.bootSecurity.endpoints(TestEndpoint1.class);
|
||||
assertThat(requestMatcher).isInstanceOf(OrRequestMatcher.class);
|
||||
this.request.setServletPath("/test/id-1");
|
||||
assertThat(requestMatcher.matches(this.request)).isTrue();
|
||||
this.request.setServletPath("/test/id-2");
|
||||
assertThat(requestMatcher.matches(this.request)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endpointsShouldThrowIfNoEndpointPaths() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("At least one endpoint must be specified.");
|
||||
this.bootSecurity.endpoints();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endpointsShouldThrowExceptionWhenClassNotEndpoint() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Only classes annotated with @Endpoint are supported.");
|
||||
this.bootSecurity.endpoints(FakeEndpoint.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void staticResourcesShouldReturnRequestMatcherWithStaticResources() throws Exception {
|
||||
RequestMatcher requestMatcher = this.bootSecurity.staticResources();
|
||||
assertThat(requestMatcher).isInstanceOf(OrRequestMatcher.class);
|
||||
for (String resource : STATIC_RESOURCES) {
|
||||
this.request.setServletPath(resource);
|
||||
assertThat(requestMatcher.matches(this.request)).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorShouldReturnRequestMatcherWithErrorControllerPath() throws Exception {
|
||||
RequestMatcher requestMatcher = this.bootSecurity.error();
|
||||
assertThat(requestMatcher).isInstanceOf(AntPathRequestMatcher.class);
|
||||
this.request.setServletPath("/test/error");
|
||||
assertThat(requestMatcher.matches(this.request)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorShouldThrowExceptionWhenNoErrorController() throws Exception {
|
||||
this.thrown.expect(IllegalStateException.class);
|
||||
this.thrown.expectMessage("Path for error controller could not be determined.");
|
||||
this.bootSecurity = new SpringBootSecurity(this.endpointPathResolver, null);
|
||||
this.bootSecurity.error();
|
||||
}
|
||||
|
||||
static class TestEndpointPathResolver implements EndpointPathResolver {
|
||||
|
||||
@Override
|
||||
public String resolvePath(String endpointId) {
|
||||
return "/test/" + endpointId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class TestErrorController implements ErrorController {
|
||||
|
||||
@Override
|
||||
public String getErrorPath() {
|
||||
return "/test/error";
|
||||
}
|
||||
}
|
||||
|
||||
@Endpoint(id = "id-1")
|
||||
static class TestEndpoint1 {
|
||||
|
||||
}
|
||||
|
||||
@Endpoint(id = "id-2")
|
||||
static class TestEndpoint2 {
|
||||
|
||||
}
|
||||
|
||||
static class FakeEndpoint {
|
||||
|
||||
}
|
||||
}
|
@ -1,343 +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.autoconfigure.security;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
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.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
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.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
|
||||
/**
|
||||
* Tests for {@link SpringBootWebSecurityConfiguration}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Rob Winch
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class SpringBootWebSecurityConfigurationTests {
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebConfigurationOverrideGlobalAuthentication() throws Exception {
|
||||
this.context = SpringApplication.run(TestWebConfiguration.class,
|
||||
"--server.port=0");
|
||||
assertThat(this.context.getBean(AuthenticationManagerBuilder.class)).isNotNull();
|
||||
assertThat(this.context.getBean(AuthenticationManager.class)
|
||||
.authenticate(new UsernamePasswordAuthenticationToken("dave", "secret")))
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebConfigurationFilterChainUnauthenticated() throws Exception {
|
||||
this.context = SpringApplication.run(VanillaWebConfiguration.class,
|
||||
"--server.port=0");
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.webAppContextSetup((WebApplicationContext) this.context)
|
||||
.addFilters(
|
||||
this.context.getBean("springSecurityFilterChain", Filter.class))
|
||||
.build();
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/"))
|
||||
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
|
||||
.andExpect(MockMvcResultMatchers.header().string("www-authenticate",
|
||||
Matchers.containsString("realm=\"Spring\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebConfigurationFilterChainUnauthenticatedWithAuthorizeModeNone()
|
||||
throws Exception {
|
||||
this.context = SpringApplication.run(VanillaWebConfiguration.class,
|
||||
"--server.port=0", "--security.basic.authorize-mode=none");
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.webAppContextSetup((WebApplicationContext) this.context)
|
||||
.addFilters(
|
||||
this.context.getBean("springSecurityFilterChain", Filter.class))
|
||||
.build();
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/"))
|
||||
.andExpect(MockMvcResultMatchers.status().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebConfigurationFilterChainUnauthenticatedWithAuthorizeModeAuthenticated()
|
||||
throws Exception {
|
||||
this.context = SpringApplication.run(VanillaWebConfiguration.class,
|
||||
"--server.port=0", "--security.basic.authorize-mode=authenticated");
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.webAppContextSetup((WebApplicationContext) this.context)
|
||||
.addFilters(
|
||||
this.context.getBean("springSecurityFilterChain", Filter.class))
|
||||
.build();
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/"))
|
||||
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
|
||||
.andExpect(MockMvcResultMatchers.header().string("www-authenticate",
|
||||
Matchers.containsString("realm=\"Spring\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebConfigurationFilterChainBadCredentials() throws Exception {
|
||||
this.context = SpringApplication.run(VanillaWebConfiguration.class,
|
||||
"--server.port=0");
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.webAppContextSetup((WebApplicationContext) this.context)
|
||||
.addFilters(
|
||||
this.context.getBean("springSecurityFilterChain", Filter.class))
|
||||
.build();
|
||||
mockMvc.perform(
|
||||
MockMvcRequestBuilders.get("/").header("authorization", "Basic xxx"))
|
||||
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
|
||||
.andExpect(MockMvcResultMatchers.header().string("www-authenticate",
|
||||
Matchers.containsString("realm=\"Spring\"")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebConfigurationInjectGlobalAuthentication() throws Exception {
|
||||
this.context = SpringApplication.run(TestInjectWebConfiguration.class,
|
||||
"--server.port=0");
|
||||
assertThat(this.context.getBean(AuthenticationManagerBuilder.class)).isNotNull();
|
||||
assertThat(this.context.getBean(AuthenticationManager.class)
|
||||
.authenticate(new UsernamePasswordAuthenticationToken("dave", "secret")))
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
// gh-3447
|
||||
@Test
|
||||
public void testHiddenHttpMethodFilterOrderedFirst() throws Exception {
|
||||
this.context = SpringApplication.run(DenyPostRequestConfig.class,
|
||||
"--server.port=0");
|
||||
int port = Integer
|
||||
.parseInt(this.context.getEnvironment().getProperty("local.server.port"));
|
||||
TestRestTemplate rest = new TestRestTemplate();
|
||||
|
||||
// not overriding causes forbidden
|
||||
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
|
||||
|
||||
ResponseEntity<Object> result = rest
|
||||
.postForEntity("http://localhost:" + port + "/", form, Object.class);
|
||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
||||
|
||||
// override method with GET
|
||||
form = new LinkedMultiValueMap<>();
|
||||
form.add("_method", "GET");
|
||||
|
||||
result = rest.postForEntity("http://localhost:" + port + "/", form, Object.class);
|
||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultHeaderConfiguration() throws Exception {
|
||||
this.context = SpringApplication.run(VanillaWebConfiguration.class,
|
||||
"--server.port=0");
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.webAppContextSetup((WebApplicationContext) this.context)
|
||||
.addFilters((FilterChainProxy) this.context
|
||||
.getBean("springSecurityFilterChain", Filter.class))
|
||||
.build();
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/"))
|
||||
.andExpect(MockMvcResultMatchers.header().string("X-Content-Type-Options",
|
||||
is(notNullValue())))
|
||||
.andExpect(MockMvcResultMatchers.header().string("X-XSS-Protection",
|
||||
is(notNullValue())))
|
||||
.andExpect(MockMvcResultMatchers.header().string("Cache-Control",
|
||||
is(notNullValue())))
|
||||
.andExpect(MockMvcResultMatchers.header().string("X-Frame-Options",
|
||||
is(notNullValue())))
|
||||
.andExpect(MockMvcResultMatchers.header()
|
||||
.doesNotExist("Content-Security-Policy"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void securityHeadersCanBeDisabled() throws Exception {
|
||||
this.context = SpringApplication.run(VanillaWebConfiguration.class,
|
||||
"--server.port=0", "--security.headers.content-type=false",
|
||||
"--security.headers.xss=false", "--security.headers.cache=false",
|
||||
"--security.headers.frame=false");
|
||||
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.webAppContextSetup((WebApplicationContext) this.context)
|
||||
.addFilters(
|
||||
this.context.getBean("springSecurityFilterChain", Filter.class))
|
||||
.build();
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/"))
|
||||
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
|
||||
.andExpect(MockMvcResultMatchers.header()
|
||||
.doesNotExist("X-Content-Type-Options"))
|
||||
.andExpect(
|
||||
MockMvcResultMatchers.header().doesNotExist("X-XSS-Protection"))
|
||||
.andExpect(MockMvcResultMatchers.header().doesNotExist("Cache-Control"))
|
||||
.andExpect(
|
||||
MockMvcResultMatchers.header().doesNotExist("X-Frame-Options"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentSecurityPolicyConfiguration() throws Exception {
|
||||
this.context = SpringApplication.run(VanillaWebConfiguration.class,
|
||||
"--security.headers.content-security-policy=default-src 'self';",
|
||||
"--server.port=0");
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.webAppContextSetup((WebApplicationContext) this.context)
|
||||
.addFilters((FilterChainProxy) this.context
|
||||
.getBean("springSecurityFilterChain", Filter.class))
|
||||
.build();
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/"))
|
||||
.andExpect(MockMvcResultMatchers.header()
|
||||
.string("Content-Security-Policy", is("default-src 'self';")))
|
||||
.andExpect(MockMvcResultMatchers.header()
|
||||
.doesNotExist("Content-Security-Policy-Report-Only"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentSecurityPolicyReportOnlyConfiguration() throws Exception {
|
||||
this.context = SpringApplication.run(VanillaWebConfiguration.class,
|
||||
"--security.headers.content-security-policy=default-src 'self';",
|
||||
"--security.headers.content-security-policy-mode=report-only",
|
||||
"--server.port=0");
|
||||
MockMvc mockMvc = MockMvcBuilders
|
||||
.webAppContextSetup((WebApplicationContext) this.context)
|
||||
.addFilters((FilterChainProxy) this.context
|
||||
.getBean("springSecurityFilterChain", Filter.class))
|
||||
.build();
|
||||
mockMvc.perform(MockMvcRequestBuilders.get("/"))
|
||||
.andExpect(MockMvcResultMatchers.header().string(
|
||||
"Content-Security-Policy-Report-Only", is("default-src 'self';")))
|
||||
.andExpect(MockMvcResultMatchers.header()
|
||||
.doesNotExist("Content-Security-Policy"));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(TestWebConfiguration.class)
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
protected static class TestInjectWebConfiguration
|
||||
extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private final AuthenticationManagerBuilder auth;
|
||||
|
||||
// It's a bad idea to inject an AuthenticationManager into a
|
||||
// WebSecurityConfigurerAdapter because it can cascade early instantiation,
|
||||
// unless you explicitly want the Boot default AuthenticationManager. It's
|
||||
// better to inject the builder, if you want the global AuthenticationManager. It
|
||||
// might even be necessary to wrap the builder in a lazy AuthenticationManager
|
||||
// (that calls getOrBuild() only when the AuthenticationManager is actually
|
||||
// called).
|
||||
protected TestInjectWebConfiguration(AuthenticationManagerBuilder auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(WebSecurity web) throws Exception {
|
||||
this.auth.getOrBuild();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@MinimalWebConfiguration
|
||||
@Import(SecurityAutoConfiguration.class)
|
||||
protected static class VanillaWebConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@MinimalWebConfiguration
|
||||
@Import(SecurityAutoConfiguration.class)
|
||||
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
|
||||
protected static class TestWebConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Autowired
|
||||
public void init(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.inMemoryAuthentication().withUser("dave").password("secret")
|
||||
.roles("USER");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests().anyRequest().denyAll();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import({ ServletWebServerFactoryAutoConfiguration.class,
|
||||
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class })
|
||||
protected @interface MinimalWebConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@MinimalWebConfiguration
|
||||
@Import(SecurityAutoConfiguration.class)
|
||||
protected static class DenyPostRequestConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests().antMatchers(HttpMethod.POST, "/**").denyAll();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<!-- Your own application should inherit from spring-boot-starter-parent -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-samples</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-boot-sample-actuator-custom-security</artifactId>
|
||||
<name>Spring Boot Actuator Custom Security Sample</name>
|
||||
<description>Spring Boot Actuator Custom Security Sample</description>
|
||||
<url>http://projects.spring.io/spring-boot/</url>
|
||||
<organization>
|
||||
<name>Pivotal Software, Inc.</name>
|
||||
<url>http://www.spring.io</url>
|
||||
</organization>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/../..</main.basedir>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<!-- Compile -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-freemarker</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jolokia</groupId>
|
||||
<artifactId>jolokia-core</artifactId>
|
||||
</dependency>
|
||||
<!-- Runtime -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -0,0 +1,26 @@
|
||||
package sample.actuator.customsecurity;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
public class ExampleController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String home(Map<String, Object> model) {
|
||||
model.put("message", "Hello World");
|
||||
model.put("title", "Hello Home");
|
||||
model.put("date", new Date());
|
||||
return "home";
|
||||
}
|
||||
|
||||
@RequestMapping("/foo")
|
||||
public String foo() {
|
||||
throw new RuntimeException("Expected exception in controller");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 sample.actuator.customsecurity;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SampleActuatorCustomSecurityApplication {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
SpringApplication.run(SampleActuatorCustomSecurityApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package sample.actuator.customsecurity;
|
||||
|
||||
import org.springframework.boot.autoconfigure.security.SpringBootSecurity;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
|
||||
@Configuration
|
||||
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private SpringBootSecurity bootSecurity;
|
||||
|
||||
public SecurityConfiguration(SpringBootSecurity bootSecurity) {
|
||||
this.bootSecurity = bootSecurity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.inMemoryAuthentication()
|
||||
.withUser("user").password("password").authorities("ROLE_USER").and()
|
||||
.withUser("admin").password("admin").authorities("ROLE_ACTUATOR", "ROLE_USER");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests()
|
||||
.requestMatchers(this.bootSecurity.endpointIds("status", "info")).permitAll()
|
||||
.requestMatchers(this.bootSecurity.endpointIds(SpringBootSecurity.ALL_ENDPOINTS)).hasRole("ACTUATOR")
|
||||
.requestMatchers(this.bootSecurity.staticResources()).permitAll()
|
||||
.antMatchers("/foo").permitAll()
|
||||
.antMatchers("/**").hasRole("USER")
|
||||
.and()
|
||||
.cors()
|
||||
.and()
|
||||
.httpBasic();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
endpoints.all.web.enabled=true
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,32 @@
|
||||
<#import "/spring.ftl" as spring />
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Error</title>
|
||||
<#assign home><@spring.url relativeUrl="/"/></#assign>
|
||||
<#assign bootstrap><@spring.url relativeUrl="/css/bootstrap.min.css"/></#assign>
|
||||
<link rel="stylesheet" href="${bootstrap}" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="navbar">
|
||||
<div class="navbar-inner">
|
||||
<a class="brand" href="http://freemarker.org/"> FreeMarker -
|
||||
Plain </a>
|
||||
<ul class="nav">
|
||||
<li><a href="${home}"> Home </a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<h1>Error Page</h1>
|
||||
<div id="created">${timestamp?datetime}</div>
|
||||
<div>
|
||||
There was an unexpected error (type=${error}, status=${status}).
|
||||
</div>
|
||||
<div>${message}</div>
|
||||
<div>
|
||||
Please contact the operator with the above information.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,26 @@
|
||||
<#import "/spring.ftl" as spring />
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>${title}</title>
|
||||
<#assign home><@spring.url relativeUrl="/"/></#assign>
|
||||
<#assign bootstrap><@spring.url relativeUrl="/css/bootstrap.min.css"/></#assign>
|
||||
<link rel="stylesheet" href="${bootstrap}" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="navbar">
|
||||
<div class="navbar-inner">
|
||||
<a class="brand" href="http://freemarker.org/"> FreeMarker -
|
||||
Plain </a>
|
||||
<ul class="nav">
|
||||
<li><a href="${home}"> Home </a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<h1>${title}</h1>
|
||||
<div>${message}</div>
|
||||
<div id="created">${date?datetime}</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
42
spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/InsecureManagementPortAndPathSampleActuatorApplicationTests.java → spring-boot-samples/spring-boot-sample-actuator-custom-security/src/test/java/sample/actuator/customsecurity/InsecureManagementPortAndPathSampleActuatorApplicationTests.java
42
spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/InsecureManagementPortAndPathSampleActuatorApplicationTests.java → spring-boot-samples/spring-boot-sample-actuator-custom-security/src/test/java/sample/actuator/customsecurity/InsecureManagementPortAndPathSampleActuatorApplicationTests.java
@ -0,0 +1,75 @@
|
||||
package sample.actuator.customsecurity;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@DirtiesContext
|
||||
public class SampleActuatorCustomSecurityApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@Test
|
||||
public void homeIsSecure() throws Exception {
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/", Map.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> body = entity.getBody();
|
||||
assertThat(body.get("error")).isEqualTo("Unauthorized");
|
||||
assertThat(entity.getHeaders()).doesNotContainKey("Set-Cookie");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsecureApplicationPath() throws Exception {
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/foo", Map.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> body = entity.getBody();
|
||||
assertThat((String)body.get("message")).contains("Expected exception in controller");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsecureStaticResources() throws Exception {
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<String> entity = this.restTemplate.getForEntity("/css/bootstrap.min.css", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(entity.getBody()).contains("body");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void insecureActuator() throws Exception {
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<String> entity = this.restTemplate.getForEntity("/application/status",
|
||||
String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(entity.getBody()).contains("\"status\":\"UP\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void secureActuator() throws Exception {
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/application/env",
|
||||
Map.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +1,2 @@
|
||||
endpoints.shutdown.enabled=true
|
||||
|
||||
management.security.enabled=false
|
||||
endpoints.all.web.enabled=true
|
@ -1,4 +1,2 @@
|
||||
health.diskspace.enabled=false
|
||||
|
||||
# empty so home page is unsecured
|
||||
security.basic.path=
|
||||
endpoints.all.web.enabled=true
|
@ -1,80 +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 sample.actuator;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for insecured service endpoints (even with Spring Security on
|
||||
* classpath).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
|
||||
"management.security.enabled:false" })
|
||||
@DirtiesContext
|
||||
@ActiveProfiles("unsecure-management")
|
||||
public class InsecureManagementSampleActuatorApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@Test
|
||||
public void testHomeIsSecure() throws Exception {
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/", Map.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> body = entity.getBody();
|
||||
assertThat(body.get("error")).isEqualTo("Unauthorized");
|
||||
assertThat(entity.getHeaders()).doesNotContainKey("Set-Cookie");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMetrics() throws Exception {
|
||||
try {
|
||||
testHomeIsSecure(); // makes sure some requests have been made
|
||||
}
|
||||
catch (AssertionError ex) {
|
||||
// ignore;
|
||||
}
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/application/metrics",
|
||||
Map.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> body = entity.getBody();
|
||||
assertThat(body).containsKey("counter.status.401.unmapped");
|
||||
}
|
||||
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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 sample.actuator;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for insecured service endpoints (even with Spring Security on
|
||||
* classpath).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
|
||||
"security.basic.enabled:false" })
|
||||
@DirtiesContext
|
||||
public class InsecureSampleActuatorApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@Test
|
||||
public void testHome() throws Exception {
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/", Map.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> body = entity.getBody();
|
||||
assertThat(body.get("message")).isEqualTo("Hello Phil");
|
||||
assertThat(entity.getHeaders()).doesNotContainKey("Set-Cookie");
|
||||
}
|
||||
|
||||
}
|
@ -1,70 +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 sample.actuator;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for insecured service endpoints (even with Spring Security on
|
||||
* classpath).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
|
||||
"security.basic.enabled:false", "server.servlet.path:/spring" })
|
||||
@DirtiesContext
|
||||
public class ServletPathInsecureSampleActuatorApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@Test
|
||||
public void testHome() throws Exception {
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/spring/",
|
||||
Map.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> body = entity.getBody();
|
||||
assertThat(body.get("message")).isEqualTo("Hello Phil");
|
||||
assertThat(entity.getHeaders()).doesNotContainKey("Set-Cookie");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMetricsIsSecure() throws Exception {
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<Map> entity = this.restTemplate
|
||||
.getForEntity("/spring/application/metrics", Map.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +1,2 @@
|
||||
server.error.path: /oops
|
||||
management.context-path: /admin
|
||||
endpoints.health.sensitive: false
|
||||
management.context-path: /admin
|
@ -0,0 +1,29 @@
|
||||
package sample.secure.oauth2.actuator;
|
||||
|
||||
import org.springframework.boot.autoconfigure.security.SpringBootSecurity;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
|
||||
/**
|
||||
* Basic auth security for actuator endpoints.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@Configuration
|
||||
public class ActuatorSecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
private final SpringBootSecurity springBootSecurity;
|
||||
|
||||
public ActuatorSecurityConfiguration(SpringBootSecurity springBootSecurity) {
|
||||
this.springBootSecurity = springBootSecurity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.requestMatcher(this.springBootSecurity.endpointIds(SpringBootSecurity.ALL_ENDPOINTS))
|
||||
.authorizeRequests().antMatchers("/**").authenticated()
|
||||
.and()
|
||||
.httpBasic();
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
server.port=8081
|
||||
security.basic.enabled=true
|
||||
security.user.password=password
|
||||
endpoints.all.web.enabled=true
|
||||
security.oauth2.resource.id=service
|
||||
security.oauth2.resource.userInfoUri=http://localhost:8080/user
|
||||
logging.level.org.springframework.security=DEBUG
|
||||
|
@ -0,0 +1,17 @@
|
||||
package sample.secure.oauth2;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
|
||||
|
||||
/**
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@Configuration
|
||||
public class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void init(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.inMemoryAuthentication().withUser("greg").password("turnquist").roles("read");
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
security.user.password=password
|
@ -1,2 +1,3 @@
|
||||
spring.thymeleaf.cache: false
|
||||
logging.level.org.springframework.security: INFO
|
||||
logging.level.org.springframework.security: INFO
|
||||
endpoints.all.web.enabled=true
|
@ -1,6 +1,5 @@
|
||||
spring.thymeleaf.cache: false
|
||||
security.basic.enabled: false
|
||||
# demo only:
|
||||
security.user.password: password
|
||||
logging.level.org.springframework.security: INFO
|
||||
logging.level.org.springframework.boot.actuate.audit.listener.AuditListener: DEBUG
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.endpoint;
|
||||
|
||||
/**
|
||||
* {@link EndpointPathResolver} implementation that does not support
|
||||
* resolving endpoint paths.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class DefaultEndpointPathResolver implements EndpointPathResolver {
|
||||
|
||||
@Override
|
||||
public String resolvePath(String endpointId) {
|
||||
throw new UnsupportedOperationException("Not supported: Endpoints not available");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.endpoint;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultEndpointPathResolver}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class DefaultEndpointPathResolverTests {
|
||||
|
||||
private DefaultEndpointPathResolver resolver = new DefaultEndpointPathResolver();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void resolveShouldThrowException() throws Exception {
|
||||
this.thrown.expect(UnsupportedOperationException.class);
|
||||
this.resolver.resolvePath("my-id");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue