From 679a47a51de8b8a4d7e952bedd7a5cff8e0fba3a Mon Sep 17 00:00:00 2001 From: Robert Winch <362503+rwinch@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:37:39 -0500 Subject: [PATCH 1/2] Add XML Based shouldWriteHeadersEagerly tests --- .../config/http/HttpHeadersConfigTests.java | 25 +++++++++++++ ...rsConfigTests-HeadersEagerlyConfigured.xml | 37 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-HeadersEagerlyConfigured.xml diff --git a/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java b/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java index 042d9185c4..16a93e9633 100644 --- a/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java @@ -28,14 +28,17 @@ import jakarta.servlet.http.HttpSession; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.session.SessionLimit; +import org.springframework.security.web.header.HeaderWriterFilter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; @@ -150,6 +153,16 @@ public class HttpHeadersConfigTests { // @formatter:on } + @Test + public void requestWhenHeadersEagerlyConfiguredThenHeadersAreWritten() throws Exception { + this.spring.configLocations(this.xml("HeadersEagerlyConfigured")).autowire(); + // @formatter:off + this.mvc.perform(get("/").secure(true)) + .andExpect(status().isOk()) + .andExpect(includesDefaults()); + // @formatter:on + } + @Test public void requestWhenFrameOptionsConfiguredThenIncludesHeader() throws Exception { Map headers = new HashMap(defaultHeaders); @@ -955,6 +968,18 @@ public class HttpHeadersConfigTests { } + public static class EagerHeadersBeanPostProcessor implements BeanPostProcessor { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof HeaderWriterFilter headerWriterFilter) { + headerWriterFilter.setShouldWriteHeadersEagerly(true); + } + return bean; + } + + } + public static class CustomSessionLimit implements SessionLimit { @Override diff --git a/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-HeadersEagerlyConfigured.xml b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-HeadersEagerlyConfigured.xml new file mode 100644 index 0000000000..0cb674e409 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-HeadersEagerlyConfigured.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + 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 2/2] 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); + } + }