Search code examples
spring-securityspring-saml

SLO fails when SP has timed out


Using Spring Security SAML 1.0.10, when a SAML session has timed out on the SP, but is still active on the IdP, an SLO attempt fails.

How to reproduce

  1. Set a session timeout to one minute (server.servlet.session = 1m)
  2. Login on your SP
  3. Login on a different secondary SP - it must use SSO
  4. Wait a minute for the session to timeout
  5. Make a lockout on the secondary SP

This causes a ClassCastException in Spring since SecurityContextHolder.getContext().getAuthentication() contains an AnonymousAuthenticationToken which does not contain credentials. It also breaks the filter chain and a LogoutResponse is never send to the IdP and an error page is shown in the browser.

java.lang.ClassCastException: class java.lang.String cannot be cast to class org.springframework.security.saml.SAMLCredential (java.lang.String is in module java.base of loader 'bootstrap'; org.springframework.security.saml.SAMLCredential is in unnamed module of loader 'app')
at org.springframework.security.saml.SAMLLogoutProcessingFilter.processLogout(SAMLLogoutProcessingFilter.java:172)

Is this intended, is it a bug, or have I misconfigured something? In case of it being intended or a bug, do there exists a workaround?


Solution

  • The error occurs in SAMLLogoutProcessingFilter.processLogout() where the following code causes the ClassCastException. In the case a user session has timed out auth.getCredentials() returns an empty string.

    SAMLCredential credential = null;
    if (auth != null) {
        credential = (SAMLCredential)auth.getCredentials();
    }
    

    The only workaround for this I found was to create a LogoutFilter dealing with the problem.

    I created a new LogoutFilter extending SAMLLogoutProcessingFilter. In the case of a LogoutRequest and a AnonymousAuthenticationToken I created a LogoutResponse. In any other case I forwarded to SAMLLogoutProcessingFilter or called a copy of a method from SAMLLogoutProcessingFilter, where I have removed the part failing.

      private void overwrittenLogout(HttpServletRequest request, HttpServletResponse response, SAMLMessageContext context)
          throws ServletException, SAMLException, MetadataProviderException, MessageEncodingException
      {  
          var auth = SecurityContextHolder.getContext().getAuthentication();
          if (auth instanceof AnonymousAuthenticationToken) {
              logoutProfile.sendLogoutResponse(context, StatusCode.SUCCESS_URI, null);
          } else if (auth != null) {
              followNormalProcedure(request, response, auth, context);
          }
      }