Summary
Configure an invalid response URL at the identity provider (using nonconfigured registration id) a NullPointerException occurs during authentication process.
E.g. registrationId is okta (at your service provider) but at the identity provider the configured response URL is http://mydomain/login/saml2/sso/anythingElse.
The Saml2WebSsoAuthenticationFilter does not check if the requested "RelyingParty" exits. If the RelyingPartyRegistrationRepository return null, processing not stopped.
java.lang.NullPointerException: null
at org.springframework.security.saml2.provider.service.servlet.filter.Saml2Utils.getServiceProviderEntityId(Saml2Utils.java:86) ~[spring-security-saml2-service-provider-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter.attemptAuthentication(Saml2WebSsoAuthenticationFilter.java:81) ~[spring-security-saml2-service-provider-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
[..]
Expected Behavior
no NPE :-)
Version
5.2.1.RELEASE
Comment From: eleftherias
Thanks for the report @berschmoe. Could you please provide a minimal sample that reproduces this issue?
Comment From: berschmoe
It can be reproduce with a unit test or the SAML2 sample.
Unit Test
This test method (for Saml2WebSsoAuthenticationFilterTests) simulates the behavior. "CASE 2" throws the NullPointerException. The mocked RelyingPartyRegistrationRepository behaves the same as the implementation - if no entry exists, it returns null.
@Test
public void attemptAuthenticationWhenRegistrationIdExitsThenReturnAuthentication() {
AuthenticationManager authenticationManager = mock(AuthenticationManager.class);
given(authenticationManager.authenticate(any(Authentication.class))).willAnswer(a->a.getArgument(0));
given(repository.findByRegistrationId("idp-registration-id")).willReturn(mock(RelyingPartyRegistration.class));
filter = new Saml2WebSsoAuthenticationFilter(repository, "/some/other/path/{registrationId}");
filter.setAuthenticationManager(authenticationManager);
// CASE 1: registrationId exits
request.setPathInfo("/some/other/path/idp-registration-id");
request.setParameter("SAMLResponse", "response");
Assert.assertNotNull( filter.attemptAuthentication( request, response ) );
// CASE 2: registrationId not exits
request.setPathInfo("/some/other/path/notExits");
request.setParameter("SAMLResponse", "response");
Assert.assertNull( filter.attemptAuthentication( request, response ) );
}
Spring Boot 2.x Sample
To reproduce this behavior in "real life" you can use the Spring Boot 2.x Sample project (https://docs.spring.io/spring-security/site/docs/5.2.1.RELEASE/reference/htmlsingle/#samllogin-sample-boot) I used Okta as identity provider and change the application.yml file (saml2login project).
spring:
security:
saml2:
relyingparty:
registration:
okta:
signing:
credentials:
- private-key-location: "file:/saml/app.key"
certificate-location: "file:/saml/app.crt"
identityprovider:
verification:
credentials:
- certificate-location: "file:/saml/okta.cert"
entity-id: http://www.okta.com/exk28lgiqnHTxxxxxx
sso-url: https://dev.okta.com/app/myuser_app_1/exk28lgiqnHTxxxxxx/sso/saml
Now I setup up Okta and make a small spelling mistake - instead of okta I wrote otak.
Launch Spring Boot application, open http://localhost:8080 and you will be caught in an endless loop. The app redirect to Okta, Okta redirect to the app (with the wrong registration id), the NPE occurs. App redirect to Okta, Okta redirect ...
Comment From: eleftherias
Thanks for the report @berschmoe. It will now throw a Saml2AuthenticationException if the relying party registration is not found.