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
pull/10101/head
Madhura Bhave 7 years ago
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;
}
}

@ -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")
.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);
"endpoints.all.web.enabled=true")
.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());
}
}
}

@ -26,7 +26,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
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.security.SecurityAuthorizeMode;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -79,7 +78,6 @@ public class H2ConsoleAutoConfiguration {
@Configuration
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnProperty(prefix = "security.basic", name = "enabled", matchIfMissing = true)
static class H2ConsoleSecurityConfiguration {
@ -95,9 +93,6 @@ public class H2ConsoleAutoConfiguration {
@Autowired
private H2ConsoleProperties console;
@Autowired
private SecurityProperties security;
@Override
public void configure(HttpSecurity http) throws Exception {
String path = this.console.getPath();
@ -106,15 +101,8 @@ public class H2ConsoleAutoConfiguration {
h2Console.csrf().disable();
h2Console.httpBasic();
h2Console.headers().frameOptions().sameOrigin();
String[] roles = this.security.getUser().getRole().toArray(new String[0]);
SecurityAuthorizeMode mode = this.security.getBasic().getAuthorizeMode();
if (mode == null || mode == SecurityAuthorizeMode.ROLE) {
http.authorizeRequests().anyRequest().hasAnyRole(roles);
}
else if (mode == SecurityAuthorizeMode.AUTHENTICATED) {
http.authorizeRequests().anyRequest().authenticated();
}
}
}

@ -17,9 +17,8 @@
package org.springframework.boot.autoconfigure.security;
import java.lang.reflect.Field;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -29,7 +28,6 @@ import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.SecurityProperties.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
@ -47,13 +45,13 @@ import org.springframework.security.config.annotation.authentication.builders.Au
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.ReflectionUtils;
/**
* Configuration for a Spring Security in-memory {@link AuthenticationManager}. Can be
* disabled by providing a bean of type AuthenticationManager, or by autowiring an
* {@link AuthenticationManagerBuilder} into a method in one of your configuration
* classes. The value provided by this configuration will become the "global"
* disabled by providing a bean of type {@link AuthenticationManager}, {@link AuthenticationProvider}
* or {@link UserDetailsService}. The value provided by this configuration will become the "global"
* authentication manager (from Spring Security), or the parent of the global instance.
* Thus it acts as a fallback when no others are provided, is used by method security if
* enabled, and as a parent authentication manager for "local" authentication managers in
@ -61,10 +59,12 @@ import org.springframework.util.ReflectionUtils;
*
* @author Dave Syer
* @author Rob Winch
* @author Madhura Bhave
*/
@Configuration
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean({ AuthenticationManager.class })
@ConditionalOnMissingBean({ AuthenticationManager.class,
AuthenticationProvider.class, UserDetailsService.class})
@Order(0)
public class AuthenticationManagerConfiguration {
@ -102,7 +102,7 @@ public class AuthenticationManagerConfiguration {
* {@link GlobalAuthenticationConfigurerAdapter#init(AuthenticationManagerBuilder)}
* exists that adds a {@link SecurityConfigurer} to the
* {@link AuthenticationManagerBuilder}.</li>
* <li>{@link AuthenticationManagerConfiguration#init(AuthenticationManagerBuilder)}
* <li>{@link AuthenticationManagerConfiguration}
* adds {@link SpringBootAuthenticationConfigurerAdapter} so it is after the
* {@link SecurityConfigurer} in the first step.</li>
* <li>We then can default an {@link AuthenticationProvider} if necessary. Note we can
@ -168,14 +168,11 @@ public class AuthenticationManagerConfiguration {
if (auth.isConfigured()) {
return;
}
User user = this.securityProperties.getUser();
if (user.isDefaultPassword()) {
String password = UUID.randomUUID().toString();
logger.info(String.format("%n%nUsing default security password: %s%n",
user.getPassword()));
}
Set<String> roles = new LinkedHashSet<>(user.getRole());
withUser(user.getName()).password(user.getPassword())
.roles(roles.toArray(new String[roles.size()]));
password));
withUser("user").password(password)
.roles();
setField(auth, "defaultUserDetailsService", getUserDetailsService());
super.configure(auth);
}

@ -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">
* &#064;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);
}
}
}
}

@ -16,10 +16,14 @@
package org.springframework.boot.autoconfigure.security;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorController;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.endpoint.DefaultEndpointPathResolver;
import org.springframework.boot.endpoint.EndpointPathResolver;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -49,10 +53,23 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
GlobalAuthenticationConfigurerAdapter.class })
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class,
WebSecurityEnablerConfiguration.class,
AuthenticationManagerConfiguration.class,
BootGlobalAuthenticationConfiguration.class, SecurityDataConfiguration.class })
SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public EndpointPathResolver endpointPathResolver() {
return new DefaultEndpointPathResolver();
}
@Bean
public SpringBootSecurity springBootSecurity(EndpointPathResolver endpointPathResolver,
ObjectProvider<ErrorController> errorController) {
return new SpringBootSecurity(endpointPathResolver, errorController.getIfAvailable());
}
@Bean
@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
public DefaultAuthenticationEventPublisher authenticationEventPublisher(

@ -16,20 +16,14 @@
package org.springframework.boot.autoconfigure.security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.DispatcherType;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.core.Ordered;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.util.StringUtils;
/**
* Properties for the security aspects of an application.
@ -70,33 +64,8 @@ public class SecurityProperties implements SecurityPrerequisite {
public static final int DEFAULT_FILTER_ORDER = FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER
- 100;
/**
* Enable secure channel for all requests.
*/
private boolean requireSsl;
/**
* Enable Cross Site Request Forgery support.
*/
// Flip this when session creation is disabled by default
private boolean enableCsrf = false;
private Basic basic = new Basic();
private final Headers headers = new Headers();
/**
* Session creation policy (always, never, if_required, stateless).
*/
private SessionCreationPolicy sessions = SessionCreationPolicy.STATELESS;
/**
* Comma-separated list of paths to exclude from the default secured paths.
*/
private List<String> ignored = new ArrayList<>();
private final User user = new User();
/**
* Security filter chain order.
*/
@ -108,22 +77,6 @@ public class SecurityProperties implements SecurityPrerequisite {
private Set<DispatcherType> filterDispatcherTypes = new HashSet<>(Arrays.asList(
DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));
public Headers getHeaders() {
return this.headers;
}
public User getUser() {
return this.user;
}
public SessionCreationPolicy getSessions() {
return this.sessions;
}
public void setSessions(SessionCreationPolicy sessions) {
this.sessions = sessions;
}
public Basic getBasic() {
return this.basic;
}
@ -132,30 +85,6 @@ public class SecurityProperties implements SecurityPrerequisite {
this.basic = basic;
}
public boolean isRequireSsl() {
return this.requireSsl;
}
public void setRequireSsl(boolean requireSsl) {
this.requireSsl = requireSsl;
}
public boolean isEnableCsrf() {
return this.enableCsrf;
}
public void setEnableCsrf(boolean enableCsrf) {
this.enableCsrf = enableCsrf;
}
public void setIgnored(List<String> ignored) {
this.ignored = new ArrayList<>(ignored);
}
public List<String> getIgnored() {
return this.ignored;
}
public int getFilterOrder() {
return this.filterOrder;
}
@ -172,122 +101,6 @@ public class SecurityProperties implements SecurityPrerequisite {
this.filterDispatcherTypes = filterDispatcherTypes;
}
public static class Headers {
public enum HSTS {
NONE, DOMAIN, ALL
}
public enum ContentSecurityPolicyMode {
/**
* Use the 'Content-Security-Policy' header.
*/
DEFAULT,
/**
* Use the 'Content-Security-Policy-Report-Only' header.
*/
REPORT_ONLY
}
/**
* Enable cross site scripting (XSS) protection.
*/
private boolean xss = true;
/**
* Enable cache control HTTP headers.
*/
private boolean cache = true;
/**
* Enable "X-Frame-Options" header.
*/
private boolean frame = true;
/**
* Enable "X-Content-Type-Options" header.
*/
private boolean contentType = true;
/**
* Value for content security policy header.
*/
private String contentSecurityPolicy;
/**
* Content security policy mode.
*/
private ContentSecurityPolicyMode contentSecurityPolicyMode = ContentSecurityPolicyMode.DEFAULT;
/**
* HTTP Strict Transport Security (HSTS) mode (none, domain, all).
*/
private HSTS hsts = HSTS.ALL;
public boolean isXss() {
return this.xss;
}
public void setXss(boolean xss) {
this.xss = xss;
}
public boolean isCache() {
return this.cache;
}
public void setCache(boolean cache) {
this.cache = cache;
}
public boolean isFrame() {
return this.frame;
}
public void setFrame(boolean frame) {
this.frame = frame;
}
public boolean isContentType() {
return this.contentType;
}
public void setContentType(boolean contentType) {
this.contentType = contentType;
}
public String getContentSecurityPolicy() {
return this.contentSecurityPolicy;
}
public void setContentSecurityPolicy(String contentSecurityPolicy) {
this.contentSecurityPolicy = contentSecurityPolicy;
}
public ContentSecurityPolicyMode getContentSecurityPolicyMode() {
return this.contentSecurityPolicyMode;
}
public void setContentSecurityPolicyMode(
ContentSecurityPolicyMode contentSecurityPolicyMode) {
this.contentSecurityPolicyMode = contentSecurityPolicyMode;
}
public HSTS getHsts() {
return this.hsts;
}
public void setHsts(HSTS hsts) {
this.hsts = hsts;
}
}
public static class Basic {
/**
@ -295,16 +108,6 @@ public class SecurityProperties implements SecurityPrerequisite {
*/
private boolean enabled = true;
/**
* HTTP basic realm name.
*/
private String realm = "Spring";
/**
* Comma-separated list of paths to secure.
*/
private String[] path = new String[] { "/**" };
/**
* Security authorize mode to apply.
*/
@ -318,84 +121,6 @@ public class SecurityProperties implements SecurityPrerequisite {
this.enabled = enabled;
}
public String getRealm() {
return this.realm;
}
public void setRealm(String realm) {
this.realm = realm;
}
public String[] getPath() {
return this.path;
}
public void setPath(String... paths) {
this.path = paths;
}
public SecurityAuthorizeMode getAuthorizeMode() {
return this.authorizeMode;
}
public void setAuthorizeMode(SecurityAuthorizeMode authorizeMode) {
this.authorizeMode = authorizeMode;
}
}
public static class User {
/**
* Default user name.
*/
private String name = "user";
/**
* Password for the default user name.
*/
private String password = UUID.randomUUID().toString();
/**
* Granted roles for the default user name.
*/
private List<String> role = new ArrayList<>(Collections.singletonList("USER"));
private boolean defaultPassword = true;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
if (password.startsWith("${") && password.endsWith("}")
|| !StringUtils.hasLength(password)) {
return;
}
this.defaultPassword = false;
this.password = password;
}
public List<String> getRole() {
return this.role;
}
public void setRole(List<String> role) {
this.role = new ArrayList<>(role);
}
public boolean isDefaultPassword() {
return this.defaultPassword;
}
}
}

