From eefbb4da6497a34661e6baeea6a55e8e6f8a7702 Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:41:32 -0400 Subject: [PATCH] Fix DefaultOidcUser.equals() Closes gh-18622 --- .../core/oidc/user/DefaultOidcUser.java | 35 +++++++++++++++++++ .../core/oidc/user/DefaultOidcUserTests.java | 28 +++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/DefaultOidcUser.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/DefaultOidcUser.java index 3b99e3e829..336e70ee5d 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/DefaultOidcUser.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/DefaultOidcUser.java @@ -19,6 +19,7 @@ package org.springframework.security.oauth2.core.oidc.user; import java.io.Serial; import java.util.Collection; import java.util.Map; +import java.util.Objects; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; @@ -114,4 +115,38 @@ public class DefaultOidcUser extends DefaultOAuth2User implements OidcUser { return this.userInfo; } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + DefaultOidcUser that = (DefaultOidcUser) obj; + if (!this.getName().equals(that.getName())) { + return false; + } + if (!this.getAuthorities().equals(that.getAuthorities())) { + return false; + } + if (this.getIdToken().getIssuer() == null || that.getIdToken().getIssuer() == null) { + return false; + } + return Objects.equals(this.getIdToken().getIssuer().toExternalForm(), + that.getIdToken().getIssuer().toExternalForm()) + && Objects.equals(this.getIdToken().getSubject(), that.getIdToken().getSubject()); + } + + @Override + public int hashCode() { + int result = this.getName().hashCode(); + result = 31 * result + this.getAuthorities().hashCode(); + result = 31 * result + ((this.getIdToken().getIssuer() != null) + ? this.getIdToken().getIssuer().toExternalForm().hashCode() : 0); + result = 31 * result + + ((this.getIdToken().getSubject() != null) ? this.getIdToken().getSubject().hashCode() : 0); + return result; + } + } diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/oidc/user/DefaultOidcUserTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/oidc/user/DefaultOidcUserTests.java index 0fb4bee1d0..27d9ed6d96 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/oidc/user/DefaultOidcUserTests.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/oidc/user/DefaultOidcUserTests.java @@ -17,6 +17,7 @@ package org.springframework.security.oauth2.core.oidc.user; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -147,4 +148,31 @@ public class DefaultOidcUserTests { StandardClaimNames.NAME, StandardClaimNames.EMAIL); } + // gh-18622 + @Test + public void equalsWhenOidcUserPrincipalSameThenTrue() { + String issuer = "https://example.com"; + String subject = "subject-1"; + + // @formatter:off + OidcIdToken idToken1 = OidcIdToken.withTokenValue("id-token-value-1") + .issuer(issuer) + .subject(subject) + .issuedAt(Instant.now()) + .expiresAt(Instant.now().plus(30, ChronoUnit.MINUTES)) + .build(); + + OidcIdToken idToken2 = OidcIdToken.withTokenValue("id-token-value-2") + .issuer(issuer) + .subject(subject) + .issuedAt(Instant.now()) + .expiresAt(Instant.now().plus(30, ChronoUnit.MINUTES)) + .build(); + // @formatter:on + + DefaultOidcUser user1 = new DefaultOidcUser(AUTHORITIES, idToken1, USER_INFO); + DefaultOidcUser user2 = new DefaultOidcUser(AUTHORITIES, idToken2, USER_INFO); + assertThat(user1).isEqualTo(user2); + } + }