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!