CVE-2024-22234: Broken Access Control in Spring Security With Direct Use of isFullyAuthenticated
Description
In Spring Security, versions 6.1.x prior to 6.1.7 and versions 6.2.x prior to 6.2.2, an application is vulnerable to broken access control when it directly uses the AuthenticationTrustResolver.isFullyAuthenticated(Authentication) method.
Specifically, an application is vulnerable if:
- The application uses AuthenticationTrustResolver.isFullyAuthenticated(Authentication) directly and a null authentication parameter is passed to it resulting in an erroneous true return value.
An application is not vulnerable if any of the following is true:
- The application does not use AuthenticationTrustResolver.isFullyAuthenticated(Authentication) directly.
- The application does not pass null to AuthenticationTrustResolver.isFullyAuthenticated
- The application only uses isFullyAuthenticated via Method Security https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html or HTTP Request Security https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Spring Security is vulnerable to broken access control when directly using AuthenticationTrustResolver.isFullyAuthenticated with a null authentication parameter, causing it to return true erroneously.
Vulnerability
Description
In Spring Security versions 6.1.x prior to 6.1.7 and 6.2.x prior to 6.2.2, the AuthenticationTrustResolver.isFullyAuthenticated(Authentication) method returns true when passed a null authentication parameter. This is due to a missing null-check in the method's implementation, leading to an erroneous authentication status.
Exploitation
An attacker can exploit this by causing the application to invoke isFullyAuthenticated directly with a null argument. This typically occurs when the application uses the method in custom access control logic rather than through the recommended Method Security or HTTP Request Security pathways. No prior authentication is required.
Impact
Successful exploitation results in broken access control, allowing an attacker to bypass authentication checks and gain unauthorized access to protected resources. The vulnerability is rated HIGH with a CVSS score of 8.1.
Mitigation
The issue is fixed in Spring Security 6.1.7 and 6.2.2 [3]. Users should upgrade to these versions or later. As a workaround, applications should avoid passing null to isFullyAuthenticated and prefer using higher-level security abstractions provided by Spring Security [1][4].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.springframework.security:spring-security-coreMaven | >= 6.1.0, < 6.1.7 | 6.1.7 |
org.springframework.security:spring-security-coreMaven | >= 6.2.0, < 6.2.2 | 6.2.2 |
Affected products
2- Spring/Spring Securityv5Range: 6.1.x
Patches
1750cb30ce44dAdd AuthenticationTrustResolver.isAuthenticated
16 files changed · +123 −26
cas/src/main/java/org/springframework/security/cas/web/CasAuthenticationFilter.java+5 −3 modified@@ -27,8 +27,9 @@ import org.apereo.cas.client.validation.TicketValidator; import org.springframework.core.log.LogMessage; -import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationDetailsSource; +import org.springframework.security.authentication.AuthenticationTrustResolver; +import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent; import org.springframework.security.cas.ServiceProperties; import org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken; @@ -195,6 +196,8 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil private SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository(); + private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + public CasAuthenticationFilter() { super("/login/cas"); setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler()); @@ -337,8 +340,7 @@ private boolean proxyTicketRequest(boolean serviceTicketRequest, HttpServletRequ */ private boolean authenticated() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - return authentication != null && authentication.isAuthenticated() - && !(authentication instanceof AnonymousAuthenticationToken); + return this.trustResolver.isAuthenticated(authentication); } /**
config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java+4 −1 modified@@ -29,6 +29,7 @@ import jakarta.servlet.http.HttpSession; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Answers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -82,6 +83,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.withSettings; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; @@ -304,7 +306,8 @@ public void configureWhenRegisteringObjectPostProcessorThenInvokedOnChangeSessio @Test public void getWhenAnonymousRequestAndTrustResolverSharedObjectReturnsAnonymousFalseThenSessionIsSaved() throws Exception { - SharedTrustResolverConfig.TR = mock(AuthenticationTrustResolver.class); + SharedTrustResolverConfig.TR = mock(AuthenticationTrustResolver.class, + withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS)); given(SharedTrustResolverConfig.TR.isAnonymous(any())).willReturn(false); this.spring.register(SharedTrustResolverConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/")).andReturn();
config/src/test/kotlin/org/springframework/security/config/annotation/web/SessionManagementDslTests.kt+4 −3 modified@@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.mock.web.MockHttpSession +import org.springframework.security.authentication.TestingAuthenticationToken import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity import org.springframework.security.config.http.SessionCreationPolicy @@ -118,7 +119,7 @@ class SessionManagementDslTests { @Test fun `session management when session authentication error url then redirected to url`() { this.spring.register(SessionAuthenticationErrorUrlConfig::class.java).autowire() - val authentication: Authentication = mockk() + val authentication: Authentication = TestingAuthenticationToken("user", "password", "ROLE_USER") val session: MockHttpSession = mockk(relaxed = true) every { session.changeSessionId() } throws SessionAuthenticationException("any SessionAuthenticationException") every<Any?> { session.getAttribute(any()) } returns null @@ -150,7 +151,7 @@ class SessionManagementDslTests { @Test fun `session management when session authentication failure handler then handler used`() { this.spring.register(SessionAuthenticationFailureHandlerConfig::class.java).autowire() - val authentication: Authentication = mockk() + val authentication: Authentication = TestingAuthenticationToken("user", "password", "ROLE_USER") val session: MockHttpSession = mockk(relaxed = true) every { session.changeSessionId() } throws SessionAuthenticationException("any SessionAuthenticationException") every<Any?> { session.getAttribute(any()) } returns null @@ -210,7 +211,7 @@ class SessionManagementDslTests { fun `session management when session authentication strategy then strategy used`() { this.spring.register(SessionAuthenticationStrategyConfig::class.java).autowire() mockkObject(SessionAuthenticationStrategyConfig.STRATEGY) - val authentication: Authentication = mockk(relaxed = true) + val authentication: Authentication = TestingAuthenticationToken("user", "password", "ROLE_USER") val session: MockHttpSession = mockk(relaxed = true) every { session.changeSessionId() } throws SessionAuthenticationException("any SessionAuthenticationException") every<Any?> { session.getAttribute(any()) } returns null
core/src/main/java/org/springframework/security/access/expression/SecurityExpressionRoot.java+1 −1 modified@@ -147,7 +147,7 @@ public final boolean isAnonymous() { @Override public final boolean isAuthenticated() { - return !isAnonymous(); + return this.trustResolver.isAuthenticated(getAuthentication()); } @Override
core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolver.java+16 −4 modified@@ -61,13 +61,25 @@ public interface AuthenticationTrustResolver { * <p> * @param authentication to test (may be <code>null</code> in which case the method * will always return <code>false</code>) - * @return <code>true</code> the passed authentication token represented an anonymous - * principal and is authenticated using a remember-me token, <code>false</code> - * otherwise + * @return <code>true</code> the passed authentication token represented an + * authenticated user ({@link #isAuthenticated(Authentication)} and not + * {@link #isRememberMe(Authentication)}, <code>false</code> otherwise * @since 6.1 */ default boolean isFullyAuthenticated(Authentication authentication) { - return !isAnonymous(authentication) && !isRememberMe(authentication); + return isAuthenticated(authentication) && !isRememberMe(authentication); + } + + /** + * Checks if the {@link Authentication} is not null, authenticated, and not anonymous. + * @param authentication the {@link Authentication} to check. + * @return true if the {@link Authentication} is not null, + * {@link #isAnonymous(Authentication)} returns false, & + * {@link Authentication#isAuthenticated()} is true. + * @since 6.1.7 + */ + default boolean isAuthenticated(Authentication authentication) { + return authentication != null && authentication.isAuthenticated() && !isAnonymous(authentication); } }
core/src/main/java/org/springframework/security/authorization/AuthenticatedAuthorizationManager.java+2 −3 modified@@ -133,8 +133,7 @@ private static class AuthenticatedAuthorizationStrategy extends AbstractAuthoriz @Override boolean isGranted(Authentication authentication) { - return authentication != null && !this.trustResolver.isAnonymous(authentication) - && authentication.isAuthenticated(); + return this.trustResolver.isAuthenticated(authentication); } } @@ -143,7 +142,7 @@ private static final class FullyAuthenticatedAuthorizationStrategy extends Authe @Override boolean isGranted(Authentication authentication) { - return authentication != null && this.trustResolver.isFullyAuthenticated(authentication); + return this.trustResolver.isFullyAuthenticated(authentication); } }
core/src/test/java/org/springframework/security/access/expression/SecurityExpressionRootTests.java+24 −0 modified@@ -25,6 +25,7 @@ import org.springframework.security.core.authority.AuthorityUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -134,4 +135,27 @@ public void hasAuthorityDoesNotAddDefaultPrefix() { assertThat(this.root.hasAnyAuthority("ROLE_A", "NOT")).isTrue(); } + @Test + void isAuthenticatedWhenAuthenticatedNullThenException() { + this.root = new SecurityExpressionRoot((Authentication) null) { + }; + assertThatIllegalArgumentException().isThrownBy(() -> this.root.isAuthenticated()); + } + + @Test + void isAuthenticatedWhenTrustResolverFalseThenFalse() { + AuthenticationTrustResolver atr = mock(AuthenticationTrustResolver.class); + given(atr.isAuthenticated(JOE)).willReturn(false); + this.root.setTrustResolver(atr); + assertThat(this.root.isAuthenticated()).isFalse(); + } + + @Test + void isAuthenticatedWhenTrustResolverTrueThenTrue() { + AuthenticationTrustResolver atr = mock(AuthenticationTrustResolver.class); + given(atr.isAuthenticated(JOE)).willReturn(true); + this.root.setTrustResolver(atr); + assertThat(this.root.isAuthenticated()).isTrue(); + } + }
core/src/test/java/org/springframework/security/authentication/AuthenticationTrustResolverImplTests.java+53 −0 modified@@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; +import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -63,4 +64,56 @@ public void testGettersSetters() { assertThat(trustResolver.getRememberMeClass()).isEqualTo(TestingAuthenticationToken.class); } + @Test + void isAuthenticatedWhenAuthenticationNullThenFalse() { + AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + Authentication authentication = null; + assertThat(trustResolver.isAuthenticated(authentication)).isFalse(); + } + + @Test + void isAuthenticatedWhenAuthenticationNotAuthenticatedThenFalse() { + AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password"); + assertThat(trustResolver.isAuthenticated(authentication)).isFalse(); + } + + @Test + void isAuthenticatedWhenAnonymousThenFalse() { + AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + AnonymousAuthenticationToken authentication = new AnonymousAuthenticationToken("key", "principal", + AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); + assertThat(trustResolver.isAuthenticated(authentication)).isFalse(); + } + + @Test + void isFullyAuthenticatedWhenAuthenticationNullThenFalse() { + AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + Authentication authentication = null; + assertThat(trustResolver.isFullyAuthenticated(authentication)).isFalse(); + } + + @Test + void isFullyAuthenticatedWhenAuthenticationNotAuthenticatedThenFalse() { + AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password"); + assertThat(trustResolver.isFullyAuthenticated(authentication)).isFalse(); + } + + @Test + void isFullyAuthenticatedWhenAnonymousThenFalse() { + AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + AnonymousAuthenticationToken authentication = new AnonymousAuthenticationToken("key", "principal", + AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); + assertThat(trustResolver.isFullyAuthenticated(authentication)).isFalse(); + } + + @Test + void isFullyAuthenticatedWhenRememberMeThenFalse() { + AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + RememberMeAuthenticationToken authentication = new RememberMeAuthenticationToken("key", "user", + AuthorityUtils.createAuthorityList("ROLE_USER")); + assertThat(trustResolver.isFullyAuthenticated(authentication)).isFalse(); + } + }
messaging/src/test/java/org/springframework/security/messaging/access/expression/DefaultMessageSecurityExpressionHandlerTests.java+2 −1 modified@@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Answers; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -50,7 +51,7 @@ @ExtendWith(MockitoExtension.class) public class DefaultMessageSecurityExpressionHandlerTests { - @Mock + @Mock(answer = Answers.CALLS_REAL_METHODS) AuthenticationTrustResolver trustResolver; @Mock
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/AuthenticatedPrincipalOAuth2AuthorizedClientRepository.java+1 −2 modified@@ -107,8 +107,7 @@ public void removeAuthorizedClient(String clientRegistrationId, Authentication p } private boolean isPrincipalAuthenticated(Authentication authentication) { - return authentication != null && !this.authenticationTrustResolver.isAnonymous(authentication) - && authentication.isAuthenticated(); + return this.authenticationTrustResolver.isAuthenticated(authentication); } }
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository.java+1 −2 modified@@ -106,8 +106,7 @@ public Mono<Void> removeAuthorizedClient(String clientRegistrationId, Authentica } private boolean isPrincipalAuthenticated(Authentication authentication) { - return authentication != null && !this.authenticationTrustResolver.isAnonymous(authentication) - && authentication.isAuthenticated(); + return this.authenticationTrustResolver.isAuthenticated(authentication); } }
web/src/main/java/org/springframework/security/web/server/authorization/ExceptionTranslationWebFilter.java+1 −1 modified@@ -52,7 +52,7 @@ public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { return chain.filter(exchange) .onErrorResume(AccessDeniedException.class, (denied) -> exchange.getPrincipal() .filter((principal) -> (!(principal instanceof Authentication) || (principal instanceof Authentication - && !(this.authenticationTrustResolver.isAnonymous((Authentication) principal))))) + && (this.authenticationTrustResolver.isAuthenticated((Authentication) principal))))) .switchIfEmpty(commenceAuthentication(exchange, new InsufficientAuthenticationException( "Full authentication is required to access this resource")))
web/src/main/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestWrapper.java+1 −1 modified@@ -93,7 +93,7 @@ public SecurityContextHolderAwareRequestWrapper(HttpServletRequest request, */ private Authentication getAuthentication() { Authentication auth = this.securityContextHolderStrategy.getContext().getAuthentication(); - return (!this.trustResolver.isAnonymous(auth)) ? auth : null; + return (this.trustResolver.isAuthenticated(auth)) ? auth : null; } /**
web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java+1 −1 modified@@ -94,7 +94,7 @@ private void doFilter(HttpServletRequest request, HttpServletResponse response, request.setAttribute(FILTER_APPLIED, Boolean.TRUE); if (!this.securityContextRepository.containsContext(request)) { Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); - if (authentication != null && !this.trustResolver.isAnonymous(authentication)) { + if (this.trustResolver.isAuthenticated(authentication)) { // The user has been authenticated during the current request, so call the // session strategy try {
web/src/test/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestWrapperTests.java+1 −1 modified@@ -140,7 +140,7 @@ public void testGetRemoteUserStringWithAuthenticatedPrincipal() { String username = "authPrincipalUsername"; AuthenticatedPrincipal principal = mock(AuthenticatedPrincipal.class); given(principal.getName()).willReturn(username); - Authentication auth = new TestingAuthenticationToken(principal, "user"); + Authentication auth = new TestingAuthenticationToken(principal, "user", "ROLE_USER"); SecurityContextHolder.getContext().setAuthentication(auth); MockHttpServletRequest request = new MockHttpServletRequest(); request.setRequestURI("/");
web/src/test/java/org/springframework/security/web/session/SessionManagementFilterTests.java+6 −2 modified@@ -21,6 +21,7 @@ import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.mockito.Answers; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; @@ -42,6 +43,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.withSettings; /** * @author Luke Taylor @@ -176,7 +178,8 @@ public void responseIsRedirectedToRequestedUrlIfSetAndSessionIsInvalid() throws @Test public void customAuthenticationTrustResolver() throws Exception { - AuthenticationTrustResolver trustResolver = mock(AuthenticationTrustResolver.class); + AuthenticationTrustResolver trustResolver = mock(AuthenticationTrustResolver.class, + withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS)); SecurityContextRepository repo = mock(SecurityContextRepository.class); SessionManagementFilter filter = new SessionManagementFilter(repo); filter.setTrustResolver(trustResolver); @@ -194,7 +197,8 @@ public void setTrustResolverNull() { } private void authenticateUser() { - SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("user", "pass")); + SecurityContextHolder.getContext() + .setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_USER")); } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-w3w6-26f2-p474ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-22234ghsaADVISORY
- github.com/spring-projects/spring-security/commit/750cb30ce44d279c2f54c845d375e6a58bded569ghsaWEB
- security.netapp.com/advisory/ntap-20240315-0003ghsaWEB
- spring.io/security/cve-2024-22234ghsaWEB
- security.netapp.com/advisory/ntap-20240315-0003/mitre
News mentions
0No linked articles in our index yet.