Skip to content

Commit 9ecfe49

Browse files
committed
Merge branch '1.4.x'
2 parents 2dff088 + f3820e5 commit 9ecfe49

File tree

3 files changed

+60
-32
lines changed

3 files changed

+60
-32
lines changed

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilter.java

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.security.core.AuthenticationException;
3535
import org.springframework.security.core.context.SecurityContext;
3636
import org.springframework.security.core.context.SecurityContextHolder;
37+
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
3738
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
3839
import org.springframework.security.oauth2.core.OAuth2Error;
3940
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
@@ -53,6 +54,7 @@
5354
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
5455
import org.springframework.security.web.authentication.DelegatingAuthenticationConverter;
5556
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
57+
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
5658
import org.springframework.security.web.util.matcher.RequestMatcher;
5759
import org.springframework.util.Assert;
5860
import org.springframework.web.filter.OncePerRequestFilter;
@@ -90,6 +92,8 @@ public final class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter
9092

9193
private final AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
9294

95+
private final BasicAuthenticationEntryPoint basicAuthenticationEntryPoint = new BasicAuthenticationEntryPoint();
96+
9397
private AuthenticationConverter authenticationConverter;
9498

9599
private AuthenticationSuccessHandler authenticationSuccessHandler = this::onAuthenticationSuccess;
@@ -110,6 +114,7 @@ public OAuth2ClientAuthenticationFilter(AuthenticationManager authenticationMana
110114
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
111115
this.authenticationManager = authenticationManager;
112116
this.requestMatcher = requestMatcher;
117+
this.basicAuthenticationEntryPoint.setRealmName("default");
113118
// @formatter:off
114119
this.authenticationConverter = new DelegatingAuthenticationConverter(
115120
Arrays.asList(
@@ -130,8 +135,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
130135
return;
131136
}
132137

138+
Authentication authenticationRequest = null;
133139
try {
134-
Authentication authenticationRequest = this.authenticationConverter.convert(request);
140+
authenticationRequest = this.authenticationConverter.convert(request);
135141
if (authenticationRequest instanceof AbstractAuthenticationToken authenticationToken) {
136142
authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request));
137143
}
@@ -147,7 +153,14 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
147153
if (this.logger.isTraceEnabled()) {
148154
this.logger.trace(LogMessage.format("Client authentication failed: %s", ex.getError()), ex);
149155
}
150-
this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
156+
if (authenticationRequest instanceof OAuth2ClientAuthenticationToken clientAuthentication) {
157+
this.authenticationFailureHandler.onAuthenticationFailure(request, response,
158+
new OAuth2ClientAuthenticationException(ex.getError(), ex, clientAuthentication));
159+
}
160+
else {
161+
this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
162+
}
163+
151164
}
152165
}
153166

@@ -199,21 +212,21 @@ private void onAuthenticationSuccess(HttpServletRequest request, HttpServletResp
199212
}
200213

201214
private void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
202-
AuthenticationException exception) throws IOException {
215+
AuthenticationException authenticationException) throws IOException {
203216

204217
SecurityContextHolder.clearContext();
205218

206-
// TODO
207-
// The authorization server MAY return an HTTP 401 (Unauthorized) status code
208-
// to indicate which HTTP authentication schemes are supported.
209-
// If the client attempted to authenticate via the "Authorization" request header
210-
// field,
211-
// the authorization server MUST respond with an HTTP 401 (Unauthorized) status
212-
// code and
213-
// include the "WWW-Authenticate" response header field
214-
// matching the authentication scheme used by the client.
215-
216-
OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
219+
if (authenticationException instanceof OAuth2ClientAuthenticationException clientAuthenticationException) {
220+
OAuth2ClientAuthenticationToken clientAuthentication = clientAuthenticationException
221+
.getClientAuthentication();
222+
if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC
223+
.equals(clientAuthentication.getClientAuthenticationMethod())) {
224+
this.basicAuthenticationEntryPoint.commence(request, response, authenticationException);
225+
return;
226+
}
227+
}
228+
229+
OAuth2Error error = ((OAuth2AuthenticationException) authenticationException).getError();
217230
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
218231
if (OAuth2ErrorCodes.INVALID_CLIENT.equals(error.getErrorCode())) {
219232
httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
@@ -248,4 +261,21 @@ private static void validateClientIdentifier(Authentication authentication) {
248261
}
249262
}
250263

264+
private static final class OAuth2ClientAuthenticationException extends OAuth2AuthenticationException {
265+
266+
private final OAuth2ClientAuthenticationToken clientAuthentication;
267+
268+
private OAuth2ClientAuthenticationException(OAuth2Error error, Throwable cause,
269+
OAuth2ClientAuthenticationToken clientAuthentication) {
270+
super(error, cause);
271+
Assert.notNull(clientAuthentication, "clientAuthentication cannot be null");
272+
this.clientAuthentication = clientAuthentication;
273+
}
274+
275+
private OAuth2ClientAuthenticationToken getClientAuthentication() {
276+
return this.clientAuthentication;
277+
}
278+
279+
}
280+
251281
}

oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ public void requestWhenPublicClientWithPkceAndEmptyCodeThenBadRequest() throws E
550550
}
551551