@ -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;
}
}

@ -16,284 +16,41 @@
package org.springframework.boot.autoconfigure.security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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.security.SecurityProperties.Headers;
import org.springframework.boot.autoconfigure.security.SecurityProperties.Headers.ContentSecurityPolicyMode;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorController;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
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.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.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.header.writers.HstsHeaderWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Configuration for security of a web application or service. By default everything is
* secured with HTTP Basic authentication except the
* {@link SecurityProperties#getIgnored() explicitly ignored} paths (defaults to
* <code>&#47;css&#47;**, &#47;js&#47;**, &#47;images&#47;**, &#47;**&#47;favicon.ico</code>
* ). Many aspects of the behavior can be controller with {@link SecurityProperties} via
* externalized application properties (or via an bean definition of that type to set the
* defaults). The user details for authentication are just placeholders
* {@code (username=user, password=password)} but can easily be customized by providing a
* an {@link AuthenticationManager}. Also provides audit logging of authentication events.
* <p>
* Some common simple customizations:
* <ul>
* <li>Switch off security completely and permanently: remove Spring Security from the
* classpath or {@link EnableAutoConfiguration#exclude() exclude}
* {@link SecurityAutoConfiguration}.</li>
* <li>Switch off security temporarily (e.g. for a dev environment): set
* {@code security.basic.enabled=false}</li>
* <li>Customize the user details: autowire an {@link AuthenticationManagerBuilder} into a
* method in one of your configuration classes or equivalently add a bean of type
* AuthenticationManager</li>
* <li>Add form login for user facing resources: add a
* {@link WebSecurityConfigurerAdapter} and use {@link HttpSecurity#formLogin()}</li>
* </ul>
* The default configuration for web security. It relies on Spring Security's
* content-negotiation strategy to determine what sort of authentication to use.
* If the user specifies their own {@link WebSecurityConfigurerAdapter}, this will back-off
* completely and the users should specify all the bits that they want to configure as part
* of the custom security configuration.
*
* @author Dave Syer
* @author Andy Wilkinson
* @author Madhura Bhave
*/
@Configuration
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnClass({ EnableWebSecurity.class, AuthenticationEntryPoint.class })
@ConditionalOnMissingBean(WebSecurityConfiguration.class)
@ConditionalOnProperty(prefix = "security.basic", name = "enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnClass(EnableWebSecurity.class)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableWebSecurity
public class SpringBootWebSecurityConfiguration {
private static List<String> DEFAULT_IGNORED = Arrays.asList("/css/**", "/js/**",
"/images/**", "/webjars/**", "/**/favicon.ico");
@Bean
@ConditionalOnMissingBean({ IgnoredPathsWebSecurityConfigurerAdapter.class })
public IgnoredPathsWebSecurityConfigurerAdapter ignoredPathsWebSecurityConfigurerAdapter(
List<IgnoredRequestCustomizer> customizers) {
return new IgnoredPathsWebSecurityConfigurerAdapter(customizers);
}
@Bean
public IgnoredRequestCustomizer defaultIgnoredRequestsCustomizer(
ServerProperties server, SecurityProperties security,
ObjectProvider<ErrorController> errorController) {
return new DefaultIgnoredRequestCustomizer(server, security,
errorController.getIfAvailable());
}
public static void configureHeaders(HeadersConfigurer<?> configurer,
SecurityProperties.Headers headers) throws Exception {
if (headers.getHsts() != Headers.HSTS.NONE) {
boolean includeSubDomains = headers.getHsts() == Headers.HSTS.ALL;
HstsHeaderWriter writer = new HstsHeaderWriter(includeSubDomains);
writer.setRequestMatcher(AnyRequestMatcher.INSTANCE);
configurer.addHeaderWriter(writer);
}
if (!headers.isContentType()) {
configurer.contentTypeOptions().disable();
}
if (StringUtils.hasText(headers.getContentSecurityPolicy())) {
String policyDirectives = headers.getContentSecurityPolicy();
ContentSecurityPolicyMode mode = headers.getContentSecurityPolicyMode();
if (mode == ContentSecurityPolicyMode.DEFAULT) {
configurer.contentSecurityPolicy(policyDirectives);
}
else {
configurer.contentSecurityPolicy(policyDirectives).reportOnly();
}
}
if (!headers.isXss()) {
configurer.xssProtection().disable();
}
if (!headers.isCache()) {
configurer.cacheControl().disable();
}
if (!headers.isFrame()) {
configurer.frameOptions().disable();
}
}
// Get the ignored paths in early
@Order(SecurityProperties.IGNORED_ORDER)
private static class IgnoredPathsWebSecurityConfigurerAdapter
implements WebSecurityConfigurer<WebSecurity> {
private final List<IgnoredRequestCustomizer> customizers;
IgnoredPathsWebSecurityConfigurerAdapter(
List<IgnoredRequestCustomizer> customizers) {
this.customizers = customizers;
}
@Override
public void configure(WebSecurity builder) throws Exception {
}
@Override
public void init(WebSecurity builder) throws Exception {
for (IgnoredRequestCustomizer customizer : this.customizers) {
customizer.customize(builder.ignoring());
}
}
}
private class DefaultIgnoredRequestCustomizer implements IgnoredRequestCustomizer {
private final ServerProperties server;
private final SecurityProperties security;
private final ErrorController errorController;
DefaultIgnoredRequestCustomizer(ServerProperties server,
SecurityProperties security, ErrorController errorController) {
this.server = server;
this.security = security;
this.errorController = errorController;
}
@Override
public void customize(IgnoredRequestConfigurer configurer) {
List<String> ignored = getIgnored(this.security);
if (this.errorController != null) {
ignored.add(normalizePath(this.errorController.getErrorPath()));
}
String[] paths = this.server.getServlet().getPathsArray(ignored);
List<RequestMatcher> matchers = new ArrayList<>();
if (!ObjectUtils.isEmpty(paths)) {
for (String pattern : paths) {
matchers.add(new AntPathRequestMatcher(pattern, null));
}
}
if (!matchers.isEmpty()) {
configurer.requestMatchers(new OrRequestMatcher(matchers));
}
}
private List<String> getIgnored(SecurityProperties security) {
List<String> ignored = new ArrayList<>(security.getIgnored());
if (ignored.isEmpty()) {
ignored.addAll(DEFAULT_IGNORED);
}
else if (ignored.contains("none")) {
ignored.remove("none");
}
return ignored;
}
private String normalizePath(String errorPath) {
String result = StringUtils.cleanPath(errorPath);
if (!result.startsWith("/")) {
result = "/" + result;
}
return result;
}
}
@Configuration
@ConditionalOnProperty(prefix = "security.basic", name = "enabled", havingValue = "false")
@Order(SecurityProperties.BASIC_AUTH_ORDER)
protected static class ApplicationNoWebSecurityConfigurerAdapter
extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher((request) -> false);
}
}
@Configuration
@ConditionalOnProperty(prefix = "security.basic", name = "enabled", matchIfMissing = true)
@Order(SecurityProperties.BASIC_AUTH_ORDER)
protected static class ApplicationWebSecurityConfigurerAdapter
extends WebSecurityConfigurerAdapter {
private SecurityProperties security;
protected ApplicationWebSecurityConfigurerAdapter(SecurityProperties security) {
this.security = security;
}
static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
if (this.security.isRequireSsl()) {
http.requiresChannel().anyRequest().requiresSecure();
}
if (!this.security.isEnableCsrf()) {
super.configure(http);
http.csrf().disable();
}
// No cookies for application endpoints by default
http.sessionManagement().sessionCreationPolicy(this.security.getSessions());
SpringBootWebSecurityConfiguration.configureHeaders(http.headers(),
this.security.getHeaders());
String[] paths = getSecureApplicationPaths();
if (paths.length > 0) {
AuthenticationEntryPoint entryPoint = entryPoint();
http.exceptionHandling().authenticationEntryPoint(entryPoint);
http.httpBasic().authenticationEntryPoint(entryPoint);
http.requestMatchers().antMatchers(paths);
String[] roles = this.security.getUser().getRole().toArray(new String[0]);
SecurityAuthorizeMode mode = this.security.getBasic().getAuthorizeMode();
if (mode == null || mode == SecurityAuthorizeMode.ROLE) {
http.authorizeRequests().anyRequest().hasAnyRole(roles);
}
else if (mode == SecurityAuthorizeMode.AUTHENTICATED) {
http.authorizeRequests().anyRequest().authenticated();
}
}
}
private String[] getSecureApplicationPaths() {
List<String> list = new ArrayList<>();
for (String path : this.security.getBasic().getPath()) {
path = (path == null ? "" : path.trim());
if (path.equals("/**")) {
return new String[] { path };
}
if (!path.equals("")) {
list.add(path);
}
}
return list.toArray(new String[list.size()]);
}
private AuthenticationEntryPoint entryPoint() {
BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
entryPoint.setRealmName(this.security.getBasic().getRealm());
return entryPoint;
}
}
}

