Allow configuration of AuthenticationManagerResolver in saml2Login()

Fixes gh-7654

https://github.com/spring-projects/spring-security/issues/7654
This commit is contained in:
Filip Hanik 2019-12-02 03:24:28 -08:00
parent b7eebabce6
commit af415948b1
7 changed files with 578 additions and 21 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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