Browse Source

Allow configuration of AuthenticationManagerResolver in saml2Login()

Fixes gh-7654

https://github.com/spring-projects/spring-security/issues/7654
Filip Hanik 5 years ago
parent
commit
af415948b1

+ 32 - 8
config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java

@@ -18,7 +18,7 @@ package org.springframework.security.config.annotation.web.configurers.saml2;
 
 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.context.ApplicationContext;
-import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
@@ -103,6 +103,25 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>> extend
 
 	private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
 
+	private AuthenticationManager authenticationManager;
+
+	private Saml2WebSsoAuthenticationFilter saml2WebSsoAuthenticationFilter;
+
+	/**
+	 * Allows a configuration of a {@link AuthenticationManager} to be used during SAML 2 authentication.
+	 * If none is specified, the system will create one inject it into the {@link Saml2WebSsoAuthenticationFilter}
+	 * @param authenticationManager the authentication manager to be used
+	 * @return the {@link Saml2LoginConfigurer} for further configuration
+	 * @throws IllegalArgumentException if authenticationManager is null
+	 * 									configure the default manager
+	 * @since 5.3
+	 */
+	public Saml2LoginConfigurer<B> authenticationManager(AuthenticationManager authenticationManager) {
+		Assert.notNull(authenticationManager, "authenticationManager cannot be null");
+		this.authenticationManager = authenticationManager;
+		return this;
+	}
+
 	/**
 	 * Sets the {@code RelyingPartyRegistrationRepository} of relying parties, each party representing a
 	 * service provider, SP and this host, and identity provider, IDP pair that communicate with each other.
@@ -164,11 +183,11 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>> extend
 			this.relyingPartyRegistrationRepository = getSharedOrBean(http, RelyingPartyRegistrationRepository.class);
 		}
 
-		Saml2WebSsoAuthenticationFilter webSsoFilter = new Saml2WebSsoAuthenticationFilter(
+		saml2WebSsoAuthenticationFilter = new Saml2WebSsoAuthenticationFilter(
 				this.relyingPartyRegistrationRepository,
 				this.loginProcessingUrl
 		);
-		setAuthenticationFilter(webSsoFilter);
+		setAuthenticationFilter(saml2WebSsoAuthenticationFilter);
 		super.loginProcessingUrl(this.loginProcessingUrl);
 
 		if (hasText(this.loginPage)) {
@@ -197,7 +216,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>> extend
 				super.init(http);
 			}
 		}
-		http.authenticationProvider(getAuthenticationProvider());
+
 		this.initDefaultLoginFilter(http);
 	}
 
@@ -211,11 +230,17 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>> extend
 	public void configure(B http) throws Exception {
 		http.addFilter(this.authenticationRequestEndpoint.build(http));
 		super.configure(http);
+		if (this.authenticationManager == null) {
+			registerDefaultAuthenticationProvider(http);
+		}
+		else {
+			saml2WebSsoAuthenticationFilter.setAuthenticationManager(this.authenticationManager);
+		}
 	}
 
-	private AuthenticationProvider getAuthenticationProvider() {
-		AuthenticationProvider provider = new OpenSamlAuthenticationProvider();
-		return postProcess(provider);
+	private void registerDefaultAuthenticationProvider(B http) {
+		OpenSamlAuthenticationProvider provider = postProcess(new OpenSamlAuthenticationProvider());
+		http.authenticationProvider(provider);
 	}
 
 	private void registerDefaultCsrfOverride(B http) {
@@ -313,5 +338,4 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>> extend
 		}
 	}
 
-
 }

+ 262 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java

@@ -0,0 +1,262 @@
+/*
+ * Copyright 2002-2019 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
+ *
+ *      https://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.security.config.annotation.web.configurers.saml2;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.mock.web.MockFilterChain;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.authentication.ProviderManager;
+import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.test.SpringTestRule;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
+import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider;
+import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
+import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
+import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
+import org.springframework.security.web.FilterChainProxy;
+import org.springframework.security.web.context.HttpRequestResponseHolder;
+import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
+import org.springframework.security.web.context.SecurityContextRepository;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.Collections;
+import javax.servlet.ServletException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.codehaus.groovy.runtime.InvokerHelper.asList;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.springframework.security.config.annotation.web.configurers.saml2.TestRelyingPartyRegistrations.saml2AuthenticationConfiguration;
+
+/**
+ * Tests for different Java configuration for {@link Saml2LoginConfigurer}
+ */
+public class Saml2LoginConfigurerTests {
+
+	private static final Converter<Assertion, Collection<? extends GrantedAuthority>>
+			AUTHORITIES_EXTRACTOR = a -> asList(new SimpleGrantedAuthority("TEST"));
+	private static final GrantedAuthoritiesMapper AUTHORITIES_MAPPER =
+			authorities -> asList(new SimpleGrantedAuthority("TEST CONVERTED"));
+	private static final Duration RESPONSE_TIME_VALIDATION_SKEW = Duration.ZERO;
+
+	@Autowired
+	private ConfigurableApplicationContext context;
+
+	@Autowired
+	private FilterChainProxy springSecurityFilterChain;
+
+	@Autowired
+	private RelyingPartyRegistrationRepository repository;
+
+	@Autowired
+	SecurityContextRepository securityContextRepository;
+
+	@Rule
+	public final SpringTestRule spring = new SpringTestRule();
+
+	@Autowired(required = false)
+	MockMvc mvc;
+
+	private MockHttpServletRequest request;
+	private MockHttpServletResponse response;
+	private MockFilterChain filterChain;
+
+	@Before
+	public void setup() {
+		this.request = new MockHttpServletRequest("POST", "");
+		this.request.setServletPath("/login/saml2/sso/test-rp");
+		this.response = new MockHttpServletResponse();
+		this.filterChain = new MockFilterChain();
+	}
+
+	@After
+	public void cleanup() {
+		if (this.context != null) {
+			this.context.close();
+		}
+	}
+
+	@Test
+	public void saml2LoginWhenConfiguringAuthenticationManagerThenTheManagerIsUsed() throws Exception {
+		// setup application context
+		this.spring.register(Saml2LoginConfigWithCustomAuthenticationManager.class).autowire();
+		performSaml2Login("ROLE_AUTH_MANAGER");
+	}
+
+	@Test
+	public void saml2LoginWhenConfiguringAuthenticationDefaultsUsingCustomizerThenTheProviderIsConfigured() throws Exception {
+		// setup application context
+		this.spring.register(Saml2LoginConfigWithAuthenticationDefaultsWithPostProcessor.class).autowire();
+		validateSaml2WebSsoAuthenticationFilterConfiguration();
+	}
+
+	private void validateSaml2WebSsoAuthenticationFilterConfiguration() {
+		// get the OpenSamlAuthenticationProvider
+		Saml2WebSsoAuthenticationFilter filter = getSaml2SsoFilter(this.springSecurityFilterChain);
+		AuthenticationManager manager =
+				(AuthenticationManager) ReflectionTestUtils.getField(filter, "authenticationManager");
+		ProviderManager pm = (ProviderManager) manager;
+		AuthenticationProvider provider = pm.getProviders()
+				.stream()
+				.filter(p -> p instanceof OpenSamlAuthenticationProvider)
+				.findFirst()
+				.get();
+		Assert.assertSame(AUTHORITIES_EXTRACTOR, ReflectionTestUtils.getField(provider, "authoritiesExtractor"));
+		Assert.assertSame(AUTHORITIES_MAPPER, ReflectionTestUtils.getField(provider, "authoritiesMapper"));
+		Assert.assertSame(RESPONSE_TIME_VALIDATION_SKEW, ReflectionTestUtils.getField(provider, "responseTimeValidationSkew"));
+	}
+
+	private Saml2WebSsoAuthenticationFilter getSaml2SsoFilter(FilterChainProxy chain) {
+		return (Saml2WebSsoAuthenticationFilter) chain.getFilters("/login/saml2/sso/test")
+				.stream()
+				.filter(f -> f instanceof Saml2WebSsoAuthenticationFilter)
+				.findFirst()
+				.get();
+	}
+
+	private void performSaml2Login(String expected) throws IOException, ServletException {
+		// setup authentication parameters
+		this.request.setParameter(
+				"SAMLResponse",
+				Base64.getEncoder().encodeToString(
+						"saml2-xml-response-object".getBytes()
+				)
+		);
+
+
+		// perform test
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
+
+		// assertions
+		Authentication authentication = this.securityContextRepository
+				.loadContext(new HttpRequestResponseHolder(this.request, this.response))
+				.getAuthentication();
+		Assert.assertNotNull("Expected a valid authentication object.", authentication);
+		assertThat(authentication.getAuthorities()).hasSize(1);
+		assertThat(authentication.getAuthorities()).first()
+				.isInstanceOf(SimpleGrantedAuthority.class).hasToString(expected);
+	}
+
+
+	@EnableWebSecurity
+	@Import(Saml2LoginConfigBeans.class)
+	static class Saml2LoginConfigWithCustomAuthenticationManager extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			http.saml2Login()
+				.authenticationManager(
+						getAuthenticationManagerMock("ROLE_AUTH_MANAGER")
+				);
+			super.configure(http);
+		}
+	}
+
+	@EnableWebSecurity
+	@Import(Saml2LoginConfigBeans.class)
+	static class Saml2LoginConfigWithAuthenticationDefaultsWithPostProcessor extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			ObjectPostProcessor<OpenSamlAuthenticationProvider> processor
+					= new ObjectPostProcessor<OpenSamlAuthenticationProvider>() {
+				@Override
+				public <O extends OpenSamlAuthenticationProvider> O postProcess(O provider) {
+					provider.setResponseTimeValidationSkew(RESPONSE_TIME_VALIDATION_SKEW);
+					provider.setAuthoritiesMapper(AUTHORITIES_MAPPER);
+					provider.setAuthoritiesExtractor(AUTHORITIES_EXTRACTOR);
+					return provider;
+				}
+			};
+
+			http.saml2Login()
+					.addObjectPostProcessor(processor)
+			;
+			super.configure(http);
+		}
+	}
+
+	private static AuthenticationManager getAuthenticationManagerMock(String role) {
+		return new AuthenticationManager() {
+
+			@Override
+			public Authentication authenticate(Authentication authentication)
+					throws AuthenticationException {
+				if (!supports(authentication.getClass())) {
+					throw new AuthenticationServiceException("not supported");
+				}
+				return new Saml2Authentication(
+						() -> "auth principal",
+						"saml2 response",
+						Collections.singletonList(
+								new SimpleGrantedAuthority(role)
+						)
+				);
+			}
+
+			public boolean supports(Class<?> authentication) {
+				return authentication.isAssignableFrom(Saml2AuthenticationToken.class);
+			}
+		};
+	}
+
+	static class Saml2LoginConfigBeans {
+
+		@Bean
+		SecurityContextRepository securityContextRepository() {
+			return new HttpSessionSecurityContextRepository();
+		}
+
+		@Bean
+		RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
+			RelyingPartyRegistrationRepository repository = mock(RelyingPartyRegistrationRepository.class);
+			when(repository.findByRegistrationId(anyString())).thenReturn(
+					saml2AuthenticationConfiguration()
+			);
+			return repository;
+		}
+	}
+
+}