@ -16,33 +16,27 @@
package org.springframework.boot.autoconfigure.security;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
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.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* If the user explicitly disables the basic security features and forgets to
* {@code @EnableWebSecurity}, and yet still wants a bean of type
* WebSecurityConfigurerAdapter, he is trying to use a custom security setup. The app
* would fail in a confusing way without this shim configuration, which just helpfully
* defines an empty {@code @EnableWebSecurity}.
* If there is a bean of type WebSecurityConfigurerAdapter,
* this adds the {@code @EnableWebSecurity} annotation if it is not already specified.
* This will make sure that the annotation is present with default security autoconfiguration
* and also if the user adds custom security and forgets to add the annotation.
*
* @author Dave Syer
* @author Madhura Bhave
*/
@ConditionalOnProperty(prefix = "security.basic", name = "enabled", havingValue = "false")
@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnClass(EnableWebSecurity.class)
@ConditionalOnMissingBean(WebSecurityConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@AutoConfigureAfter(SecurityAutoConfiguration.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity
public class FallbackWebSecurityAutoConfiguration {
public class WebSecurityEnablerConfiguration {
}

@ -98,7 +98,6 @@ org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\

@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfigurationInteg
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
@ -60,26 +61,18 @@ public class H2ConsoleAutoConfigurationIntegrationTests {
public void noPrincipal() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(springSecurity()).build();
mockMvc.perform(get("/h2-console/")).andExpect(status().isUnauthorized());
mockMvc.perform(get("/h2-console/").accept(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized());
}
@Test
public void userPrincipal() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(springSecurity()).build();
mockMvc.perform(get("/h2-console/").with(user("test").roles("USER")))
mockMvc.perform(get("/h2-console/").accept(MediaType.APPLICATION_JSON).with(user("test").roles("USER")))
.andExpect(status().isOk())
.andExpect(header().string("X-Frame-Options", "SAMEORIGIN"));
}
@Test
public void someOtherPrincipal() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(springSecurity()).build();
mockMvc.perform(get("/h2-console/").with(user("test").roles("FOO")))
.andExpect(status().isForbidden());
}
@Configuration
@Import({ SecurityAutoConfiguration.class, H2ConsoleAutoConfiguration.class })
@Controller

@ -21,6 +21,7 @@ import java.util.EnumSet;
import javax.servlet.DispatcherType;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
@ -28,6 +29,7 @@ import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoCon
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.test.City;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
@ -50,7 +52,6 @@ import org.springframework.security.config.annotation.authentication.configurers
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;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension;
@ -72,6 +73,9 @@ public class SecurityAutoConfigurationTests {
private AnnotationConfigWebApplicationContext context;
@Rule
public OutputCapture outputCapture = new OutputCapture();
@After
public void close() {
if (this.context != null) {
@ -87,9 +91,8 @@ public class SecurityAutoConfigurationTests {
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertThat(this.context.getBean(AuthenticationManagerBuilder.class)).isNotNull();
// 1 for static resources and one for the rest
assertThat(this.context.getBean(FilterChainProxy.class).getFilterChains())
.hasSize(2);
.hasSize(1);
}
@Test
@ -157,7 +160,7 @@ public class SecurityAutoConfigurationTests {
}
@Test
public void testDisableBasicAuthOnApplicationPaths() throws Exception {
public void testDisableDefaultSecurity() throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
this.context.register(SecurityAutoConfiguration.class,
@ -166,7 +169,7 @@ public class SecurityAutoConfigurationTests {
this.context.refresh();
// Ignores and the "matches-none" filter only
assertThat(this.context.getBeanNamesForType(FilterChainProxy.class).length)
.isEqualTo(1);
.isEqualTo(0);
}
@Test
@ -295,33 +298,24 @@ public class SecurityAutoConfigurationTests {
this.context.setServletContext(new MockServletContext());
this.context.register(SecurityAutoConfiguration.class);
this.context.refresh();
SecurityProperties security = this.context.getBean(SecurityProperties.class);
String password = this.outputCapture.toString().split("Using default security password: ")[1].split("\n")[0];
AuthenticationManager manager = this.context.getBean(AuthenticationManager.class);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
security.getUser().getName(), security.getUser().getPassword());
"user", password);
assertThat(manager.authenticate(token)).isNotNull();
}
@Test
public void testCustomAuthenticationDoesNotAuthenticateWithBootSecurityUser()
public void testCustomAuthenticationDoesNotCreateDefaultUser()
throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
this.context.register(AuthenticationManagerCustomizer.class,
SecurityAutoConfiguration.class);
this.context.refresh();
SecurityProperties security = this.context.getBean(SecurityProperties.class);
AuthenticationManager manager = this.context.getBean(AuthenticationManager.class);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
security.getUser().getName(), security.getUser().getPassword());
try {
manager.authenticate(token);
fail("Expected Exception");
}
catch (AuthenticationException success) {
// Expected
}
token = new UsernamePasswordAuthenticationToken("foo", "bar");
assertThat(this.outputCapture.toString()).doesNotContain("Using default security password: ");
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("foo", "bar");
assertThat(manager.authenticate(token)).isNotNull();
}

@ -23,6 +23,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
@ -30,9 +31,9 @@ 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.SecurityAutoConfigurationTests.WebSecurity;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
@ -54,17 +55,19 @@ import org.springframework.web.bind.annotation.RestController;
*/
public class SecurityFilterAutoConfigurationEarlyInitializationTests {
// gh-4154
@Rule
public OutputCapture outputCapture = new OutputCapture();
@Test
public void testSecurityFilterDoesNotCauseEarlyInitialization() throws Exception {
try (AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext()) {
TestPropertyValues.of("server.port:0", "security.user.password:password")
TestPropertyValues.of("server.port:0")
.applyTo(context);
context.register(Config.class);
context.refresh();
int port = context.getWebServer().getPort();
new TestRestTemplate("user", "password")
String password = this.outputCapture.toString().split("Using default security password: ")[1].split("\n")[0];
new TestRestTemplate("user", password)
.getForEntity("http://localhost:" + port, Object.class);
// If early initialization occurred a ConverterNotFoundException is thrown
@ -76,7 +79,7 @@ public class SecurityFilterAutoConfigurationEarlyInitializationTests {
ConverterBean.class })
@ImportAutoConfiguration({ WebMvcAutoConfiguration.class,
JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebSecurity.class,
DispatcherServletAutoConfiguration.class,
SecurityAutoConfiguration.class, SecurityFilterAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
static class Config {

@ -17,8 +17,6 @@
package org.springframework.boot.autoconfigure.security;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Test;
@ -39,63 +37,9 @@ public class SecurityPropertiesTests {
private SecurityProperties security = new SecurityProperties();
@Test
public void testBindingIgnoredSingleValued() {
bind("security.ignored", "/css/**");
assertThat(this.security.getIgnored()).hasSize(1);
}
@Test
public void testBindingIgnoredEmpty() {
bind("security.ignored", "");
assertThat(this.security.getIgnored()).isEmpty();
}
@Test
public void testBindingIgnoredDisable() {
bind("security.ignored", "none");
assertThat(this.security.getIgnored()).hasSize(1);
}
@Test
public void testBindingIgnoredMultiValued() {
bind("security.ignored", "/css/**,/images/**");
assertThat(this.security.getIgnored()).hasSize(2);
}
@Test
public void testBindingIgnoredMultiValuedList() {
Map<String, String> map = new LinkedHashMap<>();
map.put("security.ignored[0]", "/css/**");
map.put("security.ignored[1]", "/foo/**");
MapConfigurationPropertySource source = new MapConfigurationPropertySource(map);
bind(source);
assertThat(this.security.getIgnored()).hasSize(2);
assertThat(this.security.getIgnored().contains("/foo/**")).isTrue();
}
@Test
public void testDefaultPasswordAutogeneratedIfUnresolvedPlaceholder() {
bind("security.user.password", "${ADMIN_PASSWORD}");
assertThat(this.security.getUser().isDefaultPassword()).isTrue();
}
@Test
public void testDefaultPasswordAutogeneratedIfEmpty() {
bind("security.user.password", "");
assertThat(this.security.getUser().isDefaultPassword()).isTrue();
}
@Test
public void testRoles() {
bind("security.user.role", "USER,ADMIN");
assertThat(this.security.getUser().getRole().toString())
.isEqualTo("[USER, ADMIN]");
}
@Test
public void testRole() {
bind("security.user.role", "ADMIN");
assertThat(this.security.getUser().getRole().toString()).isEqualTo("[ADMIN]");
public void testBinding() {
bind("security.basic.enabled", "false");
assertThat(this.security.getBasic().isEnabled()).isFalse();
}
private void bind(String name, String value) {

@ -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();
}
}
}

@ -40,7 +40,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import static org.hamcrest.Matchers.startsWith;
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.header;
@ -76,12 +75,6 @@ public class CustomOAuth2SsoConfigurationTests {
.addFilters(this.filter).build();
}
@Test
public void homePageIsBasicAuth() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isUnauthorized())
.andExpect(header().string("WWW-Authenticate", startsWith("Basic")));
}
@Test
public void uiPageIsSecure() throws Exception {
this.mvc.perform(get("/ui/")).andExpect(status().isFound())

@ -42,10 +42,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import static org.hamcrest.Matchers.startsWith;
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.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
@ -78,12 +76,6 @@ public class CustomOAuth2SsoWithAuthenticationEntryPointConfigurationTests {
.addFilters(this.filter).build();
}
@Test
public void homePageIsBasicAuth() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isUnauthorized())
.andExpect(header().string("WWW-Authenticate", startsWith("Basic")));
}
@Test
public void uiPageIsSecure() throws Exception {
this.mvc.perform(get("/ui/")).andExpect(status().isUnauthorized());

@ -16,17 +16,21 @@
package sample;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@SpringBootApplication
public class HelloWebSecurityApplication {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("password").roles("USER").build());
return manager;
}
public static void main(String[] args) {

@ -57,6 +57,7 @@ public class HelloWebSecurityApplicationTests {
@Test
public void requiresAuthentication() throws Exception {
this.request.addHeader("Accept", "application/json");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus())
.isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
@ -64,6 +65,7 @@ public class HelloWebSecurityApplicationTests {
@Test
public void userAuthenticates() throws Exception {
this.request.addHeader("Accept", "application/json");
this.request.addHeader("Authorization", "Basic " + new String(
Base64.getEncoder().encode("user:password".getBytes("UTF-8"))));

@ -27,6 +27,7 @@
<module>spring-boot-sample-actuator-log4j2</module>
<module>spring-boot-sample-actuator-noweb</module>
<module>spring-boot-sample-actuator-ui</module>
<module>spring-boot-sample-actuator-custom-security</module>
<module>spring-boot-sample-amqp</module>
<module>spring-boot-sample-aop</module>
<module>spring-boot-sample-atmosphere</module>

@ -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,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>

@ -1,4 +1,4 @@
package sample.actuator;
package sample.actuator.customsecurity;
import java.net.URI;
import java.util.Map;
@ -12,10 +12,14 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.LocalHostUriTemplateHandler;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
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;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
@ -59,7 +63,7 @@ public class CorsSampleActuatorApplicationTests {
@Test
public void preflightRequestToEndpointShouldReturnOk() throws Exception {
RequestEntity<?> healthRequest = RequestEntity
.options(new URI("/application/health"))
.options(new URI("/application/env"))
.header("Origin", "http://localhost:8080")
.header("Access-Control-Request-Method", "GET").build();
ResponseEntity<?> exchange = this.testRestTemplate.exchange(healthRequest,
@ -70,7 +74,7 @@ public class CorsSampleActuatorApplicationTests {
@Test
public void preflightRequestWhenCorsConfigInvalidShouldReturnForbidden()
throws Exception {
RequestEntity<?> entity = RequestEntity.options(new URI("/application/health"))
RequestEntity<?> entity = RequestEntity.options(new URI("/application/env"))
.header("Origin", "http://localhost:9095")
.header("Access-Control-Request-Method", "GET").build();
ResponseEntity<byte[]> exchange = this.testRestTemplate.exchange(entity,

@ -14,16 +14,12 @@
* limitations under the License.
*/
package sample.actuator;
import java.util.Map;
package sample.actuator.customsecurity;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.web.server.LocalManagementPort;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
@ -39,17 +35,14 @@ import static org.assertj.core.api.Assertions.assertThat;
* Integration tests for separate management and main service ports.
*
* @author Dave Syer
* @author Madhura Bhave
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
"management.port=0", "management.context-path=/admin",
"management.security.enabled=false" })
"management.port=0", "management.context-path=/admin"})
@DirtiesContext
public class InsecureManagementPortAndPathSampleActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@LocalServerPort
private int port = 9010;
@ -59,27 +52,24 @@ public class InsecureManagementPortAndPathSampleActuatorApplicationTests {
@Test
public void testHome() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = new TestRestTemplate("user", getPassword())
.getForEntity("http://localhost:" + this.port, Map.class);
ResponseEntity<String> entity = new TestRestTemplate("user", "password")
.getForEntity("http://localhost:" + this.port, String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
@SuppressWarnings("unchecked")
Map<String, Object> body = entity.getBody();
assertThat(body.get("message")).isEqualTo("Hello Phil");
assertThat(entity.getBody()).contains("Hello World");
}
@Test
public void testMetrics() throws Exception {
testHome(); // makes sure some requests have been made
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(
"http://localhost:" + this.managementPort + "/admin/metrics", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
public void testSecureActuator() throws Exception {
ResponseEntity<String> entity = new TestRestTemplate().getForEntity(
"http://localhost:" + this.managementPort + "/admin/health",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
@Test
public void testHealth() throws Exception {
public void testInsecureActuator() throws Exception {
ResponseEntity<String> entity = new TestRestTemplate().getForEntity(
"http://localhost:" + this.managementPort + "/admin/health",
"http://localhost:" + this.managementPort + "/admin/status",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("\"status\":\"UP\"");
@ -87,15 +77,11 @@ public class InsecureManagementPortAndPathSampleActuatorApplicationTests {
@Test
public void testMissing() throws Exception {
ResponseEntity<String> entity = new TestRestTemplate().getForEntity(
ResponseEntity<String> entity = new TestRestTemplate("admin", "admin").getForEntity(
"http://localhost:" + this.managementPort + "/admin/missing",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
assertThat(entity.getBody()).contains("\"status\":404");
}
private String getPassword() {
return this.security.getUser().getPassword();
}
}

@ -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);
}
}

@ -18,10 +18,21 @@ package sample.actuator.log4j2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@SpringBootApplication
public class SampleActuatorLog4J2Application {
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("password").roles("USER").build());
return manager;
}
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleActuatorLog4J2Application.class, args);
}

@ -1,3 +1,2 @@
endpoints.shutdown.enabled=true
management.security.enabled=false
endpoints.all.web.enabled=true

@ -16,6 +16,8 @@
package sample.actuator.log4j2;
import java.util.Base64;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Rule;
@ -63,10 +65,17 @@ public class SampleActuatorLog4J2ApplicationTests {
@Test
public void validateLoggersEndpoint() throws Exception {
this.mvc.perform(get("/application/loggers/org.apache.coyote.http11.Http11NioProtocol"))
this.mvc.perform(get("/application/loggers/org.apache.coyote.http11.Http11NioProtocol")
.header("Authorization", "Basic " + getBasicAuth()))
.andExpect(status().isOk())
.andExpect(content().string(equalTo("{\"configuredLevel\":\"WARN\","
+ "\"effectiveLevel\":\"WARN\"}")));
}
private String getBasicAuth() {
return new String(Base64.getEncoder()
.encode(("user:password").getBytes()));
}
}

@ -21,6 +21,10 @@ import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@ -29,6 +33,13 @@ import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class SampleActuatorUiApplication {
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("password").roles("USER").build());
return manager;
}
@GetMapping("/")
public String home(Map<String, Object> model) {
model.put("message", "Hello World");

@ -1,4 +1,2 @@
health.diskspace.enabled=false
# empty so home page is unsecured
security.basic.path=
endpoints.all.web.enabled=true

@ -21,9 +21,7 @@ import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.web.server.LocalManagementPort;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
@ -46,9 +44,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext
public class SampleActuatorUiApplicationPortTests {
@Autowired
private SecurityProperties security;
@LocalServerPort
private int port = 9010;
@ -82,7 +77,7 @@ public class SampleActuatorUiApplicationPortTests {
}
private String getPassword() {
return this.security.getUser().getPassword();
return "password";
}
}

@ -54,7 +54,8 @@ public class SampleActuatorUiApplicationTests {
public void testHome() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = this.restTemplate.exchange("/", HttpMethod.GET,
ResponseEntity<String> entity = this.restTemplate.withBasicAuth("user", getPassword())
.exchange("/", HttpMethod.GET,
new HttpEntity<Void>(headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("<title>Hello");
@ -80,11 +81,16 @@ public class SampleActuatorUiApplicationTests {
public void testError() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = this.restTemplate.exchange("/error",
ResponseEntity<String> entity = this.restTemplate.withBasicAuth("user", getPassword())
.exchange("/error",
HttpMethod.GET, new HttpEntity<Void>(headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
assertThat(entity.getBody()).contains("<html>").contains("<body>")
.contains("Please contact the operator with the above information");
}
private String getPassword() {
return "password";
}
}

@ -22,6 +22,10 @@ import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@SpringBootApplication
@EnableConfigurationProperties(ServiceProperties.class)
@ -31,6 +35,13 @@ public class SampleActuatorApplication {
SpringApplication.run(SampleActuatorApplication.class, args);
}
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("password").roles("USER").build());
return manager;
}
@Bean
public HealthIndicator helloHealthIndicator() {
return new HealthIndicator() {

@ -11,6 +11,7 @@ server.tomcat.accesslog.pattern=%h %t "%r" %s %b
security.require-ssl=false
#spring.jackson.serialization.INDENT_OUTPUT=true
spring.jmx.enabled=true
endpoints.all.web.enabled=true
spring.jackson.serialization.write_dates_as_timestamps=false

@ -22,7 +22,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
@ -45,9 +44,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@ActiveProfiles("endpoints")
public class EndpointsPropertiesSampleActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@Autowired
private TestRestTemplate restTemplate;
@ -74,7 +70,7 @@ public class EndpointsPropertiesSampleActuatorApplicationTests {
}
private String getPassword() {
return this.security.getUser().getPassword();
return "password";
}
}

@ -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");
}
}

@ -21,9 +21,7 @@ import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.web.server.LocalManagementPort;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
@ -47,9 +45,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext
public class ManagementAddressActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@LocalServerPort
private int port = 9010;
@ -75,7 +70,7 @@ public class ManagementAddressActuatorApplicationTests {
}
private String getPassword() {
return this.security.getUser().getPassword();
return "password";
}
}

@ -22,7 +22,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
@ -47,9 +46,6 @@ public class ManagementPathSampleActuatorApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private SecurityProperties securityProperties;
@Test
public void testHealth() throws Exception {
ResponseEntity<String> entity = this.restTemplate
@ -71,7 +67,7 @@ public class ManagementPathSampleActuatorApplicationTests {
}
private String getPassword() {
return this.securityProperties.getUser().getPassword();
return "password";
}
}

@ -21,9 +21,7 @@ import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.web.server.LocalManagementPort;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
@ -46,9 +44,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext
public class ManagementPortAndPathSampleActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@LocalServerPort
private int port = 9010;
@ -98,7 +93,7 @@ public class ManagementPortAndPathSampleActuatorApplicationTests {
@Test
public void testErrorPage() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = new TestRestTemplate()
ResponseEntity<Map> entity = new TestRestTemplate("user", getPassword())
.getForEntity("http://localhost:" + this.port + "/error", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
@SuppressWarnings("unchecked")
@ -109,7 +104,7 @@ public class ManagementPortAndPathSampleActuatorApplicationTests {
@Test
public void testManagementErrorPage() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(
ResponseEntity<Map> entity = new TestRestTemplate("user", getPassword()).getForEntity(
"http://localhost:" + this.managementPort + "/error", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
@SuppressWarnings("unchecked")
@ -118,7 +113,7 @@ public class ManagementPortAndPathSampleActuatorApplicationTests {
}
private String getPassword() {
return this.security.getUser().getPassword();
return "password";
}
}

@ -18,14 +18,14 @@ package sample.actuator;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.web.server.LocalManagementPort;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpStatus;
@ -46,9 +46,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext
public class ManagementPortSampleActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@LocalServerPort
private int port = 9010;
@ -89,7 +86,7 @@ public class ManagementPortSampleActuatorApplicationTests {
@Test
public void testErrorPage() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(
ResponseEntity<Map> entity = new TestRestTemplate("user", getPassword()).getForEntity(
"http://localhost:" + this.managementPort + "/error", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
@SuppressWarnings("unchecked")
@ -98,7 +95,7 @@ public class ManagementPortSampleActuatorApplicationTests {
}
private String getPassword() {
return this.security.getUser().getPassword();
return "password";
}
}

@ -22,7 +22,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
@ -44,9 +43,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext
public class NoManagementSampleActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@Autowired
private TestRestTemplate restTemplate;
@ -71,7 +67,7 @@ public class NoManagementSampleActuatorApplicationTests {
}
private String getPassword() {
return this.security.getUser().getPassword();
return "password";
}
}

@ -25,7 +25,6 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
@ -51,9 +50,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext
public class SampleActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@Autowired
private TestRestTemplate restTemplate;
@ -204,7 +200,8 @@ public class SampleActuatorApplicationTests {
@Test
public void testErrorPageDirectAccess() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/error", Map.class);
ResponseEntity<Map> entity = this.restTemplate.withBasicAuth("user", getPassword())
.getForEntity("/error", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
@SuppressWarnings("unchecked")
Map<String, Object> body = entity.getBody();
@ -239,7 +236,7 @@ public class SampleActuatorApplicationTests {
}
private String getPassword() {
return this.security.getUser().getPassword();
return "password";
}
}

@ -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);
}
}

@ -22,7 +22,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
@ -47,13 +46,11 @@ public class ServletPathSampleActuatorApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private SecurityProperties security;
@Test
public void testErrorPath() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/spring/error",
ResponseEntity<Map> entity = this.restTemplate.withBasicAuth("user", getPassword())
.getForEntity("/spring/error",
Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
@SuppressWarnings("unchecked")
@ -84,7 +81,7 @@ public class ServletPathSampleActuatorApplicationTests {
}
private String getPassword() {
return this.security.getUser().getPassword();
return "password";
}
}

@ -22,7 +22,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
@ -43,9 +42,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext
public class ShutdownSampleActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@Autowired
private TestRestTemplate restTemplate;
@ -73,7 +69,7 @@ public class ShutdownSampleActuatorApplicationTests {
}
private String getPassword() {
return this.security.getUser().getPassword();
return "password";
}
}

@ -1,3 +1,2 @@
server.error.path: /oops
management.context-path: /admin
endpoints.health.sensitive: false

@ -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();
}
}

@ -20,7 +20,11 @@ import java.util.UUID;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@ -34,6 +38,13 @@ public class SampleSecureOAuth2ActuatorApplication {
return new Message("Hello World");
}
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("password").roles("USER").build());
return manager;
}
public static void main(String[] args) {
SpringApplication.run(SampleSecureOAuth2ActuatorApplication.class, args);
}

@ -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,7 +1,5 @@
spring.datasource.platform=h2
security.user.name=greg
security.user.password=turnquist
security.oauth2.client.client-id=foo
security.oauth2.client.client-secret=bar
security.oauth2.authorization.checkTokenAccess=isAuthenticated()

@ -20,11 +20,15 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@EnableAutoConfiguration
@ComponentScan
@ -34,6 +38,13 @@ public class SampleSecureApplication implements CommandLineRunner {
@Autowired
private SampleService service;
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("password").roles("USER").build());
return manager;
}
@Override
public void run(String... args) throws Exception {
SecurityContextHolder.getContext()

@ -20,15 +20,10 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import sample.secure.SampleSecureApplicationTests.TestConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@ -43,23 +38,17 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Dave Syer
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = { SampleSecureApplication.class, TestConfiguration.class })
@SpringBootTest(classes = {SampleSecureApplication.class})
public class SampleSecureApplicationTests {
@Autowired
private SampleService service;
@Autowired
private ApplicationContext context;
private Authentication authentication;
@Before
public void init() {
AuthenticationManager authenticationManager = this.context
.getBean(AuthenticationManager.class);
this.authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken("user", "password"));
this.authentication = new UsernamePasswordAuthenticationToken("user", "password");
}
@After
@ -90,9 +79,4 @@ public class SampleSecureApplicationTests {
assertThat("Goodbye World").isEqualTo(this.service.denied());
}
@PropertySource("classpath:test.properties")
@Configuration
protected static class TestConfiguration {
}
}

@ -30,11 +30,21 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@SpringBootConfiguration
@EnableAutoConfiguration
public class SampleServletApplication extends SpringBootServletInitializer {
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("password").roles("USER").build());
return manager;
}
@SuppressWarnings("serial")
@Bean
public Servlet dispatcherServlet() {

@ -16,15 +16,20 @@
package sample.servlet;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
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.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
@ -44,12 +49,11 @@ public class SampleServletApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private SecurityProperties security;
@Test
public void testHomeIsSecure() throws Exception {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/", String.class);
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
ResponseEntity<String> entity = this.restTemplate.exchange("/", HttpMethod.GET, new HttpEntity<Void>(headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
@ -62,6 +66,6 @@ public class SampleServletApplicationTests {
}
private String getPassword() {
return this.security.getUser().getPassword();
return "password";
}
}

@ -20,7 +20,7 @@ import java.util.Date;
import java.util.Map;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.autoconfigure.security.SpringBootSecurity;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
@ -80,7 +80,6 @@ public class SampleMethodSecurityApplication implements WebMvcConfigurer {
}
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
@Override
@ -94,4 +93,25 @@ public class SampleMethodSecurityApplication implements WebMvcConfigurer {
}
@Configuration
@Order(1)
protected static class ActuatorSecurity extends WebSecurityConfigurerAdapter {
private final SpringBootSecurity springBootSecurity;
public ActuatorSecurity(SpringBootSecurity springBootSecurity) {
this.springBootSecurity = springBootSecurity;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(this.springBootSecurity.endpointIds(SpringBootSecurity.ALL_ENDPOINTS))
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
}
}
}

@ -1,2 +1,3 @@
spring.thymeleaf.cache: false
logging.level.org.springframework.security: INFO
endpoints.all.web.enabled=true

@ -105,8 +105,10 @@ public class SampleMethodSecurityApplicationTests {
@Test
public void testManagementProtected() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
ResponseEntity<String> entity = this.restTemplate
.getForEntity("/application/beans", String.class);
.exchange("/application/beans", HttpMethod.GET, new HttpEntity<Void>(headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}

@ -20,10 +20,8 @@ import java.util.Date;
import java.util.Map;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.WebSecurityConfigurerAdapter;
@ -60,7 +58,6 @@ public class SampleWebSecureCustomApplication implements WebMvcConfigurer {
}
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
@Override

@ -23,10 +23,8 @@ import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.WebSecurityConfigurerAdapter;
@ -63,7 +61,6 @@ public class SampleWebSecureJdbcApplication implements WebMvcConfigurer {
}
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
@Autowired

@ -20,10 +20,9 @@ import java.util.Date;
import java.util.Map;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.autoconfigure.security.SpringBootSecurity;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.WebSecurityConfigurerAdapter;
@ -60,12 +59,19 @@ public class SampleWebSecureApplication implements WebMvcConfigurer {
}
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
private final SpringBootSecurity springBootSecurity;
public ApplicationSecurity(SpringBootSecurity springBootSecurity) {
this.springBootSecurity = springBootSecurity;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin()
http.authorizeRequests()
.requestMatchers(this.springBootSecurity.staticResources()).permitAll()
.anyRequest().fullyAuthenticated().and().formLogin()
.loginPage("/login").failureUrl("/login?error").permitAll().and()
.logout().permitAll();
}

@ -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

@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
@ -39,7 +40,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/
@WebMvcTest
@RunWith(SpringRunner.class)
@TestPropertySource(properties = { "security.user.password=secret", "debug=true" })
@TestPropertySource(properties = { "debug=true" })
public class MockMvcSecurityAutoConfigurationIntegrationTests {
@Autowired
@ -53,7 +54,8 @@ public class MockMvcSecurityAutoConfigurationIntegrationTests {
@Test
public void unauthorizedResponseWithNoUser() throws Exception {
this.mockMvc.perform(get("/")).andExpect(status().isUnauthorized());
this.mockMvc.perform(get("/")
.accept(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized());
}
@Test

@ -18,7 +18,11 @@ package org.springframework.boot.test.autoconfigure.security;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -30,6 +34,13 @@ import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
public class SecurityTestApplication {
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("secret").roles("USER").build());
return manager;
}
@RestController
static class MyController {

@ -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");
}
}

@ -14,24 +14,24 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.security;
import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer;
package org.springframework.boot.endpoint;
/**
* Customizer that can be implemented by beans to configure paths that need to be ignored
* by Spring Boot's default Spring Security configuration.
* Strategy interface that can be used to resolve endpoint paths
* based on endpoint id.
*
* @author Madhura Bhave
* @since 1.5.0
* @since 2.0.0
*/
@FunctionalInterface
public interface IgnoredRequestCustomizer {
public interface EndpointPathResolver {
/**
* Customize the provided {@link IgnoredRequestConfigurer}.
* @param configurer the configurer to customize
* Resolve endpoint path based on id.
*
* @param endpointId the endpoint id
* @return the resolved path
*/
void customize(IgnoredRequestConfigurer configurer);
String resolvePath(String endpointId);
}

@ -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…
Cancel
Save