/* * * Copyright 2016 Netflix, Inc. * * 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 com.netflix.genie.web.security.saml; import com.google.common.collect.Lists; import com.netflix.genie.web.security.x509.X509UserDetailsService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.velocity.app.VelocityEngine; import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.xml.parse.ParserPool; import org.opensaml.xml.parse.StaticBasicParserPool; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.saml.SAMLAuthenticationProvider; import org.springframework.security.saml.SAMLBootstrap; import org.springframework.security.saml.SAMLDiscovery; import org.springframework.security.saml.SAMLEntryPoint; import org.springframework.security.saml.SAMLLogoutFilter; import org.springframework.security.saml.SAMLLogoutProcessingFilter; import org.springframework.security.saml.SAMLProcessingFilter; import org.springframework.security.saml.SAMLWebSSOHoKProcessingFilter; import org.springframework.security.saml.context.SAMLContextProviderImpl; import org.springframework.security.saml.context.SAMLContextProviderLB; import org.springframework.security.saml.key.JKSKeyManager; import org.springframework.security.saml.key.KeyManager; import org.springframework.security.saml.log.SAMLDefaultLogger; import org.springframework.security.saml.metadata.CachingMetadataManager; import org.springframework.security.saml.metadata.ExtendedMetadata; import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; import org.springframework.security.saml.metadata.MetadataDisplayFilter; import org.springframework.security.saml.metadata.MetadataGenerator; import org.springframework.security.saml.metadata.MetadataGeneratorFilter; import org.springframework.security.saml.parser.ParserPoolHolder; import org.springframework.security.saml.processor.HTTPArtifactBinding; import org.springframework.security.saml.processor.HTTPPAOS11Binding; import org.springframework.security.saml.processor.HTTPPostBinding; import org.springframework.security.saml.processor.HTTPRedirectDeflateBinding; import org.springframework.security.saml.processor.HTTPSOAP11Binding; import org.springframework.security.saml.processor.SAMLBinding; import org.springframework.security.saml.processor.SAMLProcessorImpl; import org.springframework.security.saml.userdetails.SAMLUserDetailsService; import org.springframework.security.saml.util.VelocityFactory; import org.springframework.security.saml.websso.ArtifactResolutionProfile; import org.springframework.security.saml.websso.ArtifactResolutionProfileImpl; import org.springframework.security.saml.websso.SingleLogoutProfile; import org.springframework.security.saml.websso.SingleLogoutProfileImpl; import org.springframework.security.saml.websso.WebSSOProfile; import org.springframework.security.saml.websso.WebSSOProfileConsumer; import org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl; import org.springframework.security.saml.websso.WebSSOProfileConsumerImpl; import org.springframework.security.saml.websso.WebSSOProfileECPImpl; import org.springframework.security.saml.websso.WebSSOProfileImpl; import org.springframework.security.saml.websso.WebSSOProfileOptions; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.channel.ChannelProcessingFilter; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Timer; /** * Security configuration for SAML based authentication. * <p> * Modified from: https://github.com/vdenotaris/spring-boot-security-saml-sample which is basically a port of the * context-xml from Spring SAML example. * * @author tgianos * @since 3.0.0 */ @ConditionalOnProperty("genie.security.saml.enabled") @Configuration @Order(5) //@EnableGlobalMethodSecurity(securedEnabled = true) @Slf4j public class SAMLConfig extends WebSecurityConfigurerAdapter { @Autowired private ResourceLoader resourceLoader; @Autowired private X509UserDetailsService x509UserDetailsService; @Autowired private SAMLProperties samlProperties; /** * Initialization of OpenSAML library. * * @return the OpenSAML bootstrap object * @see SAMLBootstrap */ @Bean public static SAMLBootstrap samlBootstrap() { return new SAMLBootstrap(); } /** * Initialize the velocity engine. * * @return The velocity engine. * @see VelocityEngine */ @Bean public VelocityEngine velocityEngine() { return VelocityFactory.getEngine(); } /** * Parser pool used for the OpenSAML parsing. * * @return The parser pool * @see StaticBasicParserPool */ @Bean(initMethod = "initialize") public StaticBasicParserPool parserPool() { return new StaticBasicParserPool(); } /** * The holder for the parser poole. * * @return The parser pool holder * @see ParserPoolHolder */ @Bean(name = "parserPoolHolder") public ParserPoolHolder parserPoolHolder() { return new ParserPoolHolder(); } /** * Connection pool for the HTTP Client. * * @return a Multithreaded connection manager * @see MultiThreadedHttpConnectionManager */ @Bean public MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager() { return new MultiThreadedHttpConnectionManager(); } /** * The HTTP Client used to communicate with the IDP. * * @return The http client * @see HttpClient */ @Bean public HttpClient httpClient() { return new HttpClient(multiThreadedHttpConnectionManager()); } /** * Parses the response SAML messages. * * @param samlUserDetailsService The user details service to use * @return The SAML authentication provider * @see SAMLAuthenticationProvider */ @Bean public SAMLAuthenticationProvider samlAuthenticationProvider(final SAMLUserDetailsService samlUserDetailsService) { final SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider(); samlAuthenticationProvider.setUserDetails(samlUserDetailsService); samlAuthenticationProvider.setForcePrincipalAsString(false); return samlAuthenticationProvider; } /** * Provider of the SAML context. * * @param properties The SAML properties to use * @return the context provider implementation * @see SAMLContextProviderImpl */ @Bean public SAMLContextProviderImpl contextProvider(final SAMLProperties properties) { if (properties.getLoadBalancer() != null) { log.info("Using SAMLContextProviderLB implementation of SAMLContextProvider for context provider bean."); final SAMLContextProviderLB lb = new SAMLContextProviderLB(); final SAMLProperties.LoadBalancer lbProps = properties.getLoadBalancer(); final String scheme = lbProps.getScheme(); log.info("Setting the load balancer scheme to {}", scheme); lb.setScheme(scheme); final String serverName = lbProps.getServerName(); log.info("Setting the load balancer server name to {}", serverName); lb.setServerName(serverName); final String contextPath = lbProps.getContextPath(); log.info("Setting the load balancer context path to {}", contextPath); lb.setContextPath(contextPath); final int serverPort = lbProps.getServerPort(); log.info("Setting the load balancer port to {}", serverPort); lb.setServerPort(serverPort); final boolean includeServerPort = lbProps.isIncludeServerPortInRequestURL(); log.info("Setting whether to include the server port in the request URL to {}", includeServerPort); lb.setIncludeServerPortInRequestURL(includeServerPort); return lb; } else { log.info("Using SAMLContextProviderImpl implementation of SAMLContextProvider for context provider bean."); return new SAMLContextProviderImpl(); } } /** * The Logger used by the SAML package. * * @return the logger * @see SAMLDefaultLogger */ @Bean public SAMLDefaultLogger samlLogger() { return new SAMLDefaultLogger(); } /** * SAML 2.0 WebSSO Assertion Consumer. * * @return The consumer * @see WebSSOProfileConsumer * @see WebSSOProfileConsumerImpl */ @Bean public WebSSOProfileConsumer webSSOprofileConsumer() { return new WebSSOProfileConsumerImpl(); } /** * SAML 2.0 Holder-of-Key WebSSO Assertion Consumer. * * @return The holder of key consumer implementation * @see WebSSOProfileConsumerHoKImpl */ @Bean public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() { return new WebSSOProfileConsumerHoKImpl(); } /** * SAML 2.0 Web SSO profile. * * @return The web profile * @see WebSSOProfile * @see WebSSOProfileImpl */ @Bean public WebSSOProfile webSSOprofile() { return new WebSSOProfileImpl(); } /** * SAML 2.0 Holder-of-Key Web SSO profile. * * @return the HoK profile * @see WebSSOProfileConsumerHoKImpl */ @Bean public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() { return new WebSSOProfileConsumerHoKImpl(); } /** * SAML 2.0 ECP profile. * * @return The ECP profile * @see WebSSOProfileECPImpl */ @Bean public WebSSOProfileECPImpl ecpprofile() { return new WebSSOProfileECPImpl(); } /** * The logout profile for SAML single logout. * * @return The logout profile * @see SingleLogoutProfile * @see SingleLogoutProfileImpl */ @Bean public SingleLogoutProfile logoutProfile() { return new SingleLogoutProfileImpl(); } /** * Central storage of cryptographic keys. * * @return The key manager used for everything * @see KeyManager */ @Bean public KeyManager keyManager() { final Resource storeFile = this.resourceLoader.getResource("classpath:" + this.samlProperties.getKeystore().getName()); final Map<String, String> passwords = new HashMap<>(); passwords.put( this.samlProperties.getKeystore().getDefaultKey().getName(), this.samlProperties.getKeystore().getDefaultKey().getPassword() ); return new JKSKeyManager( storeFile, this.samlProperties.getKeystore().getPassword(), passwords, this.samlProperties.getKeystore().getDefaultKey().getName() ); } /** * The Web SSO profile options. * * @return The profile options for web sso * @see WebSSOProfileOptions */ @Bean public WebSSOProfileOptions defaultWebSSOProfileOptions() { final WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions(); webSSOProfileOptions.setIncludeScoping(false); return webSSOProfileOptions; } /** * Entry point to initialize authentication, default values taken from properties file. * * @return The SAML entry point * @see SAMLEntryPoint */ @Bean public SAMLEntryPoint samlEntryPoint() { final SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint(); samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions()); return samlEntryPoint; } /** * Setup the extended metadata for the SAML request. * * @return The extended metadata * @see ExtendedMetadata */ @Bean public ExtendedMetadata extendedMetadata() { return new ExtendedMetadata(); } /** * Setup the IDP discovery service. * * @return The discovery service * @see SAMLDiscovery */ @Bean public SAMLDiscovery samlIDPDiscovery() { return new SAMLDiscovery(); } /** * Setup the extended metadata delegate for the IDP. * * @param properties The SAML properties * @return The sso circle of trust metadata provider configured via the url. * @throws MetadataProviderException On any configuration error * @see ExtendedMetadataDelegate * @see HTTPMetadataProvider */ @Bean @Qualifier("idp-ssocircle") public ExtendedMetadataDelegate ssoCircleExtendedMetadataProvider( final SAMLProperties properties ) throws MetadataProviderException { // Create a daemon timer for updating the IDP metadata from the server final Timer backgroundTaskTimer = new Timer(true); final HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider( backgroundTaskTimer, httpClient(), properties.getIdp().getServiceProviderMetadataURL() ); httpMetadataProvider.setParserPool(parserPool()); final ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(httpMetadataProvider, extendedMetadata()); extendedMetadataDelegate.setMetadataTrustCheck(true); extendedMetadataDelegate.setMetadataRequireSignature(false); return extendedMetadataDelegate; } /** * Get the metadata manager for the IDP metadata. This version caches locally and refreshes periodically. * * @param ssoCircleExtendedMetadataProvider The extended metadata delegate * @return The metadata manager * @throws MetadataProviderException on any configuration error * @see CachingMetadataManager */ @Bean @Qualifier("metadata") public CachingMetadataManager metadata( final ExtendedMetadataDelegate ssoCircleExtendedMetadataProvider ) throws MetadataProviderException { return new CachingMetadataManager(Lists.newArrayList(ssoCircleExtendedMetadataProvider)); } /** * Generates default SP metadata if none is set. * * @return The metadata generator filter * @see MetadataGenerator */ @Bean public MetadataGenerator metadataGenerator() { final MetadataGenerator metadataGenerator = new MetadataGenerator(); metadataGenerator.setEntityId(this.samlProperties.getSp().getEntityId()); metadataGenerator.setExtendedMetadata(extendedMetadata()); metadataGenerator.setIncludeDiscoveryExtension(false); metadataGenerator.setKeyManager(keyManager()); if (this.samlProperties.getSp().getEntityBaseURL() != null) { metadataGenerator.setEntityBaseURL(this.samlProperties.getSp().getEntityBaseURL()); } return metadataGenerator; } /** * The filter is waiting for connections on URL suffixed with filterSuffix and presents SP metadata there. * * @return the filter that will display the SP information * @see MetadataDisplayFilter */ @Bean public MetadataDisplayFilter metadataDisplayFilter() { return new MetadataDisplayFilter(); } /** * Handler deciding where to redirect user after successful login. * * @return The login success handler * @see SavedRequestAwareAuthenticationSuccessHandler */ @Bean public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() { return new SavedRequestAwareAuthenticationSuccessHandler(); } /** * Handler deciding where to redirect user after failed login. * * @return Authentication failure handler * @see SimpleUrlAuthenticationFailureHandler */ @Bean public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() { final SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler(); failureHandler.setUseForward(true); failureHandler.setDefaultFailureUrl("/error"); return failureHandler; } /** * Filter to process holder of key sso requests. * * @return The filter * @throws Exception For any configuration error * @see SAMLWebSSOHoKProcessingFilter */ @Bean public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception { final SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter(); samlWebSSOHoKProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler()); samlWebSSOHoKProcessingFilter.setAuthenticationManager(authenticationManager()); samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler()); return samlWebSSOHoKProcessingFilter; } /** * Processing filter for WebSSO profile messages. * * @return The SAML processing filter * @throws Exception on any configuration error * @see SAMLProcessingFilter */ @Bean public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception { final SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter(); samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager()); samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler()); samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler()); return samlWebSSOProcessingFilter; } /** * The metadata generator filter which generates metadata for the SP if non is pre-configured. * * @return The metadata generator filter */ @Bean public MetadataGeneratorFilter metadataGeneratorFilter() { return new MetadataGeneratorFilter(metadataGenerator()); } /** * Handler for successful logout. * * @return the logout location to redirect the user to after they logout * @see SimpleUrlLogoutSuccessHandler */ @Bean public SimpleUrlLogoutSuccessHandler successLogoutHandler() { final SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler(); //TODO: Change this location as it looks like could send into a circular loop successLogoutHandler.setDefaultTargetUrl("/"); return successLogoutHandler; } /** * Logout handler terminating local session. * * @return The security context logout handler * @see SecurityContextLogoutHandler */ @Bean public SecurityContextLogoutHandler logoutHandler() { final SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler(); logoutHandler.setInvalidateHttpSession(true); logoutHandler.setClearAuthentication(true); return logoutHandler; } /** * Filter to handle logout requests. * * @return The filter * @see SAMLLogoutProcessingFilter */ @Bean public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() { return new SAMLLogoutProcessingFilter(successLogoutHandler(), logoutHandler()); } /** * Overrides default logout processing filter with the one processing SAML messages. * * @return The logout filter * @see SAMLLogoutFilter */ @Bean public SAMLLogoutFilter samlLogoutFilter() { return new SAMLLogoutFilter( successLogoutHandler(), new LogoutHandler[]{logoutHandler()}, new LogoutHandler[]{logoutHandler()} ); } // Bindings private ArtifactResolutionProfile artifactResolutionProfile() { final ArtifactResolutionProfileImpl artifactResolutionProfile = new ArtifactResolutionProfileImpl(httpClient()); artifactResolutionProfile.setProcessor(new SAMLProcessorImpl(soapBinding())); return artifactResolutionProfile; } /** * HTTP Artifact binding. * * @param parserPool The parser pool to use * @param velocityEngine The velocity engine to use * @return The artifact binding * @see HTTPArtifactBinding */ @Bean public HTTPArtifactBinding artifactBinding(final ParserPool parserPool, final VelocityEngine velocityEngine) { return new HTTPArtifactBinding(parserPool, velocityEngine, artifactResolutionProfile()); } /** * A SOAP binding to use. * * @return a SOAP binding * @see HTTPSOAP11Binding */ @Bean public HTTPSOAP11Binding soapBinding() { return new HTTPSOAP11Binding(parserPool()); } /** * A HTTP POST binding to use. * * @return The post binding * @see HTTPPostBinding */ @Bean public HTTPPostBinding httpPostBinding() { return new HTTPPostBinding(parserPool(), velocityEngine()); } /** * A HTTP redirect binding to use. * * @return The redirect binding * @see HTTPRedirectDeflateBinding */ @Bean public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() { return new HTTPRedirectDeflateBinding(parserPool()); } /** * A SOAP binding to use. * * @return a SOAP binding * @see HTTPSOAP11Binding */ @Bean public HTTPSOAP11Binding httpSOAP11Binding() { return new HTTPSOAP11Binding(parserPool()); } /** * A PAOS binding to use. * * @return The PAOS binding * @see HTTPPAOS11Binding */ @Bean public HTTPPAOS11Binding httpPAOS11Binding() { return new HTTPPAOS11Binding(parserPool()); } /** * The SAML processor that includes bindings for various communication protocols with the IDP. * * @return The saml processor * @see SAMLProcessorImpl */ @Bean public SAMLProcessorImpl processor() { final List<SAMLBinding> bindings = Lists.newArrayList( httpRedirectDeflateBinding(), httpPostBinding(), artifactBinding(parserPool(), velocityEngine()), httpSOAP11Binding(), httpPAOS11Binding() ); return new SAMLProcessorImpl(bindings); } /** * Define the security filter chain in order to support SSO Auth by using SAML 2.0. * * @return Filter chain proxy * @throws Exception on any configuration problem * @see FilterChainProxy */ @Bean public FilterChainProxy samlFilter() throws Exception { final List<SecurityFilterChain> chains = new ArrayList<>(); chains.add( new DefaultSecurityFilterChain( new AntPathRequestMatcher("/saml/login/**"), samlEntryPoint() ) ); chains.add( new DefaultSecurityFilterChain( new AntPathRequestMatcher("/saml/logout/**"), samlLogoutFilter() ) ); chains.add( new DefaultSecurityFilterChain( new AntPathRequestMatcher("/saml/metadata/**"), metadataDisplayFilter() ) ); chains.add( new DefaultSecurityFilterChain( new AntPathRequestMatcher("/saml/SSO/**"), samlWebSSOProcessingFilter() ) ); chains.add( new DefaultSecurityFilterChain( new AntPathRequestMatcher("/saml/SSOHoK/**"), samlWebSSOHoKProcessingFilter() ) ); chains.add( new DefaultSecurityFilterChain( new AntPathRequestMatcher("/saml/SingleLogout/**"), samlLogoutProcessingFilter() ) ); chains.add( new DefaultSecurityFilterChain( new AntPathRequestMatcher("/saml/discovery/**"), samlIDPDiscovery() ) ); return new FilterChainProxy(chains); } /** * Defines the web based security configuration. * * @param http It allows configuring web based security for specific http requests. * @throws Exception on any error */ @Override protected void configure(final HttpSecurity http) throws Exception { // @formatter:off http .httpBasic() .authenticationEntryPoint(samlEntryPoint()); http .csrf() .disable(); http .addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class) .addFilterAfter(samlFilter(), BasicAuthenticationFilter.class); http .antMatcher("/**") .authorizeRequests() .antMatchers("/actuator/**").permitAll() .antMatchers("/api/**").permitAll() .antMatchers("/error").permitAll() .antMatchers("/saml/**").permitAll() .anyRequest().authenticated() .and() .x509().authenticationUserDetailsService(this.x509UserDetailsService); http .logout() .logoutSuccessUrl("/"); // @formatter:on } protected void setSamlProperties(final SAMLProperties samlProperties) { this.samlProperties = samlProperties; } protected void setResourceLoader(final ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } }