To ensure backward compatibility, Security components that implement Serializable should have a serialVersionUID.
Based on internal testing across a few dozen JVMs, it appears that the serialVersionUID is consistent for Security's components. As such, we can safely add the calculated serialVersionUID value to each class that is missing it during the 6.4.x maintenance cycle.
- [x] #16274
- [x] Produce list of
Serializableclasses missingserialVersionUID - [ ] Fail build when a
Serializableclass is missing aserialVersionUID - [ ] Consider adding an exclusion list for classes we don't support being serialized
When addressing a class that is missing its serialVersionUID, please do the following:
- Add the calculated
serialVersionUID(IDEs can usually do this for you, or you can useserialverwhich ships with the JVM) - In
SpringSecurityCoreVersionSerializableTests, add the class and an example construction to thegeneratorByClassNamemap - Run
SpringSecurityCoreVersionSerializableTests#serializeCurrentVersionClasses. -
If successful, it will create a
{className}.serializedfile inconfig/src/main/resources/serialized:Run the other tests in
SpringSecurityCoreVersionSerializableTests; because it's new, the class will not be added to the list inshouldBeAbleToDeserializeClassFromPreviousVersion; however, the class should no longer be in the output forlistClassesMissingSerialVersionCommit the
Serialiizableclass(es) andSpringSecurityCoreVersionSerializableTests5. If unsuccessful, it is usually because one of its members is not serializable. Find the unserializable member; file a ticket to ensure that it is madeSerializable
Here are the classes:
- [x] org.springframework.security.cas.jackson2.CasJackson2Module
- [x] org.springframework.security.saml2.Saml2Exception
- [x] org.springframework.security.saml2.jackson2.Saml2Jackson2Module
- [x] org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException
- [x] ~~org.springframework.security.web.access.expression.WebExpressionConfigAttribute~~
- [x] org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException
- [x] org.springframework.security.web.authentication.rememberme.CookieTheftException
- [x] org.springframework.security.web.authentication.rememberme.InvalidCookieException
- [x] org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException
- [x] org.springframework.security.web.authentication.session.SessionAuthenticationException
- [x] org.springframework.security.web.authentication.session.SessionFixationProtectionEvent
- [x] org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent
- [x] org.springframework.security.web.authentication.www.NonceExpiredException
- [x] org.springframework.security.web.csrf.CsrfException
- [x] org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler$SupplierCsrfToken
- [x] org.springframework.security.web.csrf.DefaultCsrfToken
- [x] org.springframework.security.web.csrf.InvalidCsrfTokenException
- [x] org.springframework.security.web.csrf.LazyCsrfTokenRepository$SaveOnAccessCsrfToken
- [x] org.springframework.security.web.csrf.MissingCsrfTokenException
- [x] org.springframework.security.web.firewall.RequestRejectedException
- [x] org.springframework.security.web.jackson2.WebJackson2Module
- [x] org.springframework.security.web.jackson2.WebServletJackson2Module
- [x] org.springframework.security.web.savedrequest.SimpleSavedRequest
- [x] org.springframework.security.web.server.authentication.SwitchUserWebFilter$SwitchUserAuthenticationException
- [x] org.springframework.security.web.server.csrf.CsrfException
- [x] org.springframework.security.web.server.csrf.DefaultCsrfToken
- [x] org.springframework.security.web.server.firewall.ServerExchangeRejectedException
- [x] org.springframework.security.web.server.jackson2.WebServerJackson2Module
- [x] org.springframework.security.web.session.HttpSessionCreatedEvent
- [x] org.springframework.security.web.session.HttpSessionDestroyedEvent
- [x] org.springframework.security.web.session.HttpSessionIdChangedEvent
- [x] org.springframework.security.web.session.SessionInformationExpiredEvent
- [x] org.springframework.security.web.webauthn.authentication.WebAuthnAuthentication
- [ ] org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationRequestToken
- [x] org.springframework.security.web.webauthn.jackson.AttestationConveyancePreferenceSerializer
- [x] org.springframework.security.web.webauthn.jackson.AuthenticationExtensionsClientInputSerializer
- [x] org.springframework.security.web.webauthn.jackson.AuthenticationExtensionsClientInputsSerializer
- [x] org.springframework.security.web.webauthn.jackson.AuthenticationExtensionsClientOutputsDeserializer
- [x] org.springframework.security.web.webauthn.jackson.AuthenticatorAttachmentDeserializer
- [x] org.springframework.security.web.webauthn.jackson.AuthenticatorAttachmentSerializer
- [x] org.springframework.security.web.webauthn.jackson.AuthenticatorTransportDeserializer
- [x] org.springframework.security.web.webauthn.jackson.BytesSerializer
- [x] org.springframework.security.web.webauthn.jackson.COSEAlgorithmIdentifierDeserializer
- [x] org.springframework.security.web.webauthn.jackson.COSEAlgorithmIdentifierSerializer
- [x] org.springframework.security.web.webauthn.jackson.CredProtectAuthenticationExtensionsClientInputSerializer
- [x] org.springframework.security.web.webauthn.jackson.DurationSerializer
- [x] org.springframework.security.web.webauthn.jackson.PublicKeyCredentialTypeDeserializer
- [x] org.springframework.security.web.webauthn.jackson.PublicKeyCredentialTypeSerializer
- [x] org.springframework.security.web.webauthn.jackson.ResidentKeyRequirementSerializer
- [x] org.springframework.security.web.webauthn.jackson.UserVerificationRequirementSerializer
- [x] org.springframework.security.web.webauthn.jackson.WebauthnJackson2Module
- [x] org.springframework.security.oauth2.core.OAuth2AuthenticationException
- [x] org.springframework.security.oauth2.core.OAuth2AuthorizationException
- [x] org.springframework.security.access.AccessDeniedException
- [x] org.springframework.security.access.AuthorizationServiceException
- [x] org.springframework.security.access.SecurityConfig
- [x] org.springframework.security.access.annotation.Jsr250SecurityConfig
- [x] org.springframework.security.access.event.AuthenticationCredentialsNotFoundEvent
- [x] org.springframework.security.access.event.AuthorizationFailureEvent
- [x] org.springframework.security.access.event.AuthorizedEvent
- [x] org.springframework.security.access.event.PublicInvocationEvent
- [x] org.springframework.security.access.expression.method.PostInvocationExpressionAttribute
- [x] org.springframework.security.access.expression.method.PreInvocationExpressionAttribute
- [x] org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor
- [x] org.springframework.security.authentication.AccountExpiredException
- [x] org.springframework.security.authentication.AuthenticationCredentialsNotFoundException
- [x] org.springframework.security.authentication.AuthenticationServiceException
- [x] org.springframework.security.authentication.BadCredentialsException
- [x] org.springframework.security.authentication.CredentialsExpiredException
- [x] org.springframework.security.authentication.DisabledException
- [x] org.springframework.security.authentication.InsufficientAuthenticationException
- [x] org.springframework.security.authentication.InternalAuthenticationServiceException
- [x] org.springframework.security.authentication.LockedException
- [x] org.springframework.security.authentication.ProviderNotFoundException
- [x] org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent
- [x] org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent
- [x] org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent
- [x] org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent
- [x] org.springframework.security.authentication.event.AuthenticationFailureLockedEvent
- [x] org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent
- [x] org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent
- [x] org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent
- [x] org.springframework.security.authentication.event.AuthenticationSuccessEvent
- [x] org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent
- [x] org.springframework.security.authentication.event.LogoutSuccessEvent
- [x] org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent
- [x] org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent
- [x] org.springframework.security.authentication.ott.InvalidOneTimeTokenException
- [x] org.springframework.security.authentication.password.CompromisedPasswordException
- [x] org.springframework.security.authorization.AuthorizationDeniedException
- [x] org.springframework.security.authorization.event.AuthorizationDeniedEvent
- [x] org.springframework.security.authorization.event.AuthorizationEvent
- [x] org.springframework.security.authorization.event.AuthorizationGrantedEvent
- [x] org.springframework.security.core.ComparableVersion$ListItem
- [x] org.springframework.security.core.context.SecurityContextChangedEvent
- [x] org.springframework.security.core.context.TransientSecurityContext
- [x] org.springframework.security.core.session.AbstractSessionEvent
- [x] org.springframework.security.core.userdetails.UsernameNotFoundException
- [x] org.springframework.security.jackson2.CoreJackson2Module
- [x] org.springframework.security.jackson2.SecurityJackson2Modules$AllowlistTypeResolverBuilder
- [x] org.springframework.security.access.annotation.BusinessServiceImpl
- [x] org.springframework.security.access.annotation.ExpressionProtectedBusinessServiceImpl
- [x] org.springframework.security.access.annotation.Jsr250BusinessServiceImpl
- [x] org.springframework.security.crypto.codec.Base64$InvalidBase64CharacterException
- [x] org.springframework.security.ldap.authentication.ad.ActiveDirectoryAuthenticationException
- [x] org.springframework.security.ldap.jackson2.LdapJackson2Module
- [x] org.springframework.security.ldap.ppolicy.PasswordPolicyControl
- [x] org.springframework.security.ldap.ppolicy.PasswordPolicyException
- [x] org.springframework.security.ldap.ppolicy.PasswordPolicyResponseControl
- [x] org.springframework.security.messaging.access.expression.MessageExpressionConfigAttribute
- [x] org.springframework.security.oauth2.client.ClientAuthorizationException
- [x] org.springframework.security.oauth2.client.ClientAuthorizationRequiredException
- [x] org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module
- [x] org.springframework.security.oauth2.client.web.InvalidClientRegistrationIdException
- [x] org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter$OAuth2AuthorizationRequestException
- [x] org.springframework.security.oauth2.jwt.BadJwtException
- [x] org.springframework.security.oauth2.jwt.JwtDecoderInitializationException
- [x] org.springframework.security.oauth2.jwt.JwtEncodingException
- [x] org.springframework.security.oauth2.jwt.JwtException
- [x] org.springframework.security.oauth2.jwt.JwtValidationException
- [x] org.springframework.security.oauth2.server.resource.InvalidBearerTokenException
- [x] org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException
- [x] org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException
You can also see the list of Serializable files by running:
./gradlew :spring-security-config:test --tests "*MissingSerialVersion*" -Pserialization
Comment From: jzheaux
Here is an example: https://github.com/spring-projects/spring-security/commit/e3cd4339b2c5cb43f5cde41b7ecbd659a1d8bf59
Comment From: dalbani
Based on internal testing across a few dozen JVMs, it appears that the
serialVersionUIDis consistent for Security's components. As such, we can safely add the calculatedserialVersionUIDvalue to each class that is missing it during the 6.4.x maintenance cycle.
Upgrading my application storing sessions in database from Spring Boot 3.3.6 to Spring Boot 3.4.1 (i.e. Spring Security 6.4.2), I ran into this issue:
java.io.InvalidClassException: org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; local class incompatible: stream classdesc serialVersionUID = -4675866280835753141, local class serialVersionUID = -196018737016047617
So it looks like that the serialVersionUID that my JVM (RedHat's OpenJDK 21.0.5+10-LTS) calculated is different that yours, right?
I'm surprised that no one has been hit by the same issue 🤔
Comment From: jzheaux
Hi, @dalbani, thanks for the report. I'm a little surprised to see a difference as we internally tested several dozen JVMs to check the calculated value.
That may be why no one else has reported just yet.
We need to set a value at some point; otherwise folks will break every time one of these classes is edited. I'll take a look at the JVM you listed and get back to you.
Comment From: jzheaux
Here is my output from the latest from RedHat:
~/Downloads/java-21-openjdk-21.0.5.0.11-1.portable.jdk.x86_64/bin/serialver \
-classpath oauth2/oauth2-core/build/libs/spring-security-oauth2-core-6.4.2-SNAPSHOT.jar:core/build/libs/spring-security-core-6.4.2-SNAPSHOT.jar \
org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority
org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority: private static final long serialVersionUID = -4675866280835753141L;
This appears to agree with the value in OidcUserAuthority currently.
However, if I look at older versions:
Spring Security 6.2.7
~/Downloads/java-21-openjdk-21.0.5.0.11-1.portable.jdk.x86_64/bin/serialver \
-classpath oauth2/oauth2-core/build/libs/spring-security-oauth2-core-6.2.7-SNAPSHOT.jar:core/build/libs/spring-security-core-6.2.7-SNAPSHOT.jar \
org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority
org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority: private static final long serialVersionUID = -196018737016047617L;
Spring Security 6.3.0
~/Downloads/java-21-openjdk-21.0.5.0.11-1.portable.jdk.x86_64/bin/serialver \
-classpath oauth2/oauth2-core/build/libs/spring-security-oauth2-core-6.3.0-SNAPSHOT.jar:core/build/libs/spring-security-core-6.3.0-SNAPSHOT.jar \
org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority
org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority: private static final long serialVersionUID = -196018737016047617L;
Then I think it's a little clearer what's going on.
Because OidcUserAuthority changed its public API in 6.4, this resulted in a different calculated serialVersionUID. The local id you are seeing is likely from a serialized version of OidcUserAuthority from a previous minor version of Spring Security.
In other words, this is something you would have experienced when upgrading, independent of the commit that added serialVersionUID to this class. Once this ticket is complete, then deserializations across future minor versions of Spring Security will be much more stable.
As a whole, this is something that we are addressing in order to eliminate deserialization issues across minor versions in Spring Security. You can read https://github.com/spring-projects/spring-security/issues/16163 to learn about the issue that triggered the need for this specific ticket.
Comment From: dalbani
Thanks @jzheaux for your feedback.
The issue comes indeed from a different serialVersionUID value, when session objects originally serialized with Spring Security pre-6.4 are then read with Spring Security 6.4.
I was actually aware of the effort mentioned in https://docs.spring.io/spring-security/reference/6.3/whats-new.html#_passive_jdk_serialization_support — and I very much appreciate the improvement.
Though I'm not sure this "concern [has become] a thing of the past" as mentioned on https://spring.io/blog/2024/01/19/spring-security-6-3-adds-passive-jdk-serialization-deserialization-for. I mean, are all Java classes now covered? Won't there be new changes in say Spring Security 6.5?
I suppose I need to ask the people in charge of Spring Session, but what do think of introducing a behaviour in Spring Session that failing to deserialize a session automatically invalidates it? So that manually messing with sessions stored in database is not necessary anymore when doing "breaking" upgrades of Spring Security.
Comment From: jzheaux
You are correct that @dalbani was perhaps too confldent in hindsight. At the time, there was not an analysis performed to see which classes were missing an id.
I think this point in the description:
Fail build when a Serializable class is missing a serialVersionUID
Will go a long way to catching these before future releases.
Comment From: sn3jkeee
Experienced same issues as @dalbani mentioned, has to clear out Redis as it used by Spring Session. Quite unfortunate all users forced to logged out