From 88d9724eebb69c1073329076086f1cf57529c3b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Apr 2025 03:48:55 +0000 Subject: [PATCH 1/3] Bump io.spring.security.release from 1.0.3 to 1.0.4 Bumps io.spring.security.release from 1.0.3 to 1.0.4. --- updated-dependencies: - dependency-name: io.spring.security.release dependency-version: 1.0.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 44f94d97..eabc4611 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id "io.spring.convention.root" - id "io.spring.security.release" version "1.0.3" + id "io.spring.security.release" version "1.0.4" } group = "org.springframework.security" From 6ae338245fad7ea58ee00c5ac3bbf174715323cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Apr 2025 03:17:09 +0000 Subject: [PATCH 2/3] Bump org.springframework:spring-framework-bom from 6.1.18 to 6.1.19 Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.1.18 to 6.1.19. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.18...v6.1.19) --- updated-dependencies: - dependency-name: org.springframework:spring-framework-bom dependency-version: 6.1.19 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cbf6b43f..d7b6d2f7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -org-springframework = "6.1.18" +org-springframework = "6.1.19" org-springframework-security = "6.3.8" com-fasterxml-jackson = "2.17.3" nimbus-jose-jwt = "9.39.3" From c624d0a908af0b2a9314021e1377332ed3370661 Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Mon, 21 Apr 2025 21:15:05 -0400 Subject: [PATCH 3/3] Revert "Fix client_secret_basic authentication failures and return challenge" This reverts commit 42c18c856f168c24930ca480d2d26d84d455af47. --- .../web/OAuth2ClientAuthenticationFilter.java | 58 +++++-------------- .../OAuth2AuthorizationCodeGrantTests.java | 10 ++-- ...OAuth2ClientAuthenticationFilterTests.java | 26 +++++---- 3 files changed, 33 insertions(+), 61 deletions(-) diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilter.java index d72dc5c0..e9ba1f32 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2025 the original author or authors. + * Copyright 2020-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,6 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; @@ -54,7 +53,6 @@ import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; import org.springframework.web.filter.OncePerRequestFilter; @@ -92,8 +90,6 @@ public final class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter private final AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - private final BasicAuthenticationEntryPoint basicAuthenticationEntryPoint = new BasicAuthenticationEntryPoint(); - private AuthenticationConverter authenticationConverter; private AuthenticationSuccessHandler authenticationSuccessHandler = this::onAuthenticationSuccess; @@ -114,7 +110,6 @@ public final class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter Assert.notNull(requestMatcher, "requestMatcher cannot be null"); this.authenticationManager = authenticationManager; this.requestMatcher = requestMatcher; - this.basicAuthenticationEntryPoint.setRealmName("default"); // @formatter:off this.authenticationConverter = new DelegatingAuthenticationConverter( Arrays.asList( @@ -135,9 +130,8 @@ public final class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter return; } - Authentication authenticationRequest = null; try { - authenticationRequest = this.authenticationConverter.convert(request); + Authentication authenticationRequest = this.authenticationConverter.convert(request); if (authenticationRequest instanceof AbstractAuthenticationToken) { ((AbstractAuthenticationToken) authenticationRequest) .setDetails(this.authenticationDetailsSource.buildDetails(request)); @@ -154,14 +148,7 @@ public final class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Client authentication failed: %s", ex.getError()), ex); } - if (authenticationRequest instanceof OAuth2ClientAuthenticationToken clientAuthentication) { - this.authenticationFailureHandler.onAuthenticationFailure(request, response, - new OAuth2ClientAuthenticationException(ex.getError(), ex, clientAuthentication)); - } - else { - this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); - } - + this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); } } @@ -213,21 +200,21 @@ public final class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter } private void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authenticationException) throws IOException { + AuthenticationException exception) throws IOException { SecurityContextHolder.clearContext(); - if (authenticationException instanceof OAuth2ClientAuthenticationException clientAuthenticationException) { - OAuth2ClientAuthenticationToken clientAuthentication = clientAuthenticationException - .getClientAuthentication(); - if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC - .equals(clientAuthentication.getClientAuthenticationMethod())) { - this.basicAuthenticationEntryPoint.commence(request, response, authenticationException); - return; - } - } + // TODO + // The authorization server MAY return an HTTP 401 (Unauthorized) status code + // to indicate which HTTP authentication schemes are supported. + // If the client attempted to authenticate via the "Authorization" request header + // field, + // the authorization server MUST respond with an HTTP 401 (Unauthorized) status + // code and + // include the "WWW-Authenticate" response header field + // matching the authentication scheme used by the client. - OAuth2Error error = ((OAuth2AuthenticationException) authenticationException).getError(); + OAuth2Error error = ((OAuth2AuthenticationException) exception).getError(); ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); if (OAuth2ErrorCodes.INVALID_CLIENT.equals(error.getErrorCode())) { httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED); @@ -262,21 +249,4 @@ public final class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter } } - private static final class OAuth2ClientAuthenticationException extends OAuth2AuthenticationException { - - private final OAuth2ClientAuthenticationToken clientAuthentication; - - private OAuth2ClientAuthenticationException(OAuth2Error error, Throwable cause, - OAuth2ClientAuthenticationToken clientAuthentication) { - super(error, cause); - Assert.notNull(clientAuthentication, "clientAuthentication cannot be null"); - this.clientAuthentication = clientAuthentication; - } - - private OAuth2ClientAuthenticationToken getClientAuthentication() { - return this.clientAuthentication; - } - - } - } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java index edcdb84b..d794baf5 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2025 the original author or authors. + * Copyright 2020-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -538,7 +538,7 @@ public class OAuth2AuthorizationCodeGrantTests { } @Test - public void requestWhenConfidentialClientWithPkceAndMissingCodeVerifierThenUnauthorized() throws Exception { + public void requestWhenConfidentialClientWithPkceAndMissingCodeVerifierThenBadRequest() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); @@ -569,7 +569,7 @@ public class OAuth2AuthorizationCodeGrantTests { .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(status().isUnauthorized()); + .andExpect(status().isBadRequest()); } // gh-1011 @@ -601,7 +601,7 @@ public class OAuth2AuthorizationCodeGrantTests { } @Test - public void requestWhenConfidentialClientWithPkceAndMissingCodeChallengeButCodeVerifierProvidedThenUnauthorized() + public void requestWhenConfidentialClientWithPkceAndMissingCodeChallengeButCodeVerifierProvidedThenBadRequest() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); @@ -631,7 +631,7 @@ public class OAuth2AuthorizationCodeGrantTests { .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) .param(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(status().isUnauthorized()); + .andExpect(status().isBadRequest()); } @Test diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilterTests.java index f2ebe115..97dc1750 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2025 the original author or authors. + * Copyright 2020-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageConverter; @@ -176,25 +175,26 @@ public class OAuth2ClientAuthenticationFilterTests { // gh-889 @Test - public void doFilterWhenRequestMatchesAndClientIdContainsNonPrintableASCIIThenReturnChallenge() throws Exception { + public void doFilterWhenRequestMatchesAndClientIdContainsNonPrintableASCIIThenInvalidRequestError() + throws Exception { // Hex 00 -> null String clientId = new String(Hex.decode("00"), StandardCharsets.UTF_8); - assertWhenInvalidClientIdThenReturnChallenge(clientId); + assertWhenInvalidClientIdThenInvalidRequestError(clientId); // Hex 0a61 -> line feed + a clientId = new String(Hex.decode("0a61"), StandardCharsets.UTF_8); - assertWhenInvalidClientIdThenReturnChallenge(clientId); + assertWhenInvalidClientIdThenInvalidRequestError(clientId); // Hex 1b -> escape clientId = new String(Hex.decode("1b"), StandardCharsets.UTF_8); - assertWhenInvalidClientIdThenReturnChallenge(clientId); + assertWhenInvalidClientIdThenInvalidRequestError(clientId); // Hex 1b61 -> escape + a clientId = new String(Hex.decode("1b61"), StandardCharsets.UTF_8); - assertWhenInvalidClientIdThenReturnChallenge(clientId); + assertWhenInvalidClientIdThenInvalidRequestError(clientId); } - private void assertWhenInvalidClientIdThenReturnChallenge(String clientId) throws Exception { + private void assertWhenInvalidClientIdThenInvalidRequestError(String clientId) throws Exception { given(this.authenticationConverter.convert(any(HttpServletRequest.class))) .willReturn(new OAuth2ClientAuthenticationToken(clientId, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, "secret", null)); @@ -210,12 +210,13 @@ public class OAuth2ClientAuthenticationFilterTests { verifyNoInteractions(this.authenticationManager); assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); - assertThat(response.getStatus()).isEqualTo(HttpStatus.UNAUTHORIZED.value()); - assertThat(response.getHeader(HttpHeaders.WWW_AUTHENTICATE)).isEqualTo("Basic realm=\"default\""); + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + OAuth2Error error = readError(response); + assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); } @Test - public void doFilterWhenRequestMatchesAndBadCredentialsThenReturnChallenge() throws Exception { + public void doFilterWhenRequestMatchesAndBadCredentialsThenInvalidClientError() throws Exception { given(this.authenticationConverter.convert(any(HttpServletRequest.class))) .willReturn(new OAuth2ClientAuthenticationToken("clientId", ClientAuthenticationMethod.CLIENT_SECRET_BASIC, "invalid-secret", null)); @@ -234,7 +235,8 @@ public class OAuth2ClientAuthenticationFilterTests { assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); assertThat(response.getStatus()).isEqualTo(HttpStatus.UNAUTHORIZED.value()); - assertThat(response.getHeader(HttpHeaders.WWW_AUTHENTICATE)).isEqualTo("Basic realm=\"default\""); + OAuth2Error error = readError(response); + assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); } @Test