Skip to content

Commit 83943b2

Browse files
committed
Proposed fix for missing WWW-Authenticate header
Current implementation does not include the WWW-Authenticate header when returning a 401 for missing/invalid credentials when attempting to access the token endpoints. This PR would change to use the standard BasicAuthenticationEntryPoint in order to populate this header correctly. Fixes-468 Signed-off-by: Lucian Holland <[email protected]>
1 parent b76300b commit 83943b2

File tree

6 files changed

+71
-85
lines changed

6 files changed

+71
-85
lines changed

Diff for: oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import org.springframework.context.event.GenericApplicationListenerAdapter;
2828
import org.springframework.context.event.SmartApplicationListener;
2929
import org.springframework.http.HttpMethod;
30-
import org.springframework.http.HttpStatus;
3130
import org.springframework.security.config.Customizer;
3231
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3332
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
@@ -48,7 +47,7 @@
4847
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
4948
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
5049
import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter;
51-
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
50+
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ServerAuthenticationEntryPoint;
5251
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
5352
import org.springframework.security.web.context.SecurityContextHolderFilter;
5453
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@@ -344,7 +343,8 @@ public void init(HttpSecurity httpSecurity) throws Exception {
344343
ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling = httpSecurity
345344
.getConfigurer(ExceptionHandlingConfigurer.class);
346345
if (exceptionHandling != null) {
347-
exceptionHandling.defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
346+
var entryPoint = new OAuth2ServerAuthenticationEntryPoint();
347+
exceptionHandling.defaultAuthenticationEntryPointFor(entryPoint,
348348
new OrRequestMatcher(getRequestMatcher(OAuth2TokenEndpointConfigurer.class),
349349
getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class),
350350
getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class),

Diff for: oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java

+2-23
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,18 @@
2929
import org.springframework.http.server.ServletServerHttpResponse;
3030
import org.springframework.security.authentication.AuthenticationManager;
3131
import org.springframework.security.core.Authentication;
32-
import org.springframework.security.core.AuthenticationException;
3332
import org.springframework.security.core.context.SecurityContextHolder;
3433
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
3534
import org.springframework.security.oauth2.core.OAuth2Error;
3635
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
3736
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
38-
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
3937
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
4038
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider;
4139
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
4240
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
4341
import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter;
4442
import org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcClientRegistrationAuthenticationConverter;
43+
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ErrorAuthenticationFailureHandler;
4544
import org.springframework.security.web.authentication.AuthenticationConverter;
4645
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
4746
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@@ -85,13 +84,11 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
8584

8685
private final HttpMessageConverter<OidcClientRegistration> clientRegistrationHttpMessageConverter = new OidcClientRegistrationHttpMessageConverter();
8786

88-
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter();
89-
9087
private AuthenticationConverter authenticationConverter = new OidcClientRegistrationAuthenticationConverter();
9188

9289
private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendClientRegistrationResponse;
9390

94-
private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse;
91+
private AuthenticationFailureHandler authenticationFailureHandler = new OAuth2ErrorAuthenticationFailureHandler();
9592

9693
/**
9794
* Constructs an {@code OidcClientRegistrationEndpointFilter} using the provided
@@ -224,22 +221,4 @@ private void sendClientRegistrationResponse(HttpServletRequest request, HttpServ
224221
this.clientRegistrationHttpMessageConverter.write(clientRegistration, null, httpResponse);
225222
}
226223

227-
private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response,
228-
AuthenticationException authenticationException) throws IOException {
229-
OAuth2Error error = ((OAuth2AuthenticationException) authenticationException).getError();
230-
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
231-
if (OAuth2ErrorCodes.INVALID_TOKEN.equals(error.getErrorCode())) {
232-
httpStatus = HttpStatus.UNAUTHORIZED;
233-
}
234-
else if (OAuth2ErrorCodes.INSUFFICIENT_SCOPE.equals(error.getErrorCode())) {
235-
httpStatus = HttpStatus.FORBIDDEN;
236-
}
237-
else if (OAuth2ErrorCodes.INVALID_CLIENT.equals(error.getErrorCode())) {
238-
httpStatus = HttpStatus.UNAUTHORIZED;
239-
}
240-
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
241-
httpResponse.setStatusCode(httpStatus);
242-
this.errorHttpResponseConverter.write(error, null, httpResponse);
243-
}
244-
245224
}

Diff for: oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcUserInfoEndpointFilter.java

+2-21
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,19 @@
2424

2525
import org.springframework.core.log.LogMessage;
2626
import org.springframework.http.HttpMethod;
27-
import org.springframework.http.HttpStatus;
2827
import org.springframework.http.converter.HttpMessageConverter;
2928
import org.springframework.http.server.ServletServerHttpResponse;
3029
import org.springframework.security.authentication.AuthenticationManager;
3130
import org.springframework.security.core.Authentication;
32-
import org.springframework.security.core.AuthenticationException;
3331
import org.springframework.security.core.context.SecurityContextHolder;
3432
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
3533
import org.springframework.security.oauth2.core.OAuth2Error;
3634
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
37-
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
3835
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
3936
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationProvider;
4037
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
4138
import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcUserInfoHttpMessageConverter;
39+
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ErrorAuthenticationFailureHandler;
4240
import org.springframework.security.web.authentication.AuthenticationConverter;
4341
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
4442
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@@ -73,13 +71,11 @@ public final class OidcUserInfoEndpointFilter extends OncePerRequestFilter {
7371

7472
private final HttpMessageConverter<OidcUserInfo> userInfoHttpMessageConverter = new OidcUserInfoHttpMessageConverter();
7573

76-
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter();
77-
7874
private AuthenticationConverter authenticationConverter = this::createAuthentication;
7975

8076
private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendUserInfoResponse;
8177

82-
private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse;
78+
private AuthenticationFailureHandler authenticationFailureHandler = new OAuth2ErrorAuthenticationFailureHandler();
8379

8480
/**
8581
* Constructs an {@code OidcUserInfoEndpointFilter} using the provided parameters.
@@ -193,19 +189,4 @@ private void sendUserInfoResponse(HttpServletRequest request, HttpServletRespons
193189
this.userInfoHttpMessageConverter.write(userInfoAuthenticationToken.getUserInfo(), null, httpResponse);
194190
}
195191

196-
private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response,
197-
AuthenticationException authenticationException) throws IOException {
198-
OAuth2Error error = ((OAuth2AuthenticationException) authenticationException).getError();
199-
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
200-
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_TOKEN)) {
201-
httpStatus = HttpStatus.UNAUTHORIZED;
202-
}
203-
else if (error.getErrorCode().equals(OAuth2ErrorCodes.INSUFFICIENT_SCOPE)) {
204-
httpStatus = HttpStatus.FORBIDDEN;
205-
}
206-
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
207-
httpResponse.setStatusCode(httpStatus);
208-
this.errorHttpResponseConverter.write(error, null, httpResponse);
209-
}
210-
211192
}

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

+2-37
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,15 @@
2424
import jakarta.servlet.http.HttpServletResponse;
2525

2626
import org.springframework.core.log.LogMessage;
27-
import org.springframework.http.HttpStatus;
28-
import org.springframework.http.converter.HttpMessageConverter;
29-
import org.springframework.http.server.ServletServerHttpResponse;
3027
import org.springframework.security.authentication.AbstractAuthenticationToken;
3128
import org.springframework.security.authentication.AuthenticationDetailsSource;
3229
import org.springframework.security.authentication.AuthenticationManager;
3330
import org.springframework.security.core.Authentication;
34-
import org.springframework.security.core.AuthenticationException;
3531
import org.springframework.security.core.context.SecurityContext;
3632
import org.springframework.security.core.context.SecurityContextHolder;
3733
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
3834
import org.springframework.security.oauth2.core.OAuth2Error;
3935
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
40-
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
4136
import org.springframework.security.oauth2.server.authorization.authentication.ClientSecretAuthenticationProvider;
4237
import org.springframework.security.oauth2.server.authorization.authentication.JwtClientAssertionAuthenticationProvider;
4338
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
@@ -46,6 +41,7 @@
4641
import org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretBasicAuthenticationConverter;
4742
import org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretPostAuthenticationConverter;
4843
import org.springframework.security.oauth2.server.authorization.web.authentication.JwtClientAssertionAuthenticationConverter;
44+
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ErrorAuthenticationFailureHandler;
4945
import org.springframework.security.oauth2.server.authorization.web.authentication.PublicClientAuthenticationConverter;
5046
import org.springframework.security.oauth2.server.authorization.web.authentication.X509ClientCertificateAuthenticationConverter;
5147
import org.springframework.security.web.authentication.AuthenticationConverter;
@@ -86,15 +82,13 @@ public final class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter
8682

8783
private final RequestMatcher requestMatcher;
8884

89-
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter();
90-
9185
private final AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
9286

9387
private AuthenticationConverter authenticationConverter;
9488

9589
private AuthenticationSuccessHandler authenticationSuccessHandler = this::onAuthenticationSuccess;
9690

97-
private AuthenticationFailureHandler authenticationFailureHandler = this::onAuthenticationFailure;
91+
private AuthenticationFailureHandler authenticationFailureHandler = new OAuth2ErrorAuthenticationFailureHandler();
9892

9993
/**
10094
* Constructs an {@code OAuth2ClientAuthenticationFilter} using the provided
@@ -199,35 +193,6 @@ private void onAuthenticationSuccess(HttpServletRequest request, HttpServletResp
199193
}
200194
}
201195

202-
private void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
203-
AuthenticationException exception) throws IOException {
204-
205-
SecurityContextHolder.clearContext();
206-
207-
// TODO
208-
// The authorization server MAY return an HTTP 401 (Unauthorized) status code
209-
// to indicate which HTTP authentication schemes are supported.
210-
// If the client attempted to authenticate via the "Authorization" request header
211-
// field,
212-
// the authorization server MUST respond with an HTTP 401 (Unauthorized) status
213-
// code and
214-
// include the "WWW-Authenticate" response header field
215-
// matching the authentication scheme used by the client.
216-
217-
OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
218-
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
219-
if (OAuth2ErrorCodes.INVALID_CLIENT.equals(error.getErrorCode())) {
220-
httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
221-
}
222-
else {
223-
httpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
224-
}
225-
// We don't want to reveal too much information to the caller so just return the
226-
// error code
227-
OAuth2Error errorResponse = new OAuth2Error(error.getErrorCode());
228-
this.errorHttpResponseConverter.write(errorResponse, null, httpResponse);
229-
}
230-
231196
private static void validateClientIdentifier(Authentication authentication) {
232197
if (!(authentication instanceof OAuth2ClientAuthenticationToken)) {
233198
return;

Diff for: oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ErrorAuthenticationFailureHandler.java

+21-1
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@
2727
import org.springframework.http.converter.HttpMessageConverter;
2828
import org.springframework.http.server.ServletServerHttpResponse;
2929
import org.springframework.security.core.AuthenticationException;
30+
import org.springframework.security.core.context.SecurityContextHolder;
3031
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
3132
import org.springframework.security.oauth2.core.OAuth2Error;
33+
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
3234
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
3335
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
3436
import org.springframework.util.Assert;
@@ -49,17 +51,27 @@ public final class OAuth2ErrorAuthenticationFailureHandler implements Authentica
4951

5052
private HttpMessageConverter<OAuth2Error> errorResponseConverter = new OAuth2ErrorHttpMessageConverter();
5153

54+
private final String realmName = "oauth2"; // TODO configure this properly
55+
5256
@Override
5357
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
5458
AuthenticationException authenticationException) throws IOException, ServletException {
59+
60+
SecurityContextHolder.clearContext();
61+
5562
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
56-
httpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
5763

5864
if (authenticationException instanceof OAuth2AuthenticationException) {
5965
OAuth2Error error = ((OAuth2AuthenticationException) authenticationException).getError();
66+
var status = getHttpStatus(error);
67+
httpResponse.setStatusCode(status);
68+
if (status == HttpStatus.UNAUTHORIZED && this.realmName != null) {
69+
httpResponse.getHeaders().set("WWW-Authenticate", "Basic realm=\"" + this.realmName + "\"");
70+
}
6071
this.errorResponseConverter.write(error, null, httpResponse);
6172
}
6273
else {
74+
httpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
6375
if (this.logger.isWarnEnabled()) {
6476
this.logger.warn(AuthenticationException.class.getSimpleName() + " must be of type "
6577
+ OAuth2AuthenticationException.class.getName() + " but was "
@@ -68,6 +80,14 @@ public void onAuthenticationFailure(HttpServletRequest request, HttpServletRespo
6880
}
6981
}
7082

83+
private HttpStatus getHttpStatus(OAuth2Error error) {
84+
return switch (error.getErrorCode()) {
85+
case OAuth2ErrorCodes.INVALID_CLIENT, OAuth2ErrorCodes.INVALID_TOKEN -> HttpStatus.UNAUTHORIZED;
86+
case OAuth2ErrorCodes.INSUFFICIENT_SCOPE, OAuth2ErrorCodes.ACCESS_DENIED -> HttpStatus.FORBIDDEN;
87+
default -> HttpStatus.BAD_REQUEST;
88+
};
89+
}
90+
7191
/**
7292
* Sets the {@link HttpMessageConverter} used for converting an {@link OAuth2Error} to
7393
* an HTTP response.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.springframework.security.oauth2.server.authorization.web.authentication;
2+
3+
import jakarta.servlet.ServletException;
4+
import jakarta.servlet.http.HttpServletRequest;
5+
import jakarta.servlet.http.HttpServletResponse;
6+
import org.springframework.http.HttpStatus;
7+
import org.springframework.security.authentication.InsufficientAuthenticationException;
8+
import org.springframework.security.core.AuthenticationException;
9+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
10+
import org.springframework.security.oauth2.core.OAuth2Error;
11+
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
12+
import org.springframework.security.web.AuthenticationEntryPoint;
13+
14+
import java.io.IOException;
15+
16+
public class OAuth2ServerAuthenticationEntryPoint implements AuthenticationEntryPoint {
17+
18+
private final OAuth2ErrorAuthenticationFailureHandler authenticationFailureHandler = new OAuth2ErrorAuthenticationFailureHandler();
19+
20+
@Override
21+
public void commence(HttpServletRequest request, HttpServletResponse response,
22+
AuthenticationException authException) throws IOException, ServletException {
23+
var convertedException = convertInsufficientAccessException(authException);
24+
25+
if (authException instanceof OAuth2AuthenticationException) {
26+
authenticationFailureHandler.onAuthenticationFailure(request, response, convertedException);
27+
}
28+
else {
29+
response.sendError(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase());
30+
}
31+
}
32+
33+
private AuthenticationException convertInsufficientAccessException(AuthenticationException authException) {
34+
if (authException instanceof InsufficientAuthenticationException) {
35+
return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT),
36+
authException.getCause());
37+
}
38+
return authException;
39+
}
40+
41+
}

0 commit comments

Comments
 (0)