Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure Serializable Security Components declare serialVersionUID #16276

Open
jzheaux opened this issue Dec 13, 2024 · 6 comments
Open

Ensure Serializable Security Components declare serialVersionUID #16276

jzheaux opened this issue Dec 13, 2024 · 6 comments
Assignees
Labels
in: core An issue in spring-security-core type: bug A general bug
Milestone

Comments

@jzheaux
Copy link
Contributor

jzheaux commented Dec 13, 2024

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.

When addressing a class that is missing its serialVersionUID, please do the following:

  1. Add the calculated serialVersionUID (IDEs can usually do this for you, or you can use serialver which ships with the JVM)

  2. In SpringSecurityCoreVersionSerializableTests, add the class and an example construction to the generatorByClassName map

  3. Run SpringSecurityCoreVersionSerializableTests#serializeCurrentVersionClasses.

  4. If successful, it will create a {className}.serialized file in config/src/main/resources/serialized:

    Run the other tests in SpringSecurityCoreVersionSerializableTests; because it's new, the class will not be added to the list in shouldBeAbleToDeserializeClassFromPreviousVersion; however, the class should no longer be in the output for listClassesMissingSerialVersion

    Commit the Serialiizable class(es) and SpringSecurityCoreVersionSerializableTests

  5. 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 made Serializable

Here are the classes:

  • org.springframework.security.cas.jackson2.CasJackson2Module
  • org.springframework.security.saml2.Saml2Exception
  • org.springframework.security.saml2.jackson2.Saml2Jackson2Module
  • org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException
  • org.springframework.security.web.access.expression.WebExpressionConfigAttribute
  • org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException
  • org.springframework.security.web.authentication.rememberme.CookieTheftException
  • org.springframework.security.web.authentication.rememberme.InvalidCookieException
  • org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException
  • org.springframework.security.web.authentication.session.SessionAuthenticationException
  • org.springframework.security.web.authentication.session.SessionFixationProtectionEvent
  • org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent
  • org.springframework.security.web.authentication.www.NonceExpiredException
  • org.springframework.security.web.csrf.CsrfException
  • org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler$SupplierCsrfToken
  • org.springframework.security.web.csrf.DefaultCsrfToken
  • org.springframework.security.web.csrf.InvalidCsrfTokenException
  • org.springframework.security.web.csrf.LazyCsrfTokenRepository$SaveOnAccessCsrfToken
  • org.springframework.security.web.csrf.MissingCsrfTokenException
  • org.springframework.security.web.firewall.RequestRejectedException
  • org.springframework.security.web.jackson2.WebJackson2Module
  • org.springframework.security.web.jackson2.WebServletJackson2Module
  • org.springframework.security.web.savedrequest.SimpleSavedRequest
  • org.springframework.security.web.server.authentication.SwitchUserWebFilter$SwitchUserAuthenticationException
  • org.springframework.security.web.server.csrf.CsrfException
  • org.springframework.security.web.server.csrf.DefaultCsrfToken
  • org.springframework.security.web.server.firewall.ServerExchangeRejectedException
  • org.springframework.security.web.server.jackson2.WebServerJackson2Module
  • org.springframework.security.web.session.HttpSessionCreatedEvent
  • org.springframework.security.web.session.HttpSessionDestroyedEvent
  • org.springframework.security.web.session.HttpSessionIdChangedEvent
  • org.springframework.security.web.session.SessionInformationExpiredEvent
  • org.springframework.security.web.webauthn.authentication.WebAuthnAuthentication
  • org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationRequestToken
  • org.springframework.security.web.webauthn.jackson.AttestationConveyancePreferenceSerializer
  • org.springframework.security.web.webauthn.jackson.AuthenticationExtensionsClientInputSerializer
  • org.springframework.security.web.webauthn.jackson.AuthenticationExtensionsClientInputsSerializer
  • org.springframework.security.web.webauthn.jackson.AuthenticationExtensionsClientOutputsDeserializer
  • org.springframework.security.web.webauthn.jackson.AuthenticatorAttachmentDeserializer
  • org.springframework.security.web.webauthn.jackson.AuthenticatorAttachmentSerializer
  • org.springframework.security.web.webauthn.jackson.AuthenticatorTransportDeserializer
  • org.springframework.security.web.webauthn.jackson.BytesSerializer
  • org.springframework.security.web.webauthn.jackson.COSEAlgorithmIdentifierDeserializer
  • org.springframework.security.web.webauthn.jackson.COSEAlgorithmIdentifierSerializer
  • org.springframework.security.web.webauthn.jackson.CredProtectAuthenticationExtensionsClientInputSerializer
  • org.springframework.security.web.webauthn.jackson.DurationSerializer
  • org.springframework.security.web.webauthn.jackson.PublicKeyCredentialTypeDeserializer
  • org.springframework.security.web.webauthn.jackson.PublicKeyCredentialTypeSerializer
  • org.springframework.security.web.webauthn.jackson.ResidentKeyRequirementSerializer
  • org.springframework.security.web.webauthn.jackson.UserVerificationRequirementSerializer
  • org.springframework.security.web.webauthn.jackson.WebauthnJackson2Module
  • org.springframework.security.oauth2.core.OAuth2AuthenticationException
  • org.springframework.security.oauth2.core.OAuth2AuthorizationException
  • org.springframework.security.access.AccessDeniedException
  • org.springframework.security.access.AuthorizationServiceException
  • org.springframework.security.access.SecurityConfig
  • org.springframework.security.access.annotation.Jsr250SecurityConfig
  • org.springframework.security.access.event.AuthenticationCredentialsNotFoundEvent
  • org.springframework.security.access.event.AuthorizationFailureEvent
  • org.springframework.security.access.event.AuthorizedEvent
  • org.springframework.security.access.event.PublicInvocationEvent
  • org.springframework.security.access.expression.method.PostInvocationExpressionAttribute
  • org.springframework.security.access.expression.method.PreInvocationExpressionAttribute
  • org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor
  • org.springframework.security.authentication.AccountExpiredException
  • org.springframework.security.authentication.AuthenticationCredentialsNotFoundException
  • org.springframework.security.authentication.AuthenticationServiceException
  • org.springframework.security.authentication.BadCredentialsException
  • org.springframework.security.authentication.CredentialsExpiredException
  • org.springframework.security.authentication.DisabledException
  • org.springframework.security.authentication.InsufficientAuthenticationException
  • org.springframework.security.authentication.InternalAuthenticationServiceException
  • org.springframework.security.authentication.LockedException
  • org.springframework.security.authentication.ProviderNotFoundException
  • org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent
  • org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent
  • org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent
  • org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent
  • org.springframework.security.authentication.event.AuthenticationFailureLockedEvent
  • org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent
  • org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent
  • org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent
  • org.springframework.security.authentication.event.AuthenticationSuccessEvent
  • org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent
  • org.springframework.security.authentication.event.LogoutSuccessEvent
  • org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent
  • org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent
  • org.springframework.security.authentication.ott.InvalidOneTimeTokenException
  • org.springframework.security.authentication.password.CompromisedPasswordException
  • org.springframework.security.authorization.AuthorizationDeniedException
  • org.springframework.security.authorization.event.AuthorizationDeniedEvent
  • org.springframework.security.authorization.event.AuthorizationEvent
  • org.springframework.security.authorization.event.AuthorizationGrantedEvent
  • org.springframework.security.core.ComparableVersion$ListItem
  • org.springframework.security.core.context.SecurityContextChangedEvent
  • org.springframework.security.core.context.TransientSecurityContext
  • org.springframework.security.core.session.AbstractSessionEvent
  • org.springframework.security.core.userdetails.UsernameNotFoundException
  • org.springframework.security.jackson2.CoreJackson2Module
  • org.springframework.security.jackson2.SecurityJackson2Modules$AllowlistTypeResolverBuilder
  • org.springframework.security.access.annotation.BusinessServiceImpl
  • org.springframework.security.access.annotation.ExpressionProtectedBusinessServiceImpl
  • org.springframework.security.access.annotation.Jsr250BusinessServiceImpl
  • org.springframework.security.crypto.codec.Base64$InvalidBase64CharacterException
  • org.springframework.security.ldap.authentication.ad.ActiveDirectoryAuthenticationException
  • org.springframework.security.ldap.jackson2.LdapJackson2Module
  • org.springframework.security.ldap.ppolicy.PasswordPolicyControl
  • org.springframework.security.ldap.ppolicy.PasswordPolicyException
  • org.springframework.security.ldap.ppolicy.PasswordPolicyResponseControl
  • org.springframework.security.messaging.access.expression.MessageExpressionConfigAttribute
  • org.springframework.security.oauth2.client.ClientAuthorizationException
  • org.springframework.security.oauth2.client.ClientAuthorizationRequiredException
  • org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module
  • org.springframework.security.oauth2.client.web.InvalidClientRegistrationIdException
  • org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter$OAuth2AuthorizationRequestException
  • org.springframework.security.oauth2.jwt.BadJwtException
  • org.springframework.security.oauth2.jwt.JwtDecoderInitializationException
  • org.springframework.security.oauth2.jwt.JwtEncodingException
  • org.springframework.security.oauth2.jwt.JwtException
  • org.springframework.security.oauth2.jwt.JwtValidationException
  • org.springframework.security.oauth2.server.resource.InvalidBearerTokenException
  • org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException
  • 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
