Summary

Error in serialization of non serializable SAML Requests prevents authentication if session is stored in database.

If spring-session-jdbc (= session is stored in database) is configured / enabled, an exception is thrown during issuing a SAML AuthN request. As per documentation, spring security saml2 stores the Saml2AuthenticationRequest (either Saml2PostAuthenticationRequest or Saml2RedirectAuthenticationRequest) in the session before actually sending them to the ipd (asserting party).

Now spring-session jdbc uses object serialization to store the session-data in the db. The Saml2AuthenticationRequests are not serializable and hence an exception is thrown, preventing successful authentication.

Actual Behavior

Exception is thrown at org.springframework.core.serializer.DefaultSerializer.serialize

Expected Behavior

There should be no issue with saml2 when using jdbc session.

Supposed Solutions: - make Saml2PostAuthenticationRequest and Saml2RedirectAuthenticationRequest serializable to allow default components to work - alternatively open to subclassing and/or provide usable public constructor or factory method. This could then be used in a custom Saml2AuthenticationRequestRepository

Configuration

spring boot with spring-session-jdbc spring-security-saml2

Version

spring boot 2.6.0 coming with spring-security-saml2-service-provider-5.6.0 and spring-session-jdbc-2.6.0

Comment From: matejschwartz

Same error using spring-session-data-redis:2.6.0, spring-boot:2.6.1, spring-security-saml2-service-provider:5.6.0

org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest]

Comment From: jzheaux

We can look into making these Serializable, though we'll likely need to wait until 5.7 since it will change the public API.

In the meantime, I believe you should be able to persist Saml2RedirectAuthenticationRequest's individual fields in the session, reconstructing the request when needed.

Comment From: bitrecycling

Thank you @jzheaux! This is excactly what I did. To deserialize I am using the factory/builder method that uses the "context" and then add the samlrequest String. But it didn't feel exactly right to do that after looking into the code that originally creates the Saml2RedirectAuthenticationRequest. But it works for now.

Is there already a timeline for 5.7?

Comment From: bameur

Hello @marcusdacoregio do you have a date for the correction?

Tks,

Comment From: marcusdacoregio

Hi @bameur, as @jzheaux mentioned we need to wait until 5.7. The first milestone is due by January 17, 2022, but you can use the SNAPSHOT version when we push the fix.

I'll try to fit it into my next tasks so we can have the snapshot as soon as possible.

Comment From: bameur

Thank you @marcusdacoregio for this good news. Good luck.

Comment From: Bas83

Glad I found an issue for this now. Also waiting for this, as it removes our possibility to upgrade to Spring Boot 2.6.x

Comment From: scottshidlovsky

Thank you @jzheaux! This is excactly what I did. To deserialize I am using the factory/builder method that uses the "context" and then add the samlrequest String. But it didn't feel exactly right to do that after looking into the code that originally creates the Saml2RedirectAuthenticationRequest. But it works for now.

Is there already a timeline for 5.7?

Are you able to share where you added this deserialization?

Comment From: bitrecycling

@scottshidlovsky short outline, lack of time, hope this helps:

  • implement a serializable class that has the same fields as the saml request: private String samlRequest; private String relayState; private String authenticationRequestUri; private String bindingUrn; private String sigAlg; private String signature; plus a useful constructor (e.g. saml request as param)

  • implement a custom version of Saml2AuthenticationRequestRepository that uses the class from above to map the original ("unserializable") request object to this and puts this in the session instead of the original. vice versa on loading.

  • wire the custom requestRepository using a bean named "authenticationRequestRepository"

Comment From: dymitrs

@bitrecycling @marcusdacoregio Hi there, just a question, is it even possible to achieve that? If you provide a custom Saml2AuthRequestRepository (according to docs it is possible by creating a bean) it won't be applied and used. Saml2 filters are using object of type HttpSessionSaml2AuthenticationRequestRepository. Saml2Configurer does not offer any override possibility. Am I missing something here?

Comment From: marcusdacoregio

Hi @dymitrs. Yes, if you provide a Bean of the type Saml2AuthenticationRequestRepository it will be applied in the filters. See here, where it retrieves the Bean if it exists, and here and here where the Configurer set it in the filters.

There is also a test that checks if the custom AuthnRepository is being used here.

If you believe that this is not working, please raise a ticket!

This commit https://github.com/spring-projects/spring-security/commit/861368bda5d1462e4fc7a07d7cd9f883af1bf7d5 made the Saml2AuthenticationRequests serializable. If you want to try it just use the 5.7.0-SNAPSHOT version of Spring Security.

Comment From: bitrecycling

@dymitrs I can confirm it works, just as @marcusdacoregio explained.

Comment From: dymitrs

Hi, you were right. Both ways work as expected :) Thank you very much for your help! I've encountered a problem with serialization (similar to this issue) with RelyingPartyRegistration but I used a custom serializer for Redis session with some additional logic. Maybe making RelyingPatryRegistration serializable is a way to go here also, I'll create an issue for this.

Comment From: tompson

we ran into this issue today and I am trying to implement the workaround or to use the SNAPSHOT version but both do not work for us because the session is always null in org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository#loadAuthenticationRequest

What could be the reason causing no session to be created?

Comment From: tompson

just to answer my own question: my problem was caused by the SameSite setting of the Spring Session cookie being Lax (https://groups.google.com/a/chromium.org/g/security-dev/c/AxY6BpkkH9U/m/vgKbDm7rFgAJ?pli=1)

I had to configure a custom cookieSerializer like described in https://github.com/spring-projects/spring-boot/issues/15047 to remove the SameSite property

Comment From: marcusdacoregio

I'm happy you figured it out @tompson

Comment From: tompson

had a discussion with my team about this, removing SameSite property from session cookie or setting it to none is not something we really want to do

how are you handling this? It seems with the default cookie of Spring Session loading the stored SAML request does not work - should I open a separate issue for this?

Comment From: bitrecycling

@tompson just to understand your scenario better, I assume you have a loadbalancer and behind that some spring backend nodes that "synchronize" the session using a jdbc-session, correct? And your client webapp (?) is served on a different host/domain and shall be able to use the api provided by the load-balanced spring backend?

Do you have the possibility to provide both from the same host, at least make it seem from the internet? This would definitely ease up the problems, considering that you cannot use SameSite none, which I guess is mandatory otherwise. Anyways I am also really interested in a solution for the described scenario. Btw. I am a regular listener of you podcast :)

Comment From: marcusdacoregio

Yes, please open a separate ticket for this since this problem is solved and we can keep the discussion focused

Comment From: tompson

I created a separate issue: https://github.com/spring-projects/spring-security/issues/10828

Comment From: tompson

@bitrecycling we are running a SaaS application behind a load balancer (traefik) using Spring Session to store the sessions in redis

we added the capability for our customers to configure their custom SAML provider (google, microsoft, okta, ...) to their accounts

this issue temporarily broke the whole SAML login flow but now that I understand that the whole saving/loading of the request is completely optional I implemented a dummy repository that does nothing and everything works as expected again.

After updating to Spring Security 5.7 I may be able to remove the dummy repository again, but loading the request will not work because of issue https://github.com/spring-projects/spring-security/issues/10828

Comment From: bitrecycling

@tompson thanks for the explanation!