Allow configuration of AuthenticationManagerResolver in saml2Login()
Fixes gh-7654 https://github.com/spring-projects/spring-security/issues/7654
This commit is contained in:
parent
b7eebabce6
commit
af415948b1
|
@ -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
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -43,10 +43,20 @@ 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) {
|
||||
|
|
Loading…
Reference in New Issue