@jzheaux jzheaux added status: waiting-for-triage An issue we've not yet triaged type: enhancement A general enhancement labels Dec 13, 2024
jzheaux added a commit that referenced this issue Dec 13, 2024
jzheaux added a commit that referenced this issue Dec 13, 2024
This allows testing of classes that are serializable,
but do not use Security's serialVersionUID.

Issue gh-16276
jzheaux added a commit that referenced this issue Dec 13, 2024
@jzheaux
Copy link
Contributor Author

jzheaux commented Dec 13, 2024

Here is an example: e3cd433

jzheaux added a commit to jzheaux/spring-security that referenced this issue Dec 13, 2024
jzheaux added a commit to jzheaux/spring-security that referenced this issue Dec 13, 2024
jzheaux added a commit that referenced this issue Dec 13, 2024
jzheaux added a commit that referenced this issue Dec 14, 2024
The following inner classes are used only internally by a non-Serializable component

Issue gh-16276
@jzheaux jzheaux self-assigned this Dec 17, 2024
@jzheaux jzheaux added this to the 6.4.x milestone Dec 17, 2024
@jzheaux jzheaux added in: core An issue in spring-security-core type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged type: enhancement A general enhancement labels Dec 17, 2024
@dalbani
Copy link

dalbani commented Jan 7, 2025

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.

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 🤔

@jzheaux
Copy link
Contributor Author

jzheaux commented Jan 14, 2025

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.

@jzheaux
Copy link
Contributor Author

jzheaux commented Jan 14, 2025

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 #16163 to learn about the issue that triggered the need for this specific ticket.

@dalbani
Copy link

dalbani commented Jan 14, 2025

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.

@jzheaux
Copy link
Contributor Author

jzheaux commented Jan 14, 2025

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.

jzheaux added a commit that referenced this issue Jan 14, 2025
jzheaux added a commit that referenced this issue Jan 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core An issue in spring-security-core type: bug A general bug
Projects
Status: No status
Development

No branches or pull requests

2 participants