552552
@Test
553-
public void requestWhenConfidentialClientWithPkceAndMissingCodeVerifierThenBadRequest() throws Exception {
553+
public void requestWhenConfidentialClientWithPkceAndMissingCodeVerifierThenUnauthorized() throws Exception {
554554
this.spring.register(AuthorizationServerConfiguration.class).autowire();
555555

556556
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
@@ -581,7 +581,7 @@ public void requestWhenConfidentialClientWithPkceAndMissingCodeVerifierThenBadRe
581581
.params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization))
582582
.param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
583583
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient)))
584-
.andExpect(status().isBadRequest());
584+
.andExpect(status().isUnauthorized());
585585
}
586586

587587
// gh-1011
@@ -613,7 +613,7 @@ public void requestWhenConfidentialClientWithPkceAndMissingCodeChallengeThenErro
613613
}
614614

615615
@Test
616-
public void requestWhenConfidentialClientWithPkceAndMissingCodeChallengeButCodeVerifierProvidedThenBadRequest()
616+
public void requestWhenConfidentialClientWithPkceAndMissingCodeChallengeButCodeVerifierProvidedThenUnauthorized()
617617
throws Exception {
618618
this.spring.register(AuthorizationServerConfiguration.class).autowire();
619619

@@ -643,7 +643,7 @@ public void requestWhenConfidentialClientWithPkceAndMissingCodeChallengeButCodeV
643643
.params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization))
644644
.param(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER)
645645
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient)))
646-
.andExpect(status().isBadRequest());
646+
.andExpect(status().isUnauthorized());
647647
}
648648

649649
@Test

oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilterTests.java

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2022 the original author or authors.
2+
* Copyright 2020-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import org.junit.jupiter.api.Test;
2727
import org.mockito.ArgumentCaptor;
2828

29+
import org.springframework.http.HttpHeaders;
2930
import org.springframework.http.HttpMethod;
3031
import org.springframework.http.HttpStatus;
3132
import org.springframework.http.converter.HttpMessageConverter;
@@ -175,26 +176,25 @@ public void doFilterWhenRequestMatchesAndInvalidCredentialsThenInvalidRequestErr
175176

176177
// gh-889
177178
@Test
178-
public void doFilterWhenRequestMatchesAndClientIdContainsNonPrintableASCIIThenInvalidRequestError()
179-
throws Exception {
179+
public void doFilterWhenRequestMatchesAndClientIdContainsNonPrintableASCIIThenReturnChallenge() throws Exception {
180180
// Hex 00 -> null
181181
String clientId = new String(Hex.decode("00"), StandardCharsets.UTF_8);
182-
assertWhenInvalidClientIdThenInvalidRequestError(clientId);
182+
assertWhenInvalidClientIdThenReturnChallenge(clientId);
183183

184184
// Hex 0a61 -> line feed + a
185185
clientId = new String(Hex.decode("0a61"), StandardCharsets.UTF_8);
186-
assertWhenInvalidClientIdThenInvalidRequestError(clientId);
186+
assertWhenInvalidClientIdThenReturnChallenge(clientId);
187187

188188
// Hex 1b -> escape
189189
clientId = new String(Hex.decode("1b"), StandardCharsets.UTF_8);
190-
assertWhenInvalidClientIdThenInvalidRequestError(clientId);
190+
assertWhenInvalidClientIdThenReturnChallenge(clientId);
191191

192192
// Hex 1b61 -> escape + a
193193
clientId = new String(Hex.decode("1b61"), StandardCharsets.UTF_8);
194-
assertWhenInvalidClientIdThenInvalidRequestError(clientId);
194+
assertWhenInvalidClientIdThenReturnChallenge(clientId);
195195
}
196196

197-
private void assertWhenInvalidClientIdThenInvalidRequestError(String clientId) throws Exception {
197+
private void assertWhenInvalidClientIdThenReturnChallenge(String clientId) throws Exception {
198198
given(this.authenticationConverter.convert(any(HttpServletRequest.class)))
199199
.willReturn(new OAuth2ClientAuthenticationToken(clientId, ClientAuthenticationMethod.CLIENT_SECRET_BASIC,
200200
"secret", null));
@@ -210,13 +210,12 @@ private void assertWhenInvalidClientIdThenInvalidRequestError(String clientId) t
210210
verifyNoInteractions(this.authenticationManager);
211211

212212
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
213-
assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
214-
OAuth2Error error = readError(response);
215-
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
213+
assertThat(response.getStatus()).isEqualTo(HttpStatus.UNAUTHORIZED.value());
214+
assertThat(response.getHeader(HttpHeaders.WWW_AUTHENTICATE)).isEqualTo("Basic realm=\"default\"");
216215
}
217216

218217
@Test
219-
public void doFilterWhenRequestMatchesAndBadCredentialsThenInvalidClientError() throws Exception {
218+
public void doFilterWhenRequestMatchesAndBadCredentialsThenReturnChallenge() throws Exception {
220219
given(this.authenticationConverter.convert(any(HttpServletRequest.class)))
221220
.willReturn(new OAuth2ClientAuthenticationToken("clientId", ClientAuthenticationMethod.CLIENT_SECRET_BASIC,
222221
"invalid-secret", null));
@@ -235,8 +234,7 @@ public void doFilterWhenRequestMatchesAndBadCredentialsThenInvalidClientError()
235234

236235
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull();
237236
assertThat(response.getStatus()).isEqualTo(HttpStatus.UNAUTHORIZED.value());
238-
OAuth2Error error = readError(response);
239-
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
237+
assertThat(response.getHeader(HttpHeaders.WWW_AUTHENTICATE)).isEqualTo("Basic realm=\"default\"");
240238
}
241239

242240
@Test

0 commit comments

Comments
 (0)