+ 56 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/TestRelyingPartyRegistrations.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2019 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
+ *
+ *      https://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.security.config.annotation.web.configurers.saml2;
+
+import org.springframework.security.saml2.credentials.Saml2X509Credential;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
+import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
+
+import static org.springframework.security.config.annotation.web.configurers.saml2.TestSaml2Credentials.signingCredential;
+import static org.springframework.security.config.annotation.web.configurers.saml2.TestSaml2Credentials.verificationCertificate;
+
+/**
+ * Preconfigured test data for {@link RelyingPartyRegistration} objects
+ */
+public class TestRelyingPartyRegistrations {
+
+	static RelyingPartyRegistration saml2AuthenticationConfiguration() {
+		//remote IDP entity ID
+		String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php";
+		//remote WebSSO Endpoint - Where to Send AuthNRequests to
+		String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php";
+		//local registration ID
+		String registrationId = "simplesamlphp";
+		//local entity ID - autogenerated based on URL
+		String localEntityIdTemplate = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
+		//local signing (and decryption key)
+		Saml2X509Credential signingCredential = signingCredential();
+		//IDP certificate for verification of incoming messages
+		Saml2X509Credential idpVerificationCertificate = verificationCertificate();
+		String acsUrlTemplate = "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
+		return RelyingPartyRegistration.withRegistrationId(registrationId)
+				.remoteIdpEntityId(idpEntityId)
+				.idpWebSsoUrl(webSsoEndpoint)
+				.credentials(c -> c.add(signingCredential))
+				.credentials(c -> c.add(idpVerificationCertificate))
+				.localEntityIdTemplate(localEntityIdTemplate)
+				.assertionConsumerServiceUrlTemplate(acsUrlTemplate)
+				.build();
+	}
+
+
+}

