Closed EmanuelCozariz closed 1 year ago
Hi, @EmanuelCozariz, are you able to compare your configuration with the one present in this sample?
It would also be useful to add logging.level.org.springframework.security=TRACE
to your application.properties
and check the console.
After doing those steps, is it still a problem? Can you provide a minimal, reproducible sample where we can check? You can use the same Okta URLs from the sample that I linked above.
Still a problem yes, even after i've applied your above suggestions. Full configuration class sample:
@EnableWebSecurity
@Slf4j
@Configuration
public class obfuscate_this_SAML2Configuration
{
public static final String obfuscate_this__REG_ID = "obfuscate_this_RegId";
@Bean
public Saml2SecurityConfiguration authenticationConfiguration(final ConfigurationSectionsRepository confSecRepo)
{
return Saml2SecurityConfiguration.build(confSecRepo);
}
@Bean
public RelyingPartyRegistrationResolver relyingPartyRegistrationResolver(
final RelyingPartyRegistrationRepository relyingPartyRegistrationRepository)
{
return new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository);
}
@Bean
public obfuscate_this_Resolver openSaml3LogoutRequestResolver(final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver)
{
return new obfuscate_this_Resolver(new OpenSaml3LogoutRequestResolver(relyingPartyRegistrationResolver));
}
@Bean
public SecurityFilterChain filterChain(final HttpSecurity http,
final OpenSamlAuthenticationProvider obfuscate_this_SamlAuthenticationProvider,
final RelyingPartyRegistrationRepository relyingPartyRegistrationRepository,
final obfuscate_this_AuthenticationTokenConverter saml2AuthenticationTokenConverter,
final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver,
final obfuscate_this_Resolver openSaml3LogoutRequestResolver) throws Exception
{
final Saml2MetadataFilter saml2MetadataFilter = new Saml2MetadataFilter(relyingPartyRegistrationResolver,
new OpenSamlMetadataResolver());
saml2MetadataFilter.setRequestMatcher(new AntPathRequestMatcher("/saml2/service-provider-metadata/{registrationId}"));
final Saml2RelyingPartyInitiatedLogoutSuccessHandler logoutHandler = new Saml2RelyingPartyInitiatedLogoutSuccessHandler(
openSaml3LogoutRequestResolver);
final RelyingPartyRegistration.AssertingPartyDetails idpDetails = relyingPartyRegistrationRepository
.findByRegistrationId(obfuscate_this__REG_ID).getAssertingPartyDetails();
http.authorizeHttpRequests().antMatchers("/version.txt").permitAll() //
.and().authorizeHttpRequests().antMatchers("/**").authenticated() //
.and().authorizeHttpRequests().antMatchers("/saml2/service-provider-metadata/{registrationId}").permitAll() //
.and().authorizeHttpRequests().anyRequest().authenticated() //
.and().csrf().disable() //
.saml2Login(saml2 -> saml2.relyingPartyRegistrationRepository(relyingPartyRegistrationRepository) //
.authenticationConverter(saml2AuthenticationTokenConverter)
.authenticationManager(new ProviderManager(obfuscate_this_SamlAuthenticationProvider))) //
.saml2Logout(Customizer.withDefaults()) //
.addFilterBefore(saml2MetadataFilter, Saml2WebSsoAuthenticationFilter.class)
.logout(logout -> logout.logoutUrl("/saml/logout"));
return http.build();
}
@Bean
public Saml2LogoutResponseResolver logoutResponseResolver(final RelyingPartyRegistrationResolver registrationResolver)
{
final OpenSaml3LogoutResponseResolver logoutRequestResolver = new OpenSaml3LogoutResponseResolver(registrationResolver);
logoutRequestResolver.setParametersConsumer(
parameters -> parameters.getLogoutResponse().getStatus().getStatusCode().setValue(StatusCode.SUCCESS));
return logoutRequestResolver;
}
@Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository(
final Saml2SecurityConfiguration authenticationConfiguration)
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException
{
final RelyingPartyRegistration.Builder relyingPartyRegistrationBuilder = RelyingPartyRegistrations //
.fromMetadataLocation(authenticationConfiguration.getIdentityProviderMetadataFilePath()) //
.singleLogoutServiceLocation("https://aaaa.obfuscate_this_.com/adfs/ls/") //
.singleLogoutServiceResponseLocation("{baseUrl}/logout/saml2/slo")
.singleLogoutServiceBinding(Saml2MessageBinding.POST)//
.registrationId(obfuscate_this__REG_ID);
final KeyStore keyStore = authenticationConfiguration.getKeyStore();
if (keyStore != null) {
final PrivateKey key = (PrivateKey) keyStore.getKey(authenticationConfiguration.getKeyStoreSaml2KeyAlias(),
authenticationConfiguration.getKeystoreSaml2KeyPasswordAsText().toCharArray());
final X509Certificate cert = (X509Certificate) keyStore
.getCertificate(authenticationConfiguration.getKeyStoreSaml2KeyAlias());
relyingPartyRegistrationBuilder.signingX509Credentials(c -> c.add(Saml2X509Credential.signing(key, cert))) //
.decryptionX509Credentials(c -> c.add(Saml2X509Credential.decryption(key, cert)));
}
return new InMemoryRelyingPartyRegistrationRepository(relyingPartyRegistrationBuilder.build());
}
@Bean
public OpenSamlAuthenticationProvider obfuscate_this_SamlAuthenticationProvider(final LdapRolesExtractor ldapRolesExtractor,
final obfuscate_this_Resolver openSaml3LogoutRequestResolver)
{
final OpenSamlAuthenticationProvider obfuscate_this_SamlAuthenticationProvider = new OpenSamlAuthenticationProvider();
obfuscate_this_SamlAuthenticationProvider.setResponseAuthenticationConverter(responseToken -> {
final Assertion assertion = responseToken.getResponse().getAssertions().get(0);
final String username = assertion.getSubject().getNameID().getValue();
final List<String> attributes = assertion.getAttributeStatements().stream().flatMap(a -> a.getAttributes().stream()) //
.flatMap(a -> a.getAttributeValues().stream()).filter(XSAnyImpl.class::isInstance)
.map(a -> ((XSAnyImpl) a).getTextContent()).collect(Collectors.toList());
final List<SimpleGrantedAuthority> grantedAuthorities = ldapRolesExtractor.extractRolesFromLdapDNs(username,
attributes);
final obfuscate_this_SAML2AuthenticationToken authToken = new obfuscate_this_SAML2AuthenticationToken(username, grantedAuthorities);
openSaml3LogoutRequestResolver.setAuthentication(
OpenSamlAuthenticationProvider.createDefaultResponseAuthenticationConverter().convert(responseToken));
return authToken;
});
return obfuscate_this_SamlAuthenticationProvider;
}
@Bean
obfuscate_this_AuthenticationTokenConverter saml2AuthenticationTokenConverter(
final RelyingPartyRegistrationRepository relyingPartyRegistrationRepository)
{
final RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(
relyingPartyRegistrationRepository);
final Saml2AuthenticationTokenConverter saml2AuthenticationTokenConverter = new Saml2AuthenticationTokenConverter(
relyingPartyRegistrationResolver);
return new obfuscate_this_AuthenticationTokenConverter(saml2AuthenticationTokenConverter, relyingPartyRegistrationResolver);
}
}
My question would be: where in the spring saml implementation, does this happen: "spring sends a LogoutRequest, from service provider to IDP"
I would then debug and see calling class hierarchy, then I would understand why this request is not sent, by service provider.
If you do not change the URL in Saml2LogoutConfigurer#logoutUrl
, Spring Security will create a logout filter for SAML 2.0 Relying Party Initiated Logout and place it in the filter chain. Using the default configuration, I'd expect that a POST /logout
would hit that filter.
Right, that just customizes the logout endpoint. Fine, I removed the line logout(logout -> logout.logoutUrl("/saml/logout")); So now logout is on /logout
But that does not make spring send the logout request to ADFS. Spring logout only happens on spring side, not on Identity provider side:
Previous saml spring implementation had these 2 filters that would send a request to IDP: SAMLLogoutFilter - sends request to IDP SAMLLogoutProcessingFilter - processes response from IDP
What is the equivalent of SAMLLogoutFilter in the new spring version?
Resolution: new spring saml version expects a POST /logout. I was sending a GET. The configuration is done in Saml2LogoutConfigurer createRelyingPartyLogoutFilter The createLogoutMatcher hardcodes the httpMethod, i don't think there is a way to change this value.
Solution: I needed to create my own filter, and add it to the httpSecurity object: addFilterBefore(createCustomRelyingPartyLogoutFilter(...)) - this will accept a GET /logout
Can I ask you how do you handle global signOut initiated from the IDP party and from seperate http session?
Resolution: new spring saml version expects a POST /logout. I was sending a GET. The configuration is done in Saml2LogoutConfigurer createRelyingPartyLogoutFilter The createLogoutMatcher hardcodes the httpMethod, i don't think there is a way to change this value.
Solution: I needed to create my own filter, and add it to the httpSecurity object: addFilterBefore(createCustomRelyingPartyLogoutFilter(...)) - this will accept a GET /logout
did this solution work? it seems like Saml2RelyingPartyInitiatedLogoutFilter is a private class and cannot be overrided/configured from an external class
I'm using spring security saml2, version 5.7.6, ADFS as Identity Provider According to spring documentation, upon normal Spring /logout, initiated by the user, spring is supposed to send a LogoutRequest, from service provider to IDP
But the logout request sent to ADFS Identity Provider is not sent. Moreover, Saml2LogoutRequest is only created by the OpenSaml3LogoutRequestResolver -> that is in turn invoked by the Saml2RelyingPartyInitiatedLogoutSuccessHandler Do we need to add this Saml2RelyingPartyInitiatedLogoutSuccessHandler to the standard spring logout filter?
Configuration, according to spring documentation:
Relying party registration is configured like this: