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.

2020-01-28_11h50_39

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.