+ 116 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/TestSaml2Credentials.java

@@ -0,0 +1,116 @@
+/*
+ * Copyright 2002-2019 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
+ *
+ *      https://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.security.config.annotation.web.configurers.saml2;
+
+import org.springframework.security.converter.RsaKeyConverters;
+import org.springframework.security.saml2.credentials.Saml2X509Credential;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.PrivateKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.DECRYPTION;
+import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.SIGNING;
+import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.VERIFICATION;
+
+/**
+ * Preconfigured SAML credentials for SAML integration tests.
+ */
+public class TestSaml2Credentials {
+
+	static Saml2X509Credential verificationCertificate() {
+		String certificate = "-----BEGIN CERTIFICATE-----\n" +
+				"MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD\n" +
+				"VQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYD\n" +
+				"VQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwX\n" +
+				"c2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0Bw\n" +
+				"aXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJ\n" +
+				"BgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAa\n" +
+				"BgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQD\n" +
+				"DBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlr\n" +
+				"QHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62\n" +
+				"E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz\n" +
+				"2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWW\n" +
+				"RDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQ\n" +
+				"nX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5\n" +
+				"cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gph\n" +
+				"iJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5\n" +
+				"ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTAD\n" +
+				"AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduO\n" +
+				"nRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+v\n" +
+				"ZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLu\n" +
+				"xbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6z\n" +
+				"V9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3\n" +
+				"lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" +
+				"-----END CERTIFICATE-----";
+		return new Saml2X509Credential(
+				x509Certificate(certificate),
+				VERIFICATION
+		);
+	}
+
+	static X509Certificate x509Certificate(String source) {
+		try {
+			final CertificateFactory factory = CertificateFactory.getInstance("X.509");
+			return (X509Certificate) factory.generateCertificate(
+					new ByteArrayInputStream(source.getBytes(StandardCharsets.UTF_8))
+			);
+		} catch (Exception e) {
+			throw new IllegalArgumentException(e);
+		}
+	}
+
+	static Saml2X509Credential signingCredential() {
+		String key = "-----BEGIN PRIVATE KEY-----\n" +
+				"MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANG7v8QjQGU3MwQE\n" +
+				"VUBxvH6Uuiy/MhZT7TV0ZNjyAF2ExA1gpn3aUxx6jYK5UnrpxRRE/KbeLucYbOhK\n" +
+				"cDECt77Rggz5TStrOta0BQTvfluRyoQtmQ5Nkt6Vqg7O2ZapFt7k64Sal7AftzH6\n" +
+				"Q2BxWN1y04bLdDrH4jipqRj/2qEFAgMBAAECgYEAj4ExY1jjdN3iEDuOwXuRB+Nn\n" +
+				"x7pC4TgntE2huzdKvLJdGvIouTArce8A6JM5NlTBvm69mMepvAHgcsiMH1zGr5J5\n" +
+				"wJz23mGOyhM1veON41/DJTVG+cxq4soUZhdYy3bpOuXGMAaJ8QLMbQQoivllNihd\n" +
+				"vwH0rNSK8LTYWWPZYIECQQDxct+TFX1VsQ1eo41K0T4fu2rWUaxlvjUGhK6HxTmY\n" +
+				"8OMJptunGRJL1CUjIb45Uz7SP8TPz5FwhXWsLfS182kRAkEA3l+Qd9C9gdpUh1uX\n" +
+				"oPSNIxn5hFUrSTW1EwP9QH9vhwb5Vr8Jrd5ei678WYDLjUcx648RjkjhU9jSMzIx\n" +
+				"EGvYtQJBAMm/i9NR7IVyyNIgZUpz5q4LI21rl1r4gUQuD8vA36zM81i4ROeuCly0\n" +
+				"KkfdxR4PUfnKcQCX11YnHjk9uTFj75ECQEFY/gBnxDjzqyF35hAzrYIiMPQVfznt\n" +
+				"YX/sDTE2AdVBVGaMj1Cb51bPHnNC6Q5kXKQnj/YrLqRQND09Q7ParX0CQQC5NxZr\n" +
+				"9jKqhHj8yQD6PlXTsY4Occ7DH6/IoDenfdEVD5qlet0zmd50HatN2Jiqm5ubN7CM\n" +
+				"INrtuLp4YHbgk1mi\n" +
+				"-----END PRIVATE KEY-----";
+		String certificate = "-----BEGIN CERTIFICATE-----\n" +
+				"MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBhMC\n" +
+				"VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcMCVZhbmNvdXZlcjEdMBsG\n" +
+				"A1UECgwUU3ByaW5nIFNlY3VyaXR5IFNBTUwxCzAJBgNVBAsMAnNwMSAwHgYDVQQD\n" +
+				"DBdzcC5zcHJpbmcuc2VjdXJpdHkuc2FtbDAeFw0xODA1MTQxNDMwNDRaFw0yODA1\n" +
+				"MTExNDMwNDRaMIGEMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjES\n" +
+				"MBAGA1UEBwwJVmFuY291dmVyMR0wGwYDVQQKDBRTcHJpbmcgU2VjdXJpdHkgU0FN\n" +
+				"TDELMAkGA1UECwwCc3AxIDAeBgNVBAMMF3NwLnNwcmluZy5zZWN1cml0eS5zYW1s\n" +
+				"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRu7/EI0BlNzMEBFVAcbx+lLos\n" +
+				"vzIWU+01dGTY8gBdhMQNYKZ92lMceo2CuVJ66cUURPym3i7nGGzoSnAxAre+0YIM\n" +
+				"+U0razrWtAUE735bkcqELZkOTZLelaoOztmWqRbe5OuEmpewH7cx+kNgcVjdctOG\n" +
+				"y3Q6x+I4qakY/9qhBQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAAeViTvHOyQopWEi\n" +
+				"XOfI2Z9eukwrSknDwq/zscR0YxwwqDBMt/QdAODfSwAfnciiYLkmEjlozWRtOeN+\n" +
+				"qK7UFgP1bRl5qksrYX5S0z2iGJh0GvonLUt3e20Ssfl5tTEDDnAEUMLfBkyaxEHD\n" +
+				"RZ/nbTJ7VTeZOSyRoVn5XHhpuJ0B\n" +
+				"-----END CERTIFICATE-----";
+		PrivateKey pk = RsaKeyConverters.pkcs8().convert(new ByteArrayInputStream(key.getBytes()));
+		X509Certificate cert = x509Certificate(certificate);
+		return new Saml2X509Credential(pk, cert, SIGNING, DECRYPTION);
+	}
+}

