Expected Behavior

SAML Single Logout should work even if the principal does not implement the Saml2AuthenticatedPrincipal interface.

Current Behavior

The following parts of SAML Single Logout only work as expected if the principal implements Saml2AuthenticatedPrincipal:

  • Populating the NameID in OpenSamlLogoutRequestResolver
  • expects Authentication.getName() to return the NameID value from the SAML Assertion
  • Populating the SessionIndex list in OpenSamlLogoutRequestResolver
  • expects the principal to implement Saml2AuthenticatedPrincipal
  • Extracting the RelyingPartyRegistrationId from the Authentication in OpenSamlLogoutRequestResolver, OpenSamlLogoutResponseResolver and Saml2LogoutRequestFilter
  • expects the principal to implement Saml2AuthenticatedPrincipal
  • if it does not, the RelyingPartyRegistrationId must be in the request URL, at least when using DefaultRelyingPartyRegistrationResolver
  • there is some duplicate code in these three classes
  • Request matching for LogoutFilter in Saml2LogoutConfigurer.Saml2RequestMatcher
  • expects the principal to implement Saml2AuthenticatedPrincipal

All these don't work if the responseAuthenticationConverter in OpenSaml(4)AuthenticationProvider is customized and the principal does not implement Saml2AuthenticatedPrincipal.

It would be great if SAML Single Logout could be configured to work with any principal. This could be achieved using yet another resolver interface - passing HttpServletRequest and Authentication as parameters should be sufficient. The implementations of this resolver interface could then be made to match the responseAuthenticationConverter.

Context

In some cases, this issue can be solved by making the principal implement Saml2AuthenticatedPrincipal. Unfortunately, this does not work in my specific case, because the principal is created and used in code that doesn't use Spring Security.

Comment From: jzheaux

Hi, @chschu, thanks for the suggestions.

It would be great if SAML Single Logout could be configured to work with any principal. This could be achieved using yet another resolver interface - passing HttpServletRequest and Authentication as parameters should be sufficient.

Isn't this what Saml2LogoutResponseResolver already is? You could be able to provide your own implementation of this.

In some cases, this issue can be solved by making the principal implement Saml2AuthenticatedPrincipal. Unfortunately, this does not work in my specific case, because the principal is created and used in code that doesn't use Spring Security.

I'm not seeing why this is a problem. Can you clarify why you can't provide a custom responseAuthenticationConverter that constructs an adapter that extends your custom datatype and implements Saml2AuthenticatedPrincipal?

Comment From: chschu

Thanks for your quick response, @jzheaux.

TL;DR: It's all solvable, but the suggested resolver would simplify things a lot.

Isn't this what Saml2LogoutResponseResolver already is? You could be able to provide your own implementation of this.

You are right, and that's what I'm currently doing. It all depends on what the responseAuthenticationConverter stuffs into the Authentication.

The full "workaround" involves creating a Saml2Authentication (using the default converter created by OpenSaml4AuthenticationProvider.createDefaultResponseAuthenticationConverter()) and putting it inside my Authentication. That happens in my custom responseAuthenticationConverter in the authentication process.

During SLO, my custom Saml2LogoutRequestResolver/Saml2LogoutResponseResolver extract the Saml2Authentication from my Authentication and delegate to OpenSaml4LogoutRequestResolver/OpenSaml4LogoutResponseResolver. These generate exactly what I need, as long as I pass them the Saml2Authentication.

The request matching for the LogoutFilter was solvable using an ObjectPostProcessor that creates the LogoutFilter from scratch. That's because I need to use a RequestMatcher that is consistent with the one in LogoutConfigurer. Unlike the LogoutSuccessHandler and the LogoutHandlers, the RequestMatcher is not currently taken from there by Saml2LogoutConfigurer.

I'm not seeing why this is a problem. Can you clarify why you can't provide a custom responseAuthenticationConverter that constructs an adapter that extends your custom datatype and implements Saml2AuthenticatedPrincipal?

That was my first thought as well, and it works as long as you know what the principal actually is. Unfortunately, I do not know the runtime type of the principal. I just get that thing from one service, and I have to pass it to other services later on. So while I could easily subclass the declared type of the principal, subclassing the runtime type is a whole different story.

Comment From: jzheaux

the suggested resolver would simplify things a lot

I'm happy to take a look at a PR to clear up any ambiguity with what you are proposing. That said, I'm hesitant to merge a new interface that has the same contract as Saml2LogoutResponseResolver.

Unfortunately, I do not know the runtime type of the principal. I just get that thing from one service, and I have to pass it to other services later on. So while I could easily subclass the declared type of the principal, subclassing the runtime type is a whole different story.

Why don't you know the runtime type? If it returns an interface with multiple implementations, you could create some kind of delegate class that implements this interface and Saml2AuthenticatedPrincipal. You could add a getDelegate if getting the concrete instance is important. Or perhaps you can have multiple adapters, one for each concrete implementation and do an instanceof check.

Otherwise, I think your wrapping solution sounds quite reasonable. This is ultimately the adapter approach I mentioned, just with the extra flexibility that knowing the type is not needed.

LogoutFilter

If you want to pursue the idea of simplifying how SAML Logout and generic logout get the same request matcher, I'd be happy to discuss. Would you open a new ticket to track that since it probably affects more than just those who are wanting to not use Saml2AuthenticatedPrincipal?

Comment From: chschu

Created #10821 for the request matcher part.

I'll try to provide a PR for the resolver part.

Comment From: chschu

Sorry for the delay - I've been quite busy the last few weeks.

I'm planning to implement a Converter<Authentication, Optional<Saml2AuthenticatedPrincipal>> with a default implementation that returns authentication.getPrincipal() if it is a Saml2AuthenticatedPrincipal.

A custom implementation of the converter can then create/obtain the Saml2AuthenticatedPrincipal however it is possible. In my specific case I could extract it from the Saml2Authentication I'm already storing in my own Authentication.

Comment From: chschu

Eventually, I chose a less intrusive approach that solves my issue - see PR #11338.

Implementing a Spring Security interface on the Authentication is a lot easier than somehow extending the authenticated principal Object in my specific case.

Comment From: OrangeDog

Extending your own principal object to implement Saml2AuthenticatedPrincipal can indeed be an issue, especially if it's a JPA entity.

Comment From: jzheaux

Closing in favor of https://github.com/spring-projects/spring-security/pull/11338