/* * 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 javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.security.SecurityProperties.Headers; import org.springframework.boot.autoconfigure.security.SecurityProperties.Headers.ContentSecurityPolicyMode; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.servlet.error.ErrorController; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.WebSecurityConfigurer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; import org.springframework.security.web.header.writers.HstsHeaderWriter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AnyRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * Configuration for security of a web application or service. By default everything is * secured with HTTP Basic authentication except the * {@link SecurityProperties#getIgnored() explicitly ignored} paths (defaults to * <code>/css/**, /js/**, /images/**, /**/favicon.ico</code> * ). Many aspects of the behavior can be controller with {@link SecurityProperties} via * externalized application properties (or via an bean definition of that type to set the * defaults). The user details for authentication are just placeholders * {@code (username=user, password=password)} but can easily be customized by providing a * an {@link AuthenticationManager}. Also provides audit logging of authentication events. * <p> * Some common simple customizations: * <ul> * <li>Switch off security completely and permanently: remove Spring Security from the * classpath or {@link EnableAutoConfiguration#exclude() exclude} * {@link SecurityAutoConfiguration}.</li> * <li>Switch off security temporarily (e.g. for a dev environment): set * {@code security.basic.enabled=false}</li> * <li>Customize the user details: autowire an {@link AuthenticationManagerBuilder} into a * method in one of your configuration classes or equivalently add a bean of type * AuthenticationManager</li> * <li>Add form login for user facing resources: add a * {@link WebSecurityConfigurerAdapter} and use {@link HttpSecurity#formLogin()}</li> * </ul> * * @author Dave Syer * @author Andy Wilkinson */ @Configuration @EnableConfigurationProperties(ServerProperties.class) @ConditionalOnClass({ EnableWebSecurity.class, AuthenticationEntryPoint.class }) @ConditionalOnMissingBean(WebSecurityConfiguration.class) @ConditionalOnWebApplication(type = Type.SERVLET) @EnableWebSecurity public class SpringBootWebSecurityConfiguration { private static List<String> DEFAULT_IGNORED = Arrays.asList("/css/**", "/js/**", "/images/**", "/webjars/**", "/**/favicon.ico"); @Bean @ConditionalOnMissingBean({ IgnoredPathsWebSecurityConfigurerAdapter.class }) public IgnoredPathsWebSecurityConfigurerAdapter ignoredPathsWebSecurityConfigurerAdapter( List<IgnoredRequestCustomizer> customizers) { return new IgnoredPathsWebSecurityConfigurerAdapter(customizers); } @Bean public IgnoredRequestCustomizer defaultIgnoredRequestsCustomizer( ServerProperties server, SecurityProperties security, ObjectProvider<ErrorController> errorController) { return new DefaultIgnoredRequestCustomizer(server, security, errorController.getIfAvailable()); } public static void configureHeaders(HeadersConfigurer<?> configurer, SecurityProperties.Headers headers) throws Exception { if (headers.getHsts() != Headers.HSTS.NONE) { boolean includeSubDomains = headers.getHsts() == Headers.HSTS.ALL; HstsHeaderWriter writer = new HstsHeaderWriter(includeSubDomains); writer.setRequestMatcher(AnyRequestMatcher.INSTANCE); configurer.addHeaderWriter(writer); } if (!headers.isContentType()) { configurer.contentTypeOptions().disable(); } if (StringUtils.hasText(headers.getContentSecurityPolicy())) { String policyDirectives = headers.getContentSecurityPolicy(); ContentSecurityPolicyMode mode = headers.getContentSecurityPolicyMode(); if (mode == ContentSecurityPolicyMode.DEFAULT) { configurer.contentSecurityPolicy(policyDirectives); } else { configurer.contentSecurityPolicy(policyDirectives).reportOnly(); } } if (!headers.isXss()) { configurer.xssProtection().disable(); } if (!headers.isCache()) { configurer.cacheControl().disable(); } if (!headers.isFrame()) { configurer.frameOptions().disable(); } } // Get the ignored paths in early @Order(SecurityProperties.IGNORED_ORDER) private static class IgnoredPathsWebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> { private final List<IgnoredRequestCustomizer> customizers; IgnoredPathsWebSecurityConfigurerAdapter( List<IgnoredRequestCustomizer> customizers) { this.customizers = customizers; } @Override public void configure(WebSecurity builder) throws Exception { } @Override public void init(WebSecurity builder) throws Exception { for (IgnoredRequestCustomizer customizer : this.customizers) { customizer.customize(builder.ignoring()); } } } private class DefaultIgnoredRequestCustomizer implements IgnoredRequestCustomizer { private final ServerProperties server; private final SecurityProperties security; private final ErrorController errorController; DefaultIgnoredRequestCustomizer(ServerProperties server, SecurityProperties security, ErrorController errorController) { this.server = server; this.security = security; this.errorController = errorController; } @Override public void customize(IgnoredRequestConfigurer configurer) { List<String> ignored = getIgnored(this.security); if (this.errorController != null) { ignored.add(normalizePath(this.errorController.getErrorPath())); } String[] paths = this.server.getServlet().getPathsArray(ignored); List<RequestMatcher> matchers = new ArrayList<>(); if (!ObjectUtils.isEmpty(paths)) { for (String pattern : paths) { matchers.add(new AntPathRequestMatcher(pattern, null)); } } if (!matchers.isEmpty()) { configurer.requestMatchers(new OrRequestMatcher(matchers)); } } private List<String> getIgnored(SecurityProperties security) { List<String> ignored = new ArrayList<>(security.getIgnored()); if (ignored.isEmpty()) { ignored.addAll(DEFAULT_IGNORED); } else if (ignored.contains("none")) { ignored.remove("none"); } return ignored; } private String normalizePath(String errorPath) { String result = StringUtils.cleanPath(errorPath); if (!result.startsWith("/")) { result = "/" + result; } return result; } } @Configuration @ConditionalOnProperty(prefix = "security.basic", name = "enabled", havingValue = "false") @Order(SecurityProperties.BASIC_AUTH_ORDER) protected static class ApplicationNoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.requestMatcher(new RequestMatcher() { @Override public boolean matches(HttpServletRequest request) { return false; } }); } } @Configuration @ConditionalOnProperty(prefix = "security.basic", name = "enabled", matchIfMissing = true) @Order(SecurityProperties.BASIC_AUTH_ORDER) protected static class ApplicationWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { private SecurityProperties security; protected ApplicationWebSecurityConfigurerAdapter(SecurityProperties security) { this.security = security; } @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; } } }