+ 90 - 0
docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc

@@ -223,6 +223,96 @@ public interface Saml2AuthenticationRequestFactory {
 }
 ----
 
+=== Customizing Authentication Logic
+
+By default Spring Security configures the `OpenSamlAuthenticationProvider`
+to validate and parse the SAML 2 response and assertions that are received.
+This provider has three configuration options
+
+1. An authorities extractor - extract group information from the assertion
+2. An authorities mapper - map extracted group information to internal authorities
+3. Response time validation duration - the built in tolerances for timestamp validation
+should be used when there may be a time synchronization issue.
+
+One customization strategy is to use an `ObjectPostProcessor`, which allows you to modify the
+objects created by the implementation. Another option is to override the authentication
+manager for the filter that intercepts the SAMLResponse.
+
+==== OpenSamlAuthenticationProvider ObjectPostProcessor
+
+[source,java]
+----
+@EnableWebSecurity
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        ObjectPostProcessor<OpenSamlAuthenticationProvider> processor = new ObjectPostProcessor<>() {
+            @Override
+            public <O extends OpenSamlAuthenticationProvider> O postProcess(O provider) {
+                provider.setResponseTimeValidationSkew(RESPONSE_TIME_VALIDATION_SKEW);
+                provider.setAuthoritiesMapper(AUTHORITIES_MAPPER);
+                provider.setAuthoritiesExtractor(AUTHORITIES_EXTRACTOR);
+                return provider;
+            }
+        };
+
+        http
+            .saml2Login()
+            .addObjectPostProcessor(processor)
+        ;
+        super.configure(http);
+    }
+}
+----
+
+==== Configure OpenSamlAuthenticationProvider as an Authentication Manager
+We can leverage the same method, `authenticationManager`, to override and customize the default
+`OpenSamlAuthenticationProvider`.
+
+[source,java]
+----
+@EnableWebSecurity
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        OpenSamlAuthenticationProvider authProvider = new OpenSamlAuthenticationProvider();
+        authProvider.setResponseTimeValidationSkew(RESPONSE_TIME_VALIDATION_SKEW);
+        authProvider.setAuthoritiesMapper(AUTHORITIES_MAPPER);
+        authProvider.setAuthoritiesExtractor(AUTHORITIES_EXTRACTOR);
+        http
+            .saml2Login()
+            .authenticationManager(new ProviderManager(asList(authProvider)))
+        ;
+        super.configure(http);
+    }
+}
+----
+
+==== Custom Authentication Manager
+The authentication manager for the security filter can also be overwritten, using your own
+custom `AuthenticationManager` implementation.
+This authentication manager should expect a `Saml2AuthenticationToken` object
+containing the SAML 2 Response XML data.
+
+[source,java]
+----
+@EnableWebSecurity
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        AuthenticationManager authenticationManager = new MySaml2AuthenticationManager(...);
+        http
+            .saml2Login()
+            .authenticationManager(authenticationManager)
+        ;
+        super.configure(http);
+    }
+}
+----
+
 [[samllogin-sample-boot]]
 === Spring Boot 2.x Sample
 

