Commit e08ddbf8 authored by Madhura Bhave's avatar Madhura Bhave

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
parent f60ad0df
/*
* 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 @@ ...@@ -16,340 +16,32 @@
package org.springframework.boot.actuate.autoconfigure.security; package org.springframework.boot.actuate.autoconfigure.security;
import java.util.ArrayList; import org.springframework.boot.actuate.autoconfigure.endpoint.ManagementEndpointPathResolver;
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.web.ManagementServerProperties; 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.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.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;
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.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityPrerequisite; import org.springframework.boot.endpoint.EndpointPathResolver;
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.context.annotation.Bean; 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.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.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. * Security configuration for management 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)..
* *
* @author Dave Syer * @author Madhura Bhave
* @author Andy Wilkinson
* @since 2.0.0
*/ */
@Configuration @Configuration
@ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass({ EnableWebSecurity.class }) @ConditionalOnClass({ EnableWebSecurity.class })
@AutoConfigureAfter(SecurityAutoConfiguration.class) @AutoConfigureBefore(SecurityAutoConfiguration.class)
@AutoConfigureBefore(FallbackWebSecurityAutoConfiguration.class)
@EnableConfigurationProperties(ManagementServerProperties.class)
public class ManagementWebSecurityAutoConfiguration { public class ManagementWebSecurityAutoConfiguration {
private static final String[] NO_PATHS = new String[0];
private static final RequestMatcher MATCH_NONE = new NegatedRequestMatcher(
AnyRequestMatcher.INSTANCE);
@Bean @Bean
public IgnoredRequestCustomizer managementIgnoredRequestCustomizer( public EndpointPathResolver managementEndpointPathResolver(ManagementServerProperties properties) {
ManagementServerProperties management, return new ManagementEndpointPathResolver(properties);
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;
}
} }
} }
/* /*
* Copyright 2012-2016 the original author or authors. * Copyright 2012-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -14,48 +14,41 @@ ...@@ -14,48 +14,41 @@
* limitations under the License. * limitations under the License.
*/ */
package sample.actuator; package org.springframework.boot.actuate.autoconfigure.endpoint;
import java.util.Map;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.autoconfigure.web.ManagementServerProperties;
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; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Integration tests for insecured service endpoints (even with Spring Security on * Tests for {@link ManagementEndpointPathResolver}.
* classpath).
* *
* @author Dave Syer * @author Madhura Bhave
*/ */
@RunWith(SpringRunner.class) public class ManagementEndpointPathResolverTests {
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
"security.basic.enabled:false" }) private ManagementEndpointPathResolver resolver;
@DirtiesContext
public class InsecureSampleActuatorApplicationTests { @Rule
public ExpectedException thrown = ExpectedException.none();
@Autowired @Before
private TestRestTemplate restTemplate; public void setUp() throws Exception {
ManagementServerProperties properties = new ManagementServerProperties();
properties.setContextPath("/test");
this.resolver = new ManagementEndpointPathResolver(properties);
}
@Test @Test
public void testHome() throws Exception { public void resolveShouldReturnPathBasedOnContextPath() throws Exception {
@SuppressWarnings("rawtypes") String path = this.resolver.resolvePath("my-id");
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/", Map.class); assertThat(path.equals("/test/my-id"));
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");
} }
} }
/*
* 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; ...@@ -35,6 +35,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.mock.web.MockServletContext; 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.MockMvc;
import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
...@@ -194,4 +196,13 @@ public class MvcEndpointCorsIntegrationTests { ...@@ -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 ...@@ -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.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.EndpointInfrastructureAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.infrastructure.ServletEndpointAutoConfiguration; 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.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration; import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration;
...@@ -36,6 +35,7 @@ import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoC ...@@ -36,6 +35,7 @@ import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoC
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.test.context.TestSecurityContextHolder; import org.springframework.security.test.context.TestSecurityContextHolder;
...@@ -69,7 +69,8 @@ public class MvcEndpointIntegrationTests { ...@@ -69,7 +69,8 @@ public class MvcEndpointIntegrationTests {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.register(SecureConfiguration.class); this.context.register(SecureConfiguration.class);
MockMvc mockMvc = createSecureMockMvc(); MockMvc mockMvc = createSecureMockMvc();
mockMvc.perform(get("/application/beans")).andExpect(status().isUnauthorized()); mockMvc.perform(get("/application/beans")
.accept(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized());
} }
@Test @Test
...@@ -79,7 +80,8 @@ public class MvcEndpointIntegrationTests { ...@@ -79,7 +80,8 @@ public class MvcEndpointIntegrationTests {
TestPropertyValues.of("management.context-path:/management") TestPropertyValues.of("management.context-path:/management")
.applyTo(this.context); .applyTo(this.context);
MockMvc mockMvc = createSecureMockMvc(); MockMvc mockMvc = createSecureMockMvc();
mockMvc.perform(get("/management/beans")).andExpect(status().isUnauthorized()); mockMvc.perform(get("/management/beans")
.accept(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized());
} }
@Test @Test
...@@ -89,31 +91,13 @@ public class MvcEndpointIntegrationTests { ...@@ -89,31 +91,13 @@ public class MvcEndpointIntegrationTests {
new TestingAuthenticationToken("user", "N/A", "ROLE_ACTUATOR")); new TestingAuthenticationToken("user", "N/A", "ROLE_ACTUATOR"));
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.register(SecureConfiguration.class); 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", 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 mockMvc = createSecureMockMvc();
mockMvc.perform(get("/management/beans")).andExpect(status().isOk()); 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() { private MockMvc createSecureMockMvc() {
return doCreateMockMvc(springSecurity()); return doCreateMockMvc(springSecurity());
} }
...@@ -153,8 +137,7 @@ public class MvcEndpointIntegrationTests { ...@@ -153,8 +137,7 @@ public class MvcEndpointIntegrationTests {
} }
@Import(DefaultConfiguration.class) @Import(DefaultConfiguration.class)
@ImportAutoConfiguration({ SecurityAutoConfiguration.class, @ImportAutoConfiguration({ SecurityAutoConfiguration.class})
ManagementWebSecurityAutoConfiguration.class })
static class SecureConfiguration { static class SecureConfiguration {
} }
......
/*
* 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; ...@@ -26,7 +26,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; 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.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
...@@ -79,7 +78,6 @@ public class H2ConsoleAutoConfiguration { ...@@ -79,7 +78,6 @@ public class H2ConsoleAutoConfiguration {
@Configuration @Configuration
@ConditionalOnClass(WebSecurityConfigurerAdapter.class) @ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnBean(ObjectPostProcessor.class) @ConditionalOnBean(ObjectPostProcessor.class)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnProperty(prefix = "security.basic", name = "enabled", matchIfMissing = true) @ConditionalOnProperty(prefix = "security.basic", name = "enabled", matchIfMissing = true)
static class H2ConsoleSecurityConfiguration { static class H2ConsoleSecurityConfiguration {
...@@ -95,9 +93,6 @@ public class H2ConsoleAutoConfiguration { ...@@ -95,9 +93,6 @@ public class H2ConsoleAutoConfiguration {
@Autowired @Autowired
private H2ConsoleProperties console; private H2ConsoleProperties console;
@Autowired
private SecurityProperties security;
@Override @Override
public void configure(HttpSecurity http) throws Exception { public void configure(HttpSecurity http) throws Exception {
String path = this.console.getPath(); String path = this.console.getPath();
...@@ -106,14 +101,7 @@ public class H2ConsoleAutoConfiguration { ...@@ -106,14 +101,7 @@ public class H2ConsoleAutoConfiguration {
h2Console.csrf().disable(); h2Console.csrf().disable();
h2Console.httpBasic(); h2Console.httpBasic();
h2Console.headers().frameOptions().sameOrigin(); h2Console.headers().frameOptions().sameOrigin();
String[] roles = this.security.getUser().getRole().toArray(new String[0]); http.authorizeRequests().anyRequest().authenticated();
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 @@ ...@@ -17,9 +17,8 @@
package org.springframework.boot.autoconfigure.security; package org.springframework.boot.autoconfigure.security;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.UUID;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
...@@ -29,7 +28,6 @@ import org.springframework.beans.factory.SmartInitializingSingleton; ...@@ -29,7 +28,6 @@ import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.SecurityProperties.User;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
...@@ -47,13 +45,13 @@ import org.springframework.security.config.annotation.authentication.builders.Au ...@@ -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.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter; import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer; import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
/** /**
* Configuration for a Spring Security in-memory {@link AuthenticationManager}. Can be * Configuration for a Spring Security in-memory {@link AuthenticationManager}. Can be
* disabled by providing a bean of type AuthenticationManager, or by autowiring an * disabled by providing a bean of type {@link AuthenticationManager}, {@link AuthenticationProvider}
* {@link AuthenticationManagerBuilder} into a method in one of your configuration * or {@link UserDetailsService}. The value provided by this configuration will become the "global"
* classes. The value provided by this configuration will become the "global"
* authentication manager (from Spring Security), or the parent of the global instance. * 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 * 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 * enabled, and as a parent authentication manager for "local" authentication managers in
...@@ -61,10 +59,12 @@ import org.springframework.util.ReflectionUtils; ...@@ -61,10 +59,12 @@ import org.springframework.util.ReflectionUtils;
* *
* @author Dave Syer * @author Dave Syer
* @author Rob Winch * @author Rob Winch
* @author Madhura Bhave
*/ */
@Configuration @Configuration
@ConditionalOnBean(ObjectPostProcessor.class) @ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean({ AuthenticationManager.class }) @ConditionalOnMissingBean({ AuthenticationManager.class,
AuthenticationProvider.class, UserDetailsService.class})
@Order(0) @Order(0)
public class AuthenticationManagerConfiguration { public class AuthenticationManagerConfiguration {
...@@ -102,7 +102,7 @@ public class AuthenticationManagerConfiguration { ...@@ -102,7 +102,7 @@ public class AuthenticationManagerConfiguration {
* {@link GlobalAuthenticationConfigurerAdapter#init(AuthenticationManagerBuilder)} * {@link GlobalAuthenticationConfigurerAdapter#init(AuthenticationManagerBuilder)}
* exists that adds a {@link SecurityConfigurer} to the * exists that adds a {@link SecurityConfigurer} to the
* {@link AuthenticationManagerBuilder}.</li> * {@link AuthenticationManagerBuilder}.</li>
* <li>{@link AuthenticationManagerConfiguration#init(AuthenticationManagerBuilder)} * <li>{@link AuthenticationManagerConfiguration}
* adds {@link SpringBootAuthenticationConfigurerAdapter} so it is after the * adds {@link SpringBootAuthenticationConfigurerAdapter} so it is after the
* {@link SecurityConfigurer} in the first step.</li> * {@link SecurityConfigurer} in the first step.</li>
* <li>We then can default an {@link AuthenticationProvider} if necessary. Note we can * <li>We then can default an {@link AuthenticationProvider} if necessary. Note we can
...@@ -168,14 +168,11 @@ public class AuthenticationManagerConfiguration { ...@@ -168,14 +168,11 @@ public class AuthenticationManagerConfiguration {
if (auth.isConfigured()) { if (auth.isConfigured()) {
return; return;
} }
User user = this.securityProperties.getUser(); String password = UUID.randomUUID().toString();
if (user.isDefaultPassword()) { logger.info(String.format("%n%nUsing default security password: %s%n",
logger.info(String.format("%n%nUsing default security password: %s%n", password));
user.getPassword())); withUser("user").password(password)
} .roles();
Set<String> roles = new LinkedHashSet<>(user.getRole());
withUser(user.getName()).password(user.getPassword())
.roles(roles.toArray(new String[roles.size()]));
setField(auth, "defaultUserDetailsService", getUserDetailsService()); setField(auth, "defaultUserDetailsService", getUserDetailsService());
super.configure(auth); super.configure(auth);
} }
......
/*
* 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 @@ ...@@ -16,10 +16,14 @@
package org.springframework.boot.autoconfigure.security; package org.springframework.boot.autoconfigure.security;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 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.context.properties.EnableConfigurationProperties;
import org.springframework.boot.endpoint.DefaultEndpointPathResolver;
import org.springframework.boot.endpoint.EndpointPathResolver;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -49,10 +53,23 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur ...@@ -49,10 +53,23 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
GlobalAuthenticationConfigurerAdapter.class }) GlobalAuthenticationConfigurerAdapter.class })
@EnableConfigurationProperties(SecurityProperties.class) @EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, @Import({ SpringBootWebSecurityConfiguration.class,
WebSecurityEnablerConfiguration.class,
AuthenticationManagerConfiguration.class, AuthenticationManagerConfiguration.class,
BootGlobalAuthenticationConfiguration.class, SecurityDataConfiguration.class }) SecurityDataConfiguration.class })
public class SecurityAutoConfiguration { 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 @Bean
@ConditionalOnMissingBean(AuthenticationEventPublisher.class) @ConditionalOnMissingBean(AuthenticationEventPublisher.class)
public DefaultAuthenticationEventPublisher authenticationEventPublisher( public DefaultAuthenticationEventPublisher authenticationEventPublisher(
......
...@@ -16,20 +16,14 @@ ...@@ -16,20 +16,14 @@
package org.springframework.boot.autoconfigure.security; package org.springframework.boot.autoconfigure.security;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.DispatcherType; import org.springframework.boot.web.servlet.DispatcherType;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.core.Ordered; 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. * Properties for the security aspects of an application.
...@@ -70,33 +64,8 @@ public class SecurityProperties implements SecurityPrerequisite { ...@@ -70,33 +64,8 @@ public class SecurityProperties implements SecurityPrerequisite {
public static final int DEFAULT_FILTER_ORDER = FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER public static final int DEFAULT_FILTER_ORDER = FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER
- 100; - 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 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. * Security filter chain order.
*/ */
...@@ -108,22 +77,6 @@ public class SecurityProperties implements SecurityPrerequisite { ...@@ -108,22 +77,6 @@ public class SecurityProperties implements SecurityPrerequisite {
private Set<DispatcherType> filterDispatcherTypes = new HashSet<>(Arrays.asList( private Set<DispatcherType> filterDispatcherTypes = new HashSet<>(Arrays.asList(
DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST)); 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() { public Basic getBasic() {
return this.basic; return this.basic;
} }
...@@ -132,30 +85,6 @@ public class SecurityProperties implements SecurityPrerequisite { ...@@ -132,30 +85,6 @@ public class SecurityProperties implements SecurityPrerequisite {
this.basic = basic; 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() { public int getFilterOrder() {
return this.filterOrder; return this.filterOrder;
} }
...@@ -172,122 +101,6 @@ public class SecurityProperties implements SecurityPrerequisite { ...@@ -172,122 +101,6 @@ public class SecurityProperties implements SecurityPrerequisite {
this.filterDispatcherTypes = filterDispatcherTypes; 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 { public static class Basic {
/** /**
...@@ -295,16 +108,6 @@ public class SecurityProperties implements SecurityPrerequisite { ...@@ -295,16 +108,6 @@ public class SecurityProperties implements SecurityPrerequisite {
*/ */
private boolean enabled = true; 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. * Security authorize mode to apply.
*/ */
...@@ -318,84 +121,6 @@ public class SecurityProperties implements SecurityPrerequisite { ...@@ -318,84 +121,6 @@ public class SecurityProperties implements SecurityPrerequisite {
this.enabled = enabled; 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;
}
} }
} }
/*
* 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 @@ ...@@ -16,284 +16,41 @@
package org.springframework.boot.autoconfigure.security; 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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; 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.context.annotation.Configuration;
import org.springframework.core.annotation.Order; 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.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.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.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 * The default configuration for web security. It relies on Spring Security's
* secured with HTTP Basic authentication except the * content-negotiation strategy to determine what sort of authentication to use.
* {@link SecurityProperties#getIgnored() explicitly ignored} paths (defaults to * If the user specifies their own {@link WebSecurityConfigurerAdapter}, this will back-off
* <code>&#47;css&#47;**, &#47;js&#47;**, &#47;images&#47;**, &#47;**&#47;favicon.ico</code> * completely and the users should specify all the bits that they want to configure as part
* ). Many aspects of the behavior can be controller with {@link SecurityProperties} via * of the custom security configuration.
* 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>
* *
* @author Dave Syer * @author Madhura Bhave
* @author Andy Wilkinson
*/ */
@Configuration @ConditionalOnProperty(prefix = "security.basic", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(ServerProperties.class) @ConditionalOnClass(EnableWebSecurity.class)
@ConditionalOnClass({ EnableWebSecurity.class, AuthenticationEntryPoint.class }) @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(WebSecurityConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnWebApplication(type = Type.SERVLET)
@EnableWebSecurity
public class SpringBootWebSecurityConfiguration { 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 @Configuration
@ConditionalOnProperty(prefix = "security.basic", name = "enabled", havingValue = "false")
@Order(SecurityProperties.BASIC_AUTH_ORDER) @Order(SecurityProperties.BASIC_AUTH_ORDER)
protected static class ApplicationNoWebSecurityConfigurerAdapter static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
extends WebSecurityConfigurerAdapter {
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher((request) -> false); super.configure(http);
http.csrf().disable();
} }
}
@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;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
if (this.security.isRequireSsl()) {
http.requiresChannel().anyRequest().requiresSecure();
}
if (!this.security.isEnableCsrf()) {
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 @@ ...@@ -16,33 +16,27 @@
package org.springframework.boot.autoconfigure.security; 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.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 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;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration; 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.configuration.WebSecurityConfigurerAdapter;
/** /**
* If the user explicitly disables the basic security features and forgets to * If there is a bean of type WebSecurityConfigurerAdapter,
* {@code @EnableWebSecurity}, and yet still wants a bean of type * this adds the {@code @EnableWebSecurity} annotation if it is not already specified.
* WebSecurityConfigurerAdapter, he is trying to use a custom security setup. The app * This will make sure that the annotation is present with default security autoconfiguration
* would fail in a confusing way without this shim configuration, which just helpfully * and also if the user adds custom security and forgets to add the annotation.
* defines an empty {@code @EnableWebSecurity}.
* *
* @author Dave Syer * @author Madhura Bhave
*/ */
@ConditionalOnProperty(prefix = "security.basic", name = "enabled", havingValue = "false")
@ConditionalOnBean(WebSecurityConfigurerAdapter.class) @ConditionalOnBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnClass(EnableWebSecurity.class) @ConditionalOnClass(EnableWebSecurity.class)
@ConditionalOnMissingBean(WebSecurityConfiguration.class) @ConditionalOnMissingBean(WebSecurityConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@AutoConfigureAfter(SecurityAutoConfiguration.class)
@EnableWebSecurity @EnableWebSecurity
public class FallbackWebSecurityAutoConfiguration { public class WebSecurityEnablerConfiguration {
} }
...@@ -98,7 +98,6 @@ org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\ ...@@ -98,7 +98,6 @@ org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\ org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\ org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
......
...@@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfigurationInteg ...@@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfigurationInteg
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
...@@ -60,26 +61,18 @@ public class H2ConsoleAutoConfigurationIntegrationTests { ...@@ -60,26 +61,18 @@ public class H2ConsoleAutoConfigurationIntegrationTests {
public void noPrincipal() throws Exception { public void noPrincipal() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(springSecurity()).build(); .apply(springSecurity()).build();
mockMvc.perform(get("/h2-console/")).andExpect(status().isUnauthorized()); mockMvc.perform(get("/h2-console/").accept(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized());
} }
@Test @Test
public void userPrincipal() throws Exception { public void userPrincipal() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(springSecurity()).build(); .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(status().isOk())
.andExpect(header().string("X-Frame-Options", "SAMEORIGIN")); .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 @Configuration
@Import({ SecurityAutoConfiguration.class, H2ConsoleAutoConfiguration.class }) @Import({ SecurityAutoConfiguration.class, H2ConsoleAutoConfiguration.class })
@Controller @Controller
......
...@@ -21,6 +21,7 @@ import java.util.EnumSet; ...@@ -21,6 +21,7 @@ import java.util.EnumSet;
import javax.servlet.DispatcherType; import javax.servlet.DispatcherType;
import org.junit.After; import org.junit.After;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
...@@ -28,6 +29,7 @@ import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoCon ...@@ -28,6 +29,7 @@ import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoCon
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.test.City; 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.test.util.TestPropertyValues;
import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean; import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
...@@ -50,7 +52,6 @@ import org.springframework.security.config.annotation.authentication.configurers ...@@ -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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 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.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension; import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension;
...@@ -72,6 +73,9 @@ public class SecurityAutoConfigurationTests { ...@@ -72,6 +73,9 @@ public class SecurityAutoConfigurationTests {
private AnnotationConfigWebApplicationContext context; private AnnotationConfigWebApplicationContext context;
@Rule
public OutputCapture outputCapture = new OutputCapture();
@After @After
public void close() { public void close() {
if (this.context != null) { if (this.context != null) {
...@@ -87,9 +91,8 @@ public class SecurityAutoConfigurationTests { ...@@ -87,9 +91,8 @@ public class SecurityAutoConfigurationTests {
PropertyPlaceholderAutoConfiguration.class); PropertyPlaceholderAutoConfiguration.class);
this.context.refresh(); this.context.refresh();
assertThat(this.context.getBean(AuthenticationManagerBuilder.class)).isNotNull(); assertThat(this.context.getBean(AuthenticationManagerBuilder.class)).isNotNull();
// 1 for static resources and one for the rest
assertThat(this.context.getBean(FilterChainProxy.class).getFilterChains()) assertThat(this.context.getBean(FilterChainProxy.class).getFilterChains())
.hasSize(2); .hasSize(1);
} }
@Test @Test
...@@ -157,7 +160,7 @@ public class SecurityAutoConfigurationTests { ...@@ -157,7 +160,7 @@ public class SecurityAutoConfigurationTests {
} }
@Test @Test
public void testDisableBasicAuthOnApplicationPaths() throws Exception { public void testDisableDefaultSecurity() throws Exception {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext()); this.context.setServletContext(new MockServletContext());
this.context.register(SecurityAutoConfiguration.class, this.context.register(SecurityAutoConfiguration.class,
...@@ -166,7 +169,7 @@ public class SecurityAutoConfigurationTests { ...@@ -166,7 +169,7 @@ public class SecurityAutoConfigurationTests {
this.context.refresh(); this.context.refresh();
// Ignores and the "matches-none" filter only // Ignores and the "matches-none" filter only
assertThat(this.context.getBeanNamesForType(FilterChainProxy.class).length) assertThat(this.context.getBeanNamesForType(FilterChainProxy.class).length)
.isEqualTo(1); .isEqualTo(0);
} }
@Test @Test
...@@ -295,33 +298,24 @@ public class SecurityAutoConfigurationTests { ...@@ -295,33 +298,24 @@ public class SecurityAutoConfigurationTests {
this.context.setServletContext(new MockServletContext()); this.context.setServletContext(new MockServletContext());
this.context.register(SecurityAutoConfiguration.class); this.context.register(SecurityAutoConfiguration.class);
this.context.refresh(); 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); AuthenticationManager manager = this.context.getBean(AuthenticationManager.class);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
security.getUser().getName(), security.getUser().getPassword()); "user", password);
assertThat(manager.authenticate(token)).isNotNull(); assertThat(manager.authenticate(token)).isNotNull();
} }
@Test @Test
public void testCustomAuthenticationDoesNotAuthenticateWithBootSecurityUser() public void testCustomAuthenticationDoesNotCreateDefaultUser()
throws Exception { throws Exception {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext()); this.context.setServletContext(new MockServletContext());
this.context.register(AuthenticationManagerCustomizer.class, this.context.register(AuthenticationManagerCustomizer.class,
SecurityAutoConfiguration.class); SecurityAutoConfiguration.class);
this.context.refresh(); this.context.refresh();
SecurityProperties security = this.context.getBean(SecurityProperties.class);
AuthenticationManager manager = this.context.getBean(AuthenticationManager.class); AuthenticationManager manager = this.context.getBean(AuthenticationManager.class);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( assertThat(this.outputCapture.toString()).doesNotContain("Using default security password: ");
security.getUser().getName(), security.getUser().getPassword()); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("foo", "bar");
try {
manager.authenticate(token);
fail("Expected Exception");
}
catch (AuthenticationException success) {
// Expected
}
token = new UsernamePasswordAuthenticationToken("foo", "bar");
assertThat(manager.authenticate(token)).isNotNull(); assertThat(manager.authenticate(token)).isNotNull();
} }
......
...@@ -23,6 +23,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; ...@@ -23,6 +23,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.module.SimpleModule;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -30,9 +31,9 @@ import org.springframework.boot.autoconfigure.ImportAutoConfiguration; ...@@ -30,9 +31,9 @@ import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; 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.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; 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.util.TestPropertyValues;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
...@@ -54,17 +55,19 @@ import org.springframework.web.bind.annotation.RestController; ...@@ -54,17 +55,19 @@ import org.springframework.web.bind.annotation.RestController;
*/ */
public class SecurityFilterAutoConfigurationEarlyInitializationTests { public class SecurityFilterAutoConfigurationEarlyInitializationTests {
// gh-4154 @Rule
public OutputCapture outputCapture = new OutputCapture();
@Test @Test
public void testSecurityFilterDoesNotCauseEarlyInitialization() throws Exception { public void testSecurityFilterDoesNotCauseEarlyInitialization() throws Exception {
try (AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext()) { try (AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext()) {
TestPropertyValues.of("server.port:0", "security.user.password:password") TestPropertyValues.of("server.port:0")
.applyTo(context); .applyTo(context);
context.register(Config.class); context.register(Config.class);
context.refresh(); context.refresh();
int port = context.getWebServer().getPort(); 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); .getForEntity("http://localhost:" + port, Object.class);
// If early initialization occurred a ConverterNotFoundException is thrown // If early initialization occurred a ConverterNotFoundException is thrown
...@@ -76,7 +79,7 @@ public class SecurityFilterAutoConfigurationEarlyInitializationTests { ...@@ -76,7 +79,7 @@ public class SecurityFilterAutoConfigurationEarlyInitializationTests {
ConverterBean.class }) ConverterBean.class })
@ImportAutoConfiguration({ WebMvcAutoConfiguration.class, @ImportAutoConfiguration({ WebMvcAutoConfiguration.class,
JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebSecurity.class, DispatcherServletAutoConfiguration.class,
SecurityAutoConfiguration.class, SecurityFilterAutoConfiguration.class, SecurityAutoConfiguration.class, SecurityFilterAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class }) PropertyPlaceholderAutoConfiguration.class })
static class Config { static class Config {
......
...@@ -17,8 +17,6 @@ ...@@ -17,8 +17,6 @@
package org.springframework.boot.autoconfigure.security; package org.springframework.boot.autoconfigure.security;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Test; import org.junit.Test;
...@@ -39,63 +37,9 @@ public class SecurityPropertiesTests { ...@@ -39,63 +37,9 @@ public class SecurityPropertiesTests {
private SecurityProperties security = new SecurityProperties(); private SecurityProperties security = new SecurityProperties();
@Test @Test
public void testBindingIgnoredSingleValued() { public void testBinding() {
bind("security.ignored", "/css/**"); bind("security.basic.enabled", "false");
assertThat(this.security.getIgnored()).hasSize(1); assertThat(this.security.getBasic().isEnabled()).isFalse();
}
@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]");
} }
private void bind(String name, String value) { private void bind(String name, String value) {
......
/*
* 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 {
}
}
/*
* 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; ...@@ -40,7 +40,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext; 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.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 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.header;
...@@ -76,12 +75,6 @@ public class CustomOAuth2SsoConfigurationTests { ...@@ -76,12 +75,6 @@ public class CustomOAuth2SsoConfigurationTests {
.addFilters(this.filter).build(); .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 @Test
public void uiPageIsSecure() throws Exception { public void uiPageIsSecure() throws Exception {
this.mvc.perform(get("/ui/")).andExpect(status().isFound()) this.mvc.perform(get("/ui/")).andExpect(status().isFound())
......
...@@ -42,10 +42,8 @@ import org.springframework.web.bind.annotation.RequestMapping; ...@@ -42,10 +42,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext; 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.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 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; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/** /**
...@@ -78,12 +76,6 @@ public class CustomOAuth2SsoWithAuthenticationEntryPointConfigurationTests { ...@@ -78,12 +76,6 @@ public class CustomOAuth2SsoWithAuthenticationEntryPointConfigurationTests {
.addFilters(this.filter).build(); .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 @Test
public void uiPageIsSecure() throws Exception { public void uiPageIsSecure() throws Exception {
this.mvc.perform(get("/ui/")).andExpect(status().isUnauthorized()); this.mvc.perform(get("/ui/")).andExpect(status().isUnauthorized());
......
...@@ -16,17 +16,21 @@ ...@@ -16,17 +16,21 @@
package sample; package sample;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; 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 @SpringBootApplication
public class HelloWebSecurityApplication { public class HelloWebSecurityApplication {
@Autowired @Bean
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { public UserDetailsService userDetailsService() throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER"); InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("password").roles("USER").build());
return manager;
} }
public static void main(String[] args) { public static void main(String[] args) {
......
...@@ -57,6 +57,7 @@ public class HelloWebSecurityApplicationTests { ...@@ -57,6 +57,7 @@ public class HelloWebSecurityApplicationTests {
@Test @Test
public void requiresAuthentication() throws Exception { public void requiresAuthentication() throws Exception {
this.request.addHeader("Accept", "application/json");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()) assertThat(this.response.getStatus())
.isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
...@@ -64,6 +65,7 @@ public class HelloWebSecurityApplicationTests { ...@@ -64,6 +65,7 @@ public class HelloWebSecurityApplicationTests {
@Test @Test
public void userAuthenticates() throws Exception { public void userAuthenticates() throws Exception {
this.request.addHeader("Accept", "application/json");
this.request.addHeader("Authorization", "Basic " + new String( this.request.addHeader("Authorization", "Basic " + new String(
Base64.getEncoder().encode("user:password".getBytes("UTF-8")))); Base64.getEncoder().encode("user:password".getBytes("UTF-8"))));
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
<module>spring-boot-sample-actuator-log4j2</module> <module>spring-boot-sample-actuator-log4j2</module>
<module>spring-boot-sample-actuator-noweb</module> <module>spring-boot-sample-actuator-noweb</module>
<module>spring-boot-sample-actuator-ui</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-amqp</module>
<module>spring-boot-sample-aop</module> <module>spring-boot-sample-aop</module>
<module>spring-boot-sample-atmosphere</module> <module>spring-boot-sample-atmosphere</module>
......
<?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>
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");
}
}
/*
* 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);
}
}
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();
}
}
/*!
* Bootstrap v2.0.4
*
* Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world @twitter by @mdo and @fat.
*/article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover{color:#005580;text-decoration:underline}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;content:""}.row:after{clear:both}[class*="span"]{float:left;margin-left:20px}.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.127659574%;*margin-left:2.0744680846382977%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:99.99999998999999%;*width:99.94680850063828%}.row-fluid .span11{width:91.489361693%;*width:91.4361702036383%}.row-fluid .span10{width:82.97872339599999%;*width:82.92553190663828%}.row-fluid .span9{width:74.468085099%;*width:74.4148936096383%}.row-fluid .span8{width:65.95744680199999%;*width:65.90425531263828%}.row-fluid .span7{width:57.446808505%;*width:57.3936170156383%}.row-fluid .span6{width:48.93617020799999%;*width:48.88297871863829%}.row-fluid .span5{width:40.425531911%;*width:40.3723404216383%}.row-fluid .span4{width:31.914893614%;*width:31.8617021246383%}.row-fluid .span3{width:23.404255317%;*width:23.3510638276383%}.row-fluid .span2{width:14.89361702%;*width:14.8404255306383%}.row-fluid .span1{width:6.382978723%;*width:6.329787233638298%}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;content:""}.container-fluid:after{clear:both}p{margin:0 0 9px}p small{font-size:11px;color:#999}.lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px}h1,h2,h3,h4,h5,h6{margin:0;font-family:inherit;font-weight:bold;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;color:#999}h1{font-size:30px;line-height:36px}h1 small{font-size:18px}h2{font-size:24px;line-height:36px}h2 small{font-size:18px}h3{font-size:18px;line-height:27px}h3 small{font-size:14px}h4,h5,h6{line-height:18px}h4{font-size:14px}h4 small{font-size:12px}h5{font-size:12px}h6{font-size:11px;color:#999;text-transform:uppercase}.page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #eee}.page-header h1{line-height:1}ul,ol{padding:0;margin:0 0 9px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}ul{list-style:disc}ol{list-style:decimal}li{line-height:18px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}dl{margin-bottom:18px}dt,dd{line-height:18px}dt{font-weight:bold;line-height:17px}dd{margin-left:9px}.dl-horizontal dt{float:left;width:120px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:130px}hr{margin:18px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}strong{font-weight:bold}em{font-style:italic}.muted{color:#999}abbr[title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px}blockquote small{display:block;line-height:18px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:18px;font-style:normal;line-height:18px}small{font-size:100%}cite{font-style:normal}code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:8.5px;margin:0 0 9px;font-size:12.025px;line-height:18px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:18px}pre code{padding:0;color:inherit;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 18px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:27px;font-size:19.5px;line-height:36px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:13.5px;color:#999}label,input,button,select,textarea{font-size:13px;font-weight:normal;line-height:18px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:18px;padding:4px;margin-bottom:9px;font-size:13px;line-height:18px;color:#555}input,textarea{width:210px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-ms-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:3px 0;*margin-top:0;line-height:normal;cursor:pointer}input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}.uneditable-textarea{width:auto;height:auto}select,input[type="file"]{height:28px;*margin-top:4px;line-height:28px}select{width:220px;border:1px solid #bbb}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.radio,.checkbox{min-height:18px;padding-left:18px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}input.span12,textarea.span12,.uneditable-input.span12{width:930px}input.span11,textarea.span11,.uneditable-input.span11{width:850px}input.span10,textarea.span10,.uneditable-input.span10{width:770px}input.span9,textarea.span9,.uneditable-input.span9{width:690px}input.span8,textarea.span8,.uneditable-input.span8{width:610px}input.span7,textarea.span7,.uneditable-input.span7{width:530px}input.span6,textarea.span6,.uneditable-input.span6{width:450px}input.span5,textarea.span5,.uneditable-input.span5{width:370px}input.span4,textarea.span4,.uneditable-input.span4{width:290px}input.span3,textarea.span3,.uneditable-input.span3{width:210px}input.span2,textarea.span2,.uneditable-input.span2{width:130px}input.span1,textarea.span1,.uneditable-input.span1{width:50px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee;border-color:#ddd}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;border-color:#c09853}.control-group.warning .checkbox:focus,.control-group.warning .radio:focus,.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:0 0 6px #dbc59e;-moz-box-shadow:0 0 6px #dbc59e;box-shadow:0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;border-color:#b94a48}.control-group.error .checkbox:focus,.control-group.error .radio:focus,.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:0 0 6px #d59392;-moz-box-shadow:0 0 6px #d59392;box-shadow:0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;border-color:#468847}.control-group.success .checkbox:focus,.control-group.success .radio:focus,.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:0 0 6px #7aba7b;-moz-box-shadow:0 0 6px #7aba7b;box-shadow:0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:17px 20px 18px;margin-top:18px;margin-bottom:18px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;content:""}.form-actions:after{clear:both}.uneditable-input{overflow:hidden;white-space:nowrap;cursor:not-allowed;background-color:#fff;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}:-moz-placeholder{color:#999}:-ms-input-placeholder{color:#999}::-webkit-input-placeholder{color:#999}.help-block,.help-inline{color:#555}.help-block{display:block;margin-bottom:9px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-prepend,.input-append{margin-bottom:5px}.input-prepend input,.input-append input,.input-prepend select,.input-append select,.input-prepend .uneditable-input,.input-append .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:middle;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.input-prepend input:focus,.input-append input:focus,.input-prepend select:focus,.input-append select:focus,.input-prepend .uneditable-input:focus,.input-append .uneditable-input:focus{z-index:2}.input-prepend .uneditable-input,.input-append .uneditable-input{border-left-color:#ccc}.input-prepend .add-on,.input-append .add-on{display:inline-block;width:auto;height:18px;min-width:16px;padding:4px 5px;font-weight:normal;line-height:18px;text-align:center;text-shadow:0 1px 0 #fff;vertical-align:middle;background-color:#eee;border:1px solid #ccc}.input-prepend .add-on,.input-append .add-on,.input-prepend .btn,.input-append .btn{margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend .active,.input-append .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-append .uneditable-input{border-right-color:#ccc;border-left-color:#eee}.input-append .add-on:last-child,.input-append .btn:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:9px}legend+.control-group{margin-top:18px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:18px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:140px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:160px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:160px}.form-horizontal .help-block{margin-top:9px;margin-bottom:0}.form-horizontal .form-actions{padding-left:160px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:18px}.table th,.table td{padding:8px;line-height:18px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapsed;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9}.table tbody tr:hover td,.table tbody tr:hover th{background-color:#f5f5f5}table .span1{float:none;width:44px;margin-left:0}table .span2{float:none;width:124px;margin-left:0}table .span3{float:none;width:204px;margin-left:0}table .span4{float:none;width:284px;margin-left:0}table .span5{float:none;width:364px;margin-left:0}table .span6{float:none;width:444px;margin-left:0}table .span7{float:none;width:524px;margin-left:0}table .span8{float:none;width:604px;margin-left:0}table .span9{float:none;width:684px;margin-left:0}table .span10{float:none;width:764px;margin-left:0}table .span11{float:none;width:844px;margin-left:0}table .span12{float:none;width:924px;margin-left:0}table .span13{float:none;width:1004px;margin-left:0}table .span14{float:none;width:1084px;margin-left:0}table .span15{float:none;width:1164px;margin-left:0}table .span16{float:none;width:1244px;margin-left:0}table .span17{float:none;width:1324px;margin-left:0}table .span18{float:none;width:1404px;margin-left:0}table .span19{float:none;width:1484px;margin-left:0}table .span20{float:none;width:1564px;margin-left:0}table .span21{float:none;width:1644px;margin-left:0}table .span22{float:none;width:1724px;margin-left:0}table .span23{float:none;width:1804px;margin-left:0}table .span24{float:none;width:1884px;margin-left:0}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}[class^="icon-"]:last-child,[class*=" icon-"]:last-child{*margin-left:0}.icon-white{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{background-position:-384px -120px}.icon-folder-open{background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:"";opacity:.3;filter:alpha(opacity=30)}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown:hover .caret,.open .caret{opacity:1;filter:alpha(opacity=100)}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:4px 0;margin:1px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:8px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu a{display:block;padding:3px 15px;clear:both;font-weight:normal;line-height:18px;color:#333;white-space:nowrap}.dropdown-menu li>a:hover,.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#fff;text-decoration:none;background-color:#08c}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:"\2191"}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #eee;border:1px solid rgba(0,0,0,0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-ms-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-ms-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:18px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 10px 4px;margin-bottom:0;*margin-left:.3em;font-size:13px;line-height:18px;*line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-ms-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(top,#fff,#e6e6e6);background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff',endColorstr='#e6e6e6',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover{color:#333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-ms-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-color:#e6e6e6;background-color:#d9d9d9 \9;background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-color:#e6e6e6;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:9px 14px;font-size:15px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.btn-large [class^="icon-"]{margin-top:1px}.btn-small{padding:5px 9px;font-size:11px;line-height:16px}.btn-small [class^="icon-"]{margin-top:-1px}.btn-mini{padding:2px 6px;font-size:11px;line-height:14px}.btn-primary,.btn-primary:hover,.btn-warning,.btn-warning:hover,.btn-danger,.btn-danger:hover,.btn-success,.btn-success:hover,.btn-info,.btn-info:hover,.btn-inverse,.btn-inverse:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn{border-color:#ccc;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.btn-primary{background-color:#0074cc;*background-color:#05c;background-image:-ms-linear-gradient(top,#08c,#05c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#05c));background-image:-webkit-linear-gradient(top,#08c,#05c);background-image:-o-linear-gradient(top,#08c,#05c);background-image:-moz-linear-gradient(top,#08c,#05c);background-image:linear-gradient(top,#08c,#05c);background-repeat:repeat-x;border-color:#05c #05c #003580;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#0088cc',endColorstr='#0055cc',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#05c;*background-color:#004ab3}.btn-primary:active,.btn-primary.active{background-color:#004099 \9}.btn-warning{background-color:#faa732;*background-color:#f89406;background-image:-ms-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(top,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#fbb450',endColorstr='#f89406',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{background-color:#da4f49;*background-color:#bd362f;background-image:-ms-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(top,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#bd362f',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{background-color:#5bb75b;*background-color:#51a351;background-image:-ms-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(top,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#62c462',endColorstr='#51a351',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{background-color:#49afcd;*background-color:#2f96b4;background-image:-ms-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(top,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#5bc0de',endColorstr='#2f96b4',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{background-color:#414141;*background-color:#222;background-image:-ms-linear-gradient(top,#555,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#555),to(#222));background-image:-webkit-linear-gradient(top,#555,#222);background-image:-o-linear-gradient(top,#555,#222);background-image:-moz-linear-gradient(top,#555,#222);background-image:linear-gradient(top,#555,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#555555',endColorstr='#222222',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-group{position:relative;*margin-left:.3em;*zoom:1}.btn-group:before,.btn-group:after{display:table;content:""}.btn-group:after{clear:both}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:9px;margin-bottom:9px}.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1}.btn-group>.btn{position:relative;float:left;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.dropdown-toggle{*padding-top:4px;padding-right:8px;*padding-bottom:4px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini.dropdown-toggle{padding-right:5px;padding-left:5px}.btn-group>.btn-small.dropdown-toggle{*padding-top:4px;*padding-bottom:4px}.btn-group>.btn-large.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#05c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:7px;margin-left:0}.btn:hover .caret,.open.btn-group .caret{opacity:1;filter:alpha(opacity=100)}.btn-mini .caret{margin-top:5px}.btn-small .caret{margin-top:6px}.btn-large .caret{margin-top:6px;border-top-width:5px;border-right-width:5px;border-left-width:5px}.dropup .btn-large .caret{border-top:0;border-bottom:5px solid #000}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:.75;filter:alpha(opacity=75)}.alert{padding:8px 35px 8px 14px;margin-bottom:18px;color:#c09853;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert-heading{color:inherit}.alert .close{position:relative;top:-2px;right:-21px;line-height:18px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:18px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>.pull-right{float:right}.nav .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:18px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:8px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:18px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.nav-tabs.nav-stacked>li>a:hover{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 5px 5px;-moz-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px}.nav-pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-tabs .dropdown-toggle .caret,.nav-pills .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav-tabs .dropdown-toggle:hover .caret,.nav-pills .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .active .dropdown-toggle .caret,.nav-pills .active .dropdown-toggle .caret{border-top-color:#333;border-bottom-color:#333}.nav>.dropdown.active>a:hover{color:#000;cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.navbar{*position:relative;*z-index:2;margin-bottom:18px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top,#333,#222);background-image:-ms-linear-gradient(top,#333,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#333),to(#222));background-image:-webkit-linear-gradient(top,#333,#222);background-image:-o-linear-gradient(top,#333,#222);background-image:linear-gradient(top,#333,#222);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#333333',endColorstr='#222222',GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1)}.navbar .container{width:auto}.nav-collapse.collapse{height:auto}.navbar{color:#999}.navbar .brand:hover{text-decoration:none}.navbar .brand{display:block;float:left;padding:8px 20px 12px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#999}.navbar .navbar-text{margin-bottom:0;line-height:40px}.navbar .navbar-link{color:#999}.navbar .navbar-link:hover{color:#fff}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn{margin:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0}.navbar-search .search-query{padding:4px 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#fff;background-color:#626262;border:1px solid #151515;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none}.navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-bottom{bottom:0}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right}.navbar .nav>li{display:block;float:left}.navbar .nav>li>a{float:none;padding:9px 10px 11px;line-height:19px;color:#999;text-decoration:none;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar .btn{display:inline-block;padding:4px 10px 4px;margin:5px 5px 6px;line-height:18px}.navbar .btn-group{padding:5px 5px 6px;margin:0}.navbar .nav>li>a:hover{color:#fff;text-decoration:none;background-color:transparent}.navbar .nav .active>a,.navbar .nav .active>a:hover{color:#fff;text-decoration:none;background-color:#222}.navbar .divider-vertical{width:1px;height:40px;margin:0 9px;overflow:hidden;background-color:#222;border-right:1px solid #333}.navbar .nav.pull-right{margin-right:0;margin-left:10px}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;background-color:#2c2c2c;*background-color:#222;background-image:-ms-linear-gradient(top,#333,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#333),to(#222));background-image:-webkit-linear-gradient(top,#333,#222);background-image:-o-linear-gradient(top,#333,#222);background-image:linear-gradient(top,#333,#222);background-image:-moz-linear-gradient(top,#333,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:dximagetransform.microsoft.gradient(startColorstr='#333333',endColorstr='#222222',GradientType=0);filter:progid:dximagetransform.microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{background-color:#222;*background-color:#151515}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#080808 \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown .dropdown-toggle .caret,.navbar .nav li.dropdown.open .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar .nav li.dropdown.active .caret{opacity:1;filter:alpha(opacity=100)}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:transparent}.navbar .nav li.dropdown.active>.dropdown-toggle:hover{color:#fff}.navbar .pull-right .dropdown-menu,.navbar .dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right .dropdown-menu:before,.navbar .dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right .dropdown-menu:after,.navbar .dropdown-menu.pull-right:after{right:13px;left:auto}.breadcrumb{padding:7px 14px;margin:0 0 18px;list-style:none;background-color:#fbfbfb;background-image:-moz-linear-gradient(top,#fff,#f5f5f5);background-image:-ms-linear-gradient(top,#fff,#f5f5f5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#fff,#f5f5f5);background-image:-o-linear-gradient(top,#fff,#f5f5f5);background-image:linear-gradient(top,#fff,#f5f5f5);background-repeat:repeat-x;border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff',endColorstr='#f5f5f5',GradientType=0);-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.breadcrumb li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb .divider{padding:0 5px;color:#999}.breadcrumb .active a{color:#333}.pagination{height:36px;margin:18px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination li{display:inline}.pagination a{float:left;padding:0 14px;line-height:34px;text-decoration:none;border:1px solid #ddd;border-left-width:0}.pagination a:hover,.pagination .active a{background-color:#f5f5f5}.pagination .active a{color:#999;cursor:default}.pagination .disabled span,.pagination .disabled a,.pagination .disabled a:hover{color:#999;cursor:default;background-color:transparent}.pagination li:first-child a{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.pagination li:last-child a{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pager{margin-bottom:18px;margin-left:0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;content:""}.pager:after{clear:both}.pager li{display:inline}.pager a{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager a:hover{text-decoration:none;background-color:#f5f5f5}.pager .next a{float:right}.pager .previous a{float:left}.pager .disabled a,.pager .disabled a:hover{color:#999;cursor:default;background-color:#fff}.modal-open .dropdown-menu{z-index:2050}.modal-open .dropdown.open{*z-index:2050}.modal-open .popover{z-index:2060}.modal-open .tooltip{z-index:2070}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:50%;left:50%;z-index:1050;width:560px;margin:-250px 0 0 -280px;overflow:auto;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-ms-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:50%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-body{max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.tooltip{position:absolute;z-index:1020;display:block;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{margin-top:-2px}.tooltip.right{margin-left:2px}.tooltip.bottom{margin-top:2px}.tooltip.left{margin-left:-2px}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top:5px solid #000;border-right:5px solid transparent;border-left:5px solid transparent}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-right:5px solid transparent;border-bottom:5px solid #000;border-left:5px solid transparent}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-right:5px solid #000;border-bottom:5px solid transparent}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px}.popover.top{margin-top:-5px}.popover.right{margin-left:5px}.popover.bottom{margin-top:5px}.popover.left{margin-left:-5px}.popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-top:5px solid #000;border-right:5px solid transparent;border-left:5px solid transparent}.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-right:5px solid #000;border-bottom:5px solid transparent}.popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-right:5px solid transparent;border-bottom:5px solid #000;border-left:5px solid transparent}.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000}.popover .arrow{position:absolute;width:0;height:0}.popover-inner{width:280px;padding:3px;overflow:hidden;background:#000;background:rgba(0,0,0,0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3)}.popover-title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0}.popover-content{padding:14px;background-color:#fff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:18px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:1;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.075);box-shadow:0 1px 1px rgba(0,0,0,0.075)}a.thumbnail:hover{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px}.label,.badge{font-size:10.998px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{padding:1px 4px 2px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding:1px 9px 2px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}a.label:hover,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:18px;margin-bottom:18px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-ms-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(top,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#f5f5f5',endColorstr='#f9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{width:0;height:18px;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(top,#149bdf,#0480be);background-image:-ms-linear-gradient(top,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#149bdf',endColorstr='#0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-ms-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .bar{background-color:#149bdf;background-image:-o-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-webkit-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-ms-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-ms-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(top,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#c43c35',GradientType=0)}.progress-danger.progress-striped .bar{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-ms-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-ms-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(top,#62c462,#57a957);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#62c462',endColorstr='#57a957',GradientType=0)}.progress-success.progress-striped .bar{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-ms-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-ms-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(top,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#5bc0de',endColorstr='#339bb9',GradientType=0)}.progress-info.progress-striped .bar{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-ms-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-ms-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(top,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:dximagetransform.microsoft.gradient(startColorstr='#fbb450',endColorstr='#f89406',GradientType=0)}.progress-warning.progress-striped .bar{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-ms-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:18px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:18px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel .item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-ms-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel .item>img{display:block;line-height:1}.carousel .active,.carousel .next,.carousel .prev{display:block}.carousel .active{left:0}.carousel .next,.carousel .prev{position:absolute;top:0;width:100%}.carousel .next{left:100%}.carousel .prev{left:-100%}.carousel .next.left,.carousel .prev.right{left:0}.carousel .active.left{left:-100%}.carousel .active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:10px 15px 5px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{color:#fff}.hero-unit{padding:60px;margin-bottom:30px;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit p{font-size:18px;font-weight:200;line-height:27px;color:inherit}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}
input.field-error, textarea.field-error { border: 1px solid #B94A48; }
\ No newline at end of file
<#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>
<#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>
package sample.actuator; package sample.actuator.customsecurity;
import java.net.URI; import java.net.URI;
import java.util.Map; import java.util.Map;
...@@ -12,10 +12,14 @@ import org.springframework.boot.test.context.SpringBootTest; ...@@ -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.LocalHostUriTemplateHandler;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity; import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 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.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
...@@ -59,7 +63,7 @@ public class CorsSampleActuatorApplicationTests { ...@@ -59,7 +63,7 @@ public class CorsSampleActuatorApplicationTests {
@Test @Test
public void preflightRequestToEndpointShouldReturnOk() throws Exception { public void preflightRequestToEndpointShouldReturnOk() throws Exception {
RequestEntity<?> healthRequest = RequestEntity RequestEntity<?> healthRequest = RequestEntity
.options(new URI("/application/health")) .options(new URI("/application/env"))
.header("Origin", "http://localhost:8080") .header("Origin", "http://localhost:8080")
.header("Access-Control-Request-Method", "GET").build(); .header("Access-Control-Request-Method", "GET").build();
ResponseEntity<?> exchange = this.testRestTemplate.exchange(healthRequest, ResponseEntity<?> exchange = this.testRestTemplate.exchange(healthRequest,
...@@ -70,7 +74,7 @@ public class CorsSampleActuatorApplicationTests { ...@@ -70,7 +74,7 @@ public class CorsSampleActuatorApplicationTests {
@Test @Test
public void preflightRequestWhenCorsConfigInvalidShouldReturnForbidden() public void preflightRequestWhenCorsConfigInvalidShouldReturnForbidden()
throws Exception { throws Exception {
RequestEntity<?> entity = RequestEntity.options(new URI("/application/health")) RequestEntity<?> entity = RequestEntity.options(new URI("/application/env"))
.header("Origin", "http://localhost:9095") .header("Origin", "http://localhost:9095")
.header("Access-Control-Request-Method", "GET").build(); .header("Access-Control-Request-Method", "GET").build();
ResponseEntity<byte[]> exchange = this.testRestTemplate.exchange(entity, ResponseEntity<byte[]> exchange = this.testRestTemplate.exchange(entity,
......
...@@ -14,16 +14,12 @@ ...@@ -14,16 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package sample.actuator; package sample.actuator.customsecurity;
import java.util.Map;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.web.server.LocalManagementPort; 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;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
...@@ -39,17 +35,14 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -39,17 +35,14 @@ import static org.assertj.core.api.Assertions.assertThat;
* Integration tests for separate management and main service ports. * Integration tests for separate management and main service ports.
* *
* @author Dave Syer * @author Dave Syer
* @author Madhura Bhave
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
"management.port=0", "management.context-path=/admin", "management.port=0", "management.context-path=/admin"})
"management.security.enabled=false" })
@DirtiesContext @DirtiesContext
public class InsecureManagementPortAndPathSampleActuatorApplicationTests { public class InsecureManagementPortAndPathSampleActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@LocalServerPort @LocalServerPort
private int port = 9010; private int port = 9010;
...@@ -59,27 +52,24 @@ public class InsecureManagementPortAndPathSampleActuatorApplicationTests { ...@@ -59,27 +52,24 @@ public class InsecureManagementPortAndPathSampleActuatorApplicationTests {
@Test @Test
public void testHome() throws Exception { public void testHome() throws Exception {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = new TestRestTemplate("user", getPassword()) ResponseEntity<String> entity = new TestRestTemplate("user", "password")
.getForEntity("http://localhost:" + this.port, Map.class); .getForEntity("http://localhost:" + this.port, String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
@SuppressWarnings("unchecked") assertThat(entity.getBody()).contains("Hello World");
Map<String, Object> body = entity.getBody();
assertThat(body.get("message")).isEqualTo("Hello Phil");
} }
@Test @Test
public void testMetrics() throws Exception { public void testSecureActuator() throws Exception {
testHome(); // makes sure some requests have been made ResponseEntity<String> entity = new TestRestTemplate().getForEntity(
@SuppressWarnings("rawtypes") "http://localhost:" + this.managementPort + "/admin/health",
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity( String.class);
"http://localhost:" + this.managementPort + "/admin/metrics", Map.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
} }
@Test @Test
public void testHealth() throws Exception { public void testInsecureActuator() throws Exception {
ResponseEntity<String> entity = new TestRestTemplate().getForEntity( ResponseEntity<String> entity = new TestRestTemplate().getForEntity(
"http://localhost:" + this.managementPort + "/admin/health", "http://localhost:" + this.managementPort + "/admin/status",
String.class); String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("\"status\":\"UP\""); assertThat(entity.getBody()).contains("\"status\":\"UP\"");
...@@ -87,15 +77,11 @@ public class InsecureManagementPortAndPathSampleActuatorApplicationTests { ...@@ -87,15 +77,11 @@ public class InsecureManagementPortAndPathSampleActuatorApplicationTests {
@Test @Test
public void testMissing() throws Exception { 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", "http://localhost:" + this.managementPort + "/admin/missing",
String.class); String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
assertThat(entity.getBody()).contains("\"status\":404"); assertThat(entity.getBody()).contains("\"status\":404");
} }
private String getPassword() {
return this.security.getUser().getPassword();
}
} }
/* package sample.actuator.customsecurity;
* 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 java.util.Map;
...@@ -23,34 +7,27 @@ import org.junit.runner.RunWith; ...@@ -23,34 +7,27 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; 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.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Integration tests for insecured service endpoints (even with Spring Security on * @author Madhura Bhave
* classpath).
*
* @author Dave Syer
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
"management.security.enabled:false" })
@DirtiesContext @DirtiesContext
@ActiveProfiles("unsecure-management") public class SampleActuatorCustomSecurityApplicationTests {
public class InsecureManagementSampleActuatorApplicationTests {
@Autowired @Autowired
private TestRestTemplate restTemplate; private TestRestTemplate restTemplate;
@Test @Test
public void testHomeIsSecure() throws Exception { public void homeIsSecure() throws Exception {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/", Map.class); ResponseEntity<Map> entity = this.restTemplate.getForEntity("/", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
...@@ -61,20 +38,38 @@ public class InsecureManagementSampleActuatorApplicationTests { ...@@ -61,20 +38,38 @@ public class InsecureManagementSampleActuatorApplicationTests {
} }
@Test @Test
public void testMetrics() throws Exception { public void testInsecureApplicationPath() throws Exception {
try {
testHomeIsSecure(); // makes sure some requests have been made
}
catch (AssertionError ex) {
// ignore;
}
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/application/metrics", ResponseEntity<Map> entity = this.restTemplate.getForEntity("/foo", Map.class);
Map.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Object> body = entity.getBody(); Map<String, Object> body = entity.getBody();
assertThat(body).containsKey("counter.status.401.unmapped"); 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; ...@@ -18,10 +18,21 @@ package sample.actuator.log4j2;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; 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 @SpringBootApplication
public class SampleActuatorLog4J2Application { 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 { public static void main(String[] args) throws Exception {
SpringApplication.run(SampleActuatorLog4J2Application.class, args); SpringApplication.run(SampleActuatorLog4J2Application.class, args);
} }
......
endpoints.shutdown.enabled=true endpoints.shutdown.enabled=true
endpoints.all.web.enabled=true
management.security.enabled=false \ No newline at end of file
\ No newline at end of file
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package sample.actuator.log4j2; package sample.actuator.log4j2;
import java.util.Base64;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.junit.Rule; import org.junit.Rule;
...@@ -63,10 +65,17 @@ public class SampleActuatorLog4J2ApplicationTests { ...@@ -63,10 +65,17 @@ public class SampleActuatorLog4J2ApplicationTests {
@Test @Test
public void validateLoggersEndpoint() throws Exception { 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(status().isOk())
.andExpect(content().string(equalTo("{\"configuredLevel\":\"WARN\"," .andExpect(content().string(equalTo("{\"configuredLevel\":\"WARN\","
+ "\"effectiveLevel\":\"WARN\"}"))); + "\"effectiveLevel\":\"WARN\"}")));
} }
private String getBasicAuth() {
return new String(Base64.getEncoder()
.encode(("user:password").getBytes()));
}
} }
...@@ -21,6 +21,10 @@ import java.util.Map; ...@@ -21,6 +21,10 @@ import java.util.Map;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; 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.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
...@@ -29,6 +33,13 @@ import org.springframework.web.bind.annotation.RequestMapping; ...@@ -29,6 +33,13 @@ import org.springframework.web.bind.annotation.RequestMapping;
@Controller @Controller
public class SampleActuatorUiApplication { 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("/") @GetMapping("/")
public String home(Map<String, Object> model) { public String home(Map<String, Object> model) {
model.put("message", "Hello World"); model.put("message", "Hello World");
......
health.diskspace.enabled=false health.diskspace.enabled=false
endpoints.all.web.enabled=true
# empty so home page is unsecured \ No newline at end of file
security.basic.path=
\ No newline at end of file
...@@ -21,9 +21,7 @@ import java.util.Map; ...@@ -21,9 +21,7 @@ import java.util.Map;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.web.server.LocalManagementPort; 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;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
...@@ -46,9 +44,6 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -46,9 +44,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext @DirtiesContext
public class SampleActuatorUiApplicationPortTests { public class SampleActuatorUiApplicationPortTests {
@Autowired
private SecurityProperties security;
@LocalServerPort @LocalServerPort
private int port = 9010; private int port = 9010;
...@@ -82,7 +77,7 @@ public class SampleActuatorUiApplicationPortTests { ...@@ -82,7 +77,7 @@ public class SampleActuatorUiApplicationPortTests {
} }
private String getPassword() { private String getPassword() {
return this.security.getUser().getPassword(); return "password";
} }
} }
...@@ -54,7 +54,8 @@ public class SampleActuatorUiApplicationTests { ...@@ -54,7 +54,8 @@ public class SampleActuatorUiApplicationTests {
public void testHome() throws Exception { public void testHome() throws Exception {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); 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); new HttpEntity<Void>(headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("<title>Hello"); assertThat(entity.getBody()).contains("<title>Hello");
...@@ -80,11 +81,16 @@ public class SampleActuatorUiApplicationTests { ...@@ -80,11 +81,16 @@ public class SampleActuatorUiApplicationTests {
public void testError() throws Exception { public void testError() throws Exception {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); 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); HttpMethod.GET, new HttpEntity<Void>(headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
assertThat(entity.getBody()).contains("<html>").contains("<body>") assertThat(entity.getBody()).contains("<html>").contains("<body>")
.contains("Please contact the operator with the above information"); .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; ...@@ -22,6 +22,10 @@ import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; 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 @SpringBootApplication
@EnableConfigurationProperties(ServiceProperties.class) @EnableConfigurationProperties(ServiceProperties.class)
...@@ -31,6 +35,13 @@ public class SampleActuatorApplication { ...@@ -31,6 +35,13 @@ public class SampleActuatorApplication {
SpringApplication.run(SampleActuatorApplication.class, args); 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 @Bean
public HealthIndicator helloHealthIndicator() { public HealthIndicator helloHealthIndicator() {
return new HealthIndicator() { return new HealthIndicator() {
......
...@@ -11,6 +11,7 @@ server.tomcat.accesslog.pattern=%h %t "%r" %s %b ...@@ -11,6 +11,7 @@ server.tomcat.accesslog.pattern=%h %t "%r" %s %b
security.require-ssl=false security.require-ssl=false
#spring.jackson.serialization.INDENT_OUTPUT=true #spring.jackson.serialization.INDENT_OUTPUT=true
spring.jmx.enabled=true spring.jmx.enabled=true
endpoints.all.web.enabled=true
spring.jackson.serialization.write_dates_as_timestamps=false spring.jackson.serialization.write_dates_as_timestamps=false
......
...@@ -22,7 +22,6 @@ import org.junit.Test; ...@@ -22,7 +22,6 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; 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;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
...@@ -45,9 +44,6 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -45,9 +44,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@ActiveProfiles("endpoints") @ActiveProfiles("endpoints")
public class EndpointsPropertiesSampleActuatorApplicationTests { public class EndpointsPropertiesSampleActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@Autowired @Autowired
private TestRestTemplate restTemplate; private TestRestTemplate restTemplate;
...@@ -74,7 +70,7 @@ public class EndpointsPropertiesSampleActuatorApplicationTests { ...@@ -74,7 +70,7 @@ public class EndpointsPropertiesSampleActuatorApplicationTests {
} }
private String getPassword() { private String getPassword() {
return this.security.getUser().getPassword(); return "password";
} }
} }
...@@ -21,9 +21,7 @@ import java.util.Map; ...@@ -21,9 +21,7 @@ import java.util.Map;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.web.server.LocalManagementPort; 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;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
...@@ -47,9 +45,6 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -47,9 +45,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext @DirtiesContext
public class ManagementAddressActuatorApplicationTests { public class ManagementAddressActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@LocalServerPort @LocalServerPort
private int port = 9010; private int port = 9010;
...@@ -75,7 +70,7 @@ public class ManagementAddressActuatorApplicationTests { ...@@ -75,7 +70,7 @@ public class ManagementAddressActuatorApplicationTests {
} }
private String getPassword() { private String getPassword() {
return this.security.getUser().getPassword(); return "password";
} }
} }
...@@ -22,7 +22,6 @@ import org.junit.Test; ...@@ -22,7 +22,6 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; 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;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
...@@ -47,9 +46,6 @@ public class ManagementPathSampleActuatorApplicationTests { ...@@ -47,9 +46,6 @@ public class ManagementPathSampleActuatorApplicationTests {
@Autowired @Autowired
private TestRestTemplate restTemplate; private TestRestTemplate restTemplate;
@Autowired
private SecurityProperties securityProperties;
@Test @Test
public void testHealth() throws Exception { public void testHealth() throws Exception {
ResponseEntity<String> entity = this.restTemplate ResponseEntity<String> entity = this.restTemplate
...@@ -71,7 +67,7 @@ public class ManagementPathSampleActuatorApplicationTests { ...@@ -71,7 +67,7 @@ public class ManagementPathSampleActuatorApplicationTests {
} }
private String getPassword() { private String getPassword() {
return this.securityProperties.getUser().getPassword(); return "password";
} }
} }
...@@ -21,9 +21,7 @@ import java.util.Map; ...@@ -21,9 +21,7 @@ import java.util.Map;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.web.server.LocalManagementPort; 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;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
...@@ -46,9 +44,6 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -46,9 +44,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext @DirtiesContext
public class ManagementPortAndPathSampleActuatorApplicationTests { public class ManagementPortAndPathSampleActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@LocalServerPort @LocalServerPort
private int port = 9010; private int port = 9010;
...@@ -98,7 +93,7 @@ public class ManagementPortAndPathSampleActuatorApplicationTests { ...@@ -98,7 +93,7 @@ public class ManagementPortAndPathSampleActuatorApplicationTests {
@Test @Test
public void testErrorPage() throws Exception { public void testErrorPage() throws Exception {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = new TestRestTemplate() ResponseEntity<Map> entity = new TestRestTemplate("user", getPassword())
.getForEntity("http://localhost:" + this.port + "/error", Map.class); .getForEntity("http://localhost:" + this.port + "/error", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
...@@ -109,7 +104,7 @@ public class ManagementPortAndPathSampleActuatorApplicationTests { ...@@ -109,7 +104,7 @@ public class ManagementPortAndPathSampleActuatorApplicationTests {
@Test @Test
public void testManagementErrorPage() throws Exception { public void testManagementErrorPage() throws Exception {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity( ResponseEntity<Map> entity = new TestRestTemplate("user", getPassword()).getForEntity(
"http://localhost:" + this.managementPort + "/error", Map.class); "http://localhost:" + this.managementPort + "/error", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
...@@ -118,7 +113,7 @@ public class ManagementPortAndPathSampleActuatorApplicationTests { ...@@ -118,7 +113,7 @@ public class ManagementPortAndPathSampleActuatorApplicationTests {
} }
private String getPassword() { private String getPassword() {
return this.security.getUser().getPassword(); return "password";
} }
} }
...@@ -18,14 +18,14 @@ package sample.actuator; ...@@ -18,14 +18,14 @@ package sample.actuator;
import java.util.Map; import java.util.Map;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.web.server.LocalManagementPort; 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;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 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.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort; import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
...@@ -46,9 +46,6 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -46,9 +46,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext @DirtiesContext
public class ManagementPortSampleActuatorApplicationTests { public class ManagementPortSampleActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@LocalServerPort @LocalServerPort
private int port = 9010; private int port = 9010;
...@@ -89,7 +86,7 @@ public class ManagementPortSampleActuatorApplicationTests { ...@@ -89,7 +86,7 @@ public class ManagementPortSampleActuatorApplicationTests {
@Test @Test
public void testErrorPage() throws Exception { public void testErrorPage() throws Exception {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity( ResponseEntity<Map> entity = new TestRestTemplate("user", getPassword()).getForEntity(
"http://localhost:" + this.managementPort + "/error", Map.class); "http://localhost:" + this.managementPort + "/error", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
...@@ -98,7 +95,7 @@ public class ManagementPortSampleActuatorApplicationTests { ...@@ -98,7 +95,7 @@ public class ManagementPortSampleActuatorApplicationTests {
} }
private String getPassword() { private String getPassword() {
return this.security.getUser().getPassword(); return "password";
} }
} }
...@@ -22,7 +22,6 @@ import org.junit.Test; ...@@ -22,7 +22,6 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; 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;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
...@@ -44,9 +43,6 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -44,9 +43,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext @DirtiesContext
public class NoManagementSampleActuatorApplicationTests { public class NoManagementSampleActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@Autowired @Autowired
private TestRestTemplate restTemplate; private TestRestTemplate restTemplate;
...@@ -71,7 +67,7 @@ public class NoManagementSampleActuatorApplicationTests { ...@@ -71,7 +67,7 @@ public class NoManagementSampleActuatorApplicationTests {
} }
private String getPassword() { private String getPassword() {
return this.security.getUser().getPassword(); return "password";
} }
} }
...@@ -25,7 +25,6 @@ import org.junit.runner.RunWith; ...@@ -25,7 +25,6 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; 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;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
...@@ -51,9 +50,6 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -51,9 +50,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext @DirtiesContext
public class SampleActuatorApplicationTests { public class SampleActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@Autowired @Autowired
private TestRestTemplate restTemplate; private TestRestTemplate restTemplate;
...@@ -204,7 +200,8 @@ public class SampleActuatorApplicationTests { ...@@ -204,7 +200,8 @@ public class SampleActuatorApplicationTests {
@Test @Test
public void testErrorPageDirectAccess() throws Exception { public void testErrorPageDirectAccess() throws Exception {
@SuppressWarnings("rawtypes") @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); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Object> body = entity.getBody(); Map<String, Object> body = entity.getBody();
...@@ -239,7 +236,7 @@ public class SampleActuatorApplicationTests { ...@@ -239,7 +236,7 @@ public class SampleActuatorApplicationTests {
} }
private String getPassword() { private String getPassword() {
return this.security.getUser().getPassword(); return "password";
} }
} }
/*
* 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; ...@@ -22,7 +22,6 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; 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;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
...@@ -47,13 +46,11 @@ public class ServletPathSampleActuatorApplicationTests { ...@@ -47,13 +46,11 @@ public class ServletPathSampleActuatorApplicationTests {
@Autowired @Autowired
private TestRestTemplate restTemplate; private TestRestTemplate restTemplate;
@Autowired
private SecurityProperties security;
@Test @Test
public void testErrorPath() throws Exception { public void testErrorPath() throws Exception {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/spring/error", ResponseEntity<Map> entity = this.restTemplate.withBasicAuth("user", getPassword())
.getForEntity("/spring/error",
Map.class); Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
...@@ -84,7 +81,7 @@ public class ServletPathSampleActuatorApplicationTests { ...@@ -84,7 +81,7 @@ public class ServletPathSampleActuatorApplicationTests {
} }
private String getPassword() { private String getPassword() {
return this.security.getUser().getPassword(); return "password";
} }
} }
...@@ -22,7 +22,6 @@ import org.junit.Test; ...@@ -22,7 +22,6 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; 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;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
...@@ -43,9 +42,6 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -43,9 +42,6 @@ import static org.assertj.core.api.Assertions.assertThat;
@DirtiesContext @DirtiesContext
public class ShutdownSampleActuatorApplicationTests { public class ShutdownSampleActuatorApplicationTests {
@Autowired
private SecurityProperties security;
@Autowired @Autowired
private TestRestTemplate restTemplate; private TestRestTemplate restTemplate;
...@@ -73,7 +69,7 @@ public class ShutdownSampleActuatorApplicationTests { ...@@ -73,7 +69,7 @@ public class ShutdownSampleActuatorApplicationTests {
} }
private String getPassword() { private String getPassword() {
return this.security.getUser().getPassword(); return "password";
} }
} }
server.error.path: /oops server.error.path: /oops
management.context-path: /admin management.context-path: /admin
endpoints.health.sensitive: false \ No newline at end of file
\ No newline at end of file
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; ...@@ -20,7 +20,11 @@ import java.util.UUID;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; 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.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.GetMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
...@@ -34,6 +38,13 @@ public class SampleSecureOAuth2ActuatorApplication { ...@@ -34,6 +38,13 @@ public class SampleSecureOAuth2ActuatorApplication {
return new Message("Hello World"); 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) { public static void main(String[] args) {
SpringApplication.run(SampleSecureOAuth2ActuatorApplication.class, args); SpringApplication.run(SampleSecureOAuth2ActuatorApplication.class, args);
} }
......
server.port=8081 server.port=8081
security.basic.enabled=true endpoints.all.web.enabled=true
security.user.password=password
security.oauth2.resource.id=service security.oauth2.resource.id=service
security.oauth2.resource.userInfoUri=http://localhost:8080/user security.oauth2.resource.userInfoUri=http://localhost:8080/user
logging.level.org.springframework.security=DEBUG logging.level.org.springframework.security=DEBUG
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");
}
}
spring.datasource.platform=h2 spring.datasource.platform=h2
security.user.name=greg
security.user.password=turnquist
security.oauth2.client.client-id=foo security.oauth2.client.client-id=foo
security.oauth2.client.client-secret=bar security.oauth2.client.client-secret=bar
security.oauth2.authorization.checkTokenAccess=isAuthenticated() security.oauth2.authorization.checkTokenAccess=isAuthenticated()
......
...@@ -20,11 +20,15 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -20,11 +20,15 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner; import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder; 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 @EnableAutoConfiguration
@ComponentScan @ComponentScan
...@@ -34,6 +38,13 @@ public class SampleSecureApplication implements CommandLineRunner { ...@@ -34,6 +38,13 @@ public class SampleSecureApplication implements CommandLineRunner {
@Autowired @Autowired
private SampleService service; 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 @Override
public void run(String... args) throws Exception { public void run(String... args) throws Exception {
SecurityContextHolder.getContext() SecurityContextHolder.getContext()
......
...@@ -20,15 +20,10 @@ import org.junit.After; ...@@ -20,15 +20,10 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import sample.secure.SampleSecureApplicationTests.TestConfiguration;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; 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.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
...@@ -43,23 +38,17 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -43,23 +38,17 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Dave Syer * @author Dave Syer
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(classes = { SampleSecureApplication.class, TestConfiguration.class }) @SpringBootTest(classes = {SampleSecureApplication.class})
public class SampleSecureApplicationTests { public class SampleSecureApplicationTests {
@Autowired @Autowired
private SampleService service; private SampleService service;
@Autowired
private ApplicationContext context;
private Authentication authentication; private Authentication authentication;
@Before @Before
public void init() { public void init() {
AuthenticationManager authenticationManager = this.context this.authentication = new UsernamePasswordAuthenticationToken("user", "password");
.getBean(AuthenticationManager.class);
this.authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken("user", "password"));
} }
@After @After
...@@ -90,9 +79,4 @@ public class SampleSecureApplicationTests { ...@@ -90,9 +79,4 @@ public class SampleSecureApplicationTests {
assertThat("Goodbye World").isEqualTo(this.service.denied()); 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; ...@@ -30,11 +30,21 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean; 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 @SpringBootConfiguration
@EnableAutoConfiguration @EnableAutoConfiguration
public class SampleServletApplication extends SpringBootServletInitializer { 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") @SuppressWarnings("serial")
@Bean @Bean
public Servlet dispatcherServlet() { public Servlet dispatcherServlet() {
......
...@@ -16,15 +16,20 @@ ...@@ -16,15 +16,20 @@
package sample.servlet; package sample.servlet;
import java.util.Collections;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; 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;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate; 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.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
...@@ -44,12 +49,11 @@ public class SampleServletApplicationTests { ...@@ -44,12 +49,11 @@ public class SampleServletApplicationTests {
@Autowired @Autowired
private TestRestTemplate restTemplate; private TestRestTemplate restTemplate;
@Autowired
private SecurityProperties security;
@Test @Test
public void testHomeIsSecure() throws Exception { 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); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
} }
...@@ -62,6 +66,6 @@ public class SampleServletApplicationTests { ...@@ -62,6 +66,6 @@ public class SampleServletApplicationTests {
} }
private String getPassword() { private String getPassword() {
return this.security.getUser().getPassword(); return "password";
} }
} }
...@@ -20,7 +20,7 @@ import java.util.Date; ...@@ -20,7 +20,7 @@ import java.util.Date;
import java.util.Map; import java.util.Map;
import org.springframework.boot.autoconfigure.SpringBootApplication; 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.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
...@@ -80,7 +80,6 @@ public class SampleMethodSecurityApplication implements WebMvcConfigurer { ...@@ -80,7 +80,6 @@ public class SampleMethodSecurityApplication implements WebMvcConfigurer {
} }
@Configuration @Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter { protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
@Override @Override
...@@ -94,4 +93,25 @@ public class SampleMethodSecurityApplication implements WebMvcConfigurer { ...@@ -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();
}
}
} }
spring.thymeleaf.cache: false spring.thymeleaf.cache: false
logging.level.org.springframework.security: INFO logging.level.org.springframework.security: INFO
\ No newline at end of file endpoints.all.web.enabled=true
\ No newline at end of file
...@@ -105,8 +105,10 @@ public class SampleMethodSecurityApplicationTests { ...@@ -105,8 +105,10 @@ public class SampleMethodSecurityApplicationTests {
@Test @Test
public void testManagementProtected() throws Exception { public void testManagementProtected() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
ResponseEntity<String> entity = this.restTemplate 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); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
} }
......
...@@ -20,10 +20,8 @@ import java.util.Date; ...@@ -20,10 +20,8 @@ import java.util.Date;
import java.util.Map; import java.util.Map;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Configuration; 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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
...@@ -60,7 +58,6 @@ public class SampleWebSecureCustomApplication implements WebMvcConfigurer { ...@@ -60,7 +58,6 @@ public class SampleWebSecureCustomApplication implements WebMvcConfigurer {
} }
@Configuration @Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter { protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
@Override @Override
......
...@@ -23,10 +23,8 @@ import javax.sql.DataSource; ...@@ -23,10 +23,8 @@ import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Configuration; 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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
...@@ -63,7 +61,6 @@ public class SampleWebSecureJdbcApplication implements WebMvcConfigurer { ...@@ -63,7 +61,6 @@ public class SampleWebSecureJdbcApplication implements WebMvcConfigurer {
} }
@Configuration @Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter { protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
@Autowired @Autowired
......
...@@ -20,10 +20,9 @@ import java.util.Date; ...@@ -20,10 +20,9 @@ import java.util.Date;
import java.util.Map; import java.util.Map;
import org.springframework.boot.autoconfigure.SpringBootApplication; 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.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Configuration; 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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
...@@ -60,12 +59,19 @@ public class SampleWebSecureApplication implements WebMvcConfigurer { ...@@ -60,12 +59,19 @@ public class SampleWebSecureApplication implements WebMvcConfigurer {
} }
@Configuration @Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter { protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
private final SpringBootSecurity springBootSecurity;
public ApplicationSecurity(SpringBootSecurity springBootSecurity) {
this.springBootSecurity = springBootSecurity;
}
@Override @Override
protected void configure(HttpSecurity http) throws Exception { 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() .loginPage("/login").failureUrl("/login?error").permitAll().and()
.logout().permitAll(); .logout().permitAll();
} }
......
spring.thymeleaf.cache: false spring.thymeleaf.cache: false
security.basic.enabled: false security.basic.enabled: false
# demo only: # demo only:
security.user.password: password
logging.level.org.springframework.security: INFO logging.level.org.springframework.security: INFO
logging.level.org.springframework.boot.actuate.audit.listener.AuditListener: DEBUG logging.level.org.springframework.boot.actuate.audit.listener.AuditListener: DEBUG
\ No newline at end of file
...@@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -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.MockMvcSecurityAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
...@@ -39,7 +40,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. ...@@ -39,7 +40,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/ */
@WebMvcTest @WebMvcTest
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@TestPropertySource(properties = { "security.user.password=secret", "debug=true" }) @TestPropertySource(properties = { "debug=true" })
public class MockMvcSecurityAutoConfigurationIntegrationTests { public class MockMvcSecurityAutoConfigurationIntegrationTests {
@Autowired @Autowired
...@@ -53,7 +54,8 @@ public class MockMvcSecurityAutoConfigurationIntegrationTests { ...@@ -53,7 +54,8 @@ public class MockMvcSecurityAutoConfigurationIntegrationTests {
@Test @Test
public void unauthorizedResponseWithNoUser() throws Exception { public void unauthorizedResponseWithNoUser() throws Exception {
this.mockMvc.perform(get("/")).andExpect(status().isUnauthorized()); this.mockMvc.perform(get("/")
.accept(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized());
} }
@Test @Test
......
...@@ -18,7 +18,11 @@ package org.springframework.boot.test.autoconfigure.security; ...@@ -18,7 +18,11 @@ package org.springframework.boot.test.autoconfigure.security;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityAutoConfiguration; 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.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.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
...@@ -30,6 +34,13 @@ import org.springframework.web.bind.annotation.RestController; ...@@ -30,6 +34,13 @@ import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication @SpringBootApplication
public class SecurityTestApplication { 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 @RestController
static class MyController { static class MyController {
......
/*
* 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 @@ ...@@ -14,24 +14,24 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.security; package org.springframework.boot.endpoint;
import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer;
/** /**
* Customizer that can be implemented by beans to configure paths that need to be ignored * Strategy interface that can be used to resolve endpoint paths
* by Spring Boot's default Spring Security configuration. * based on endpoint id.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @since 1.5.0 * @since 2.0.0
*/ */
@FunctionalInterface public interface EndpointPathResolver {
public interface IgnoredRequestCustomizer {
/** /**
* Customize the provided {@link IgnoredRequestConfigurer}. * Resolve endpoint path based on id.
* @param configurer the configurer to customize *
* @param endpointId the endpoint id
* @return the resolved path
*/ */
void customize(IgnoredRequestConfigurer configurer); String resolvePath(String endpointId);
} }
/*
* 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");
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment