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
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?
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);
}
}