+ 10 - 11
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java

@@ -15,17 +15,6 @@
  */
 package org.springframework.security.saml2.provider.service.authentication;
 
-import org.springframework.core.convert.converter.Converter;
-import org.springframework.security.authentication.AuthenticationProvider;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
-import org.springframework.security.saml2.Saml2Exception;
-import org.springframework.security.saml2.credentials.Saml2X509Credential;
-import org.springframework.util.Assert;
-
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.opensaml.saml.common.SignableSAMLObject;
@@ -61,6 +50,16 @@ import org.opensaml.xmlsec.signature.support.SignaturePrevalidator;
 import org.opensaml.xmlsec.signature.support.SignatureTrustEngine;
 import org.opensaml.xmlsec.signature.support.SignatureValidator;
 import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
+import org.springframework.security.saml2.Saml2Exception;
+import org.springframework.security.saml2.credentials.Saml2X509Credential;
+import org.springframework.util.Assert;
 
 import java.security.cert.X509Certificate;
 import java.time.Duration;

+ 12 - 2
saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java

@@ -43,13 +43,23 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce
 	private final RequestMatcher matcher;
 	private final RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
 
+	/**
+	 * Creates a {@code Saml2WebSsoAuthenticationFilter} authentication filter that is configured
+	 * to use the {@link #DEFAULT_FILTER_PROCESSES_URI} processing URL
+	 * @param relyingPartyRegistrationRepository - repository of configured SAML 2 entities. Required.
+	 */
 	public Saml2WebSsoAuthenticationFilter(RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
 		this(relyingPartyRegistrationRepository, DEFAULT_FILTER_PROCESSES_URI);
 	}
 
+	/**
+	 * Creates a {@code Saml2WebSsoAuthenticationFilter} authentication filter
+	 * @param relyingPartyRegistrationRepository - repository of configured SAML 2 entities. Required.
+	 * @param filterProcessesUrl the processing URL, must contain a {registrationId} variable. Required.
+	 */
 	public Saml2WebSsoAuthenticationFilter(
-			RelyingPartyRegistrationRepository relyingPartyRegistrationRepository,
-			String filterProcessesUrl) {
+				RelyingPartyRegistrationRepository relyingPartyRegistrationRepository,
+				String filterProcessesUrl) {
 		super(filterProcessesUrl);
 		Assert.notNull(relyingPartyRegistrationRepository, "relyingPartyRegistrationRepository cannot be null");
 		Assert.hasText(filterProcessesUrl, "filterProcessesUrl must contain a URL pattern");