Skip to content

Commit 987a8c8

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. Fixes-468 Signed-off-by: Lucian Holland <lucian@patientsknowbest.com>
1 parent b76300b commit 987a8c8

3 files changed

Lines changed: 30 additions & 18 deletions

File tree

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientAuthenticationConfigurer.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Co
7979

8080
private AuthenticationFailureHandler errorResponseHandler;
8181

82+
private String realmName = "oauth";
83+
8284
/**
8385
* Restrict for internal use only.
8486
* @param objectPostProcessor an {@code ObjectPostProcessor}
@@ -102,6 +104,18 @@ public OAuth2ClientAuthenticationConfigurer authenticationConverter(
102104
return this;
103105
}
104106

107+
/**
108+
* Sets the realm name for Http Basic when returning a WWW-Authenticate header on
109+
* client authentication failure.
110+
* @param realmName the Http Basic realm name
111+
* @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
112+
*/
113+
public OAuth2ClientAuthenticationConfigurer realmName(String realmName) {
114+
Assert.hasText(realmName, "realmName cannot be empty");
115+
this.realmName = realmName;
116+
return this;
117+
}
118+
105119
/**
106120
* Sets the {@code Consumer} providing access to the {@code List} of default and
107121
* (optionally) added {@link #authenticationConverter(AuthenticationConverter)
@@ -213,7 +227,7 @@ void init(HttpSecurity httpSecurity) {
213227
void configure(HttpSecurity httpSecurity) {
214228
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
215229
OAuth2ClientAuthenticationFilter clientAuthenticationFilter = new OAuth2ClientAuthenticationFilter(
216-
authenticationManager, this.requestMatcher);
230+
authenticationManager, this.requestMatcher, this.realmName);
217231
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
218232
if (!this.authenticationConverters.isEmpty()) {
219233
authenticationConverters.addAll(0, this.authenticationConverters);

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

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ public final class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter
9090

9191
private final AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
9292

93+
private final String realmName;
94+
9395
private AuthenticationConverter authenticationConverter;
9496

9597
private AuthenticationSuccessHandler authenticationSuccessHandler = this::onAuthenticationSuccess;
@@ -104,10 +106,12 @@ public final class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter
104106
* @param requestMatcher the {@link RequestMatcher} used for matching against the
105107
* {@code HttpServletRequest}
106108
*/
107-
public OAuth2ClientAuthenticationFilter(AuthenticationManager authenticationManager,
108-
RequestMatcher requestMatcher) {
109+
public OAuth2ClientAuthenticationFilter(AuthenticationManager authenticationManager, RequestMatcher requestMatcher,
110+
String realmName) {
111+
this.realmName = realmName;
109112
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
110113
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
114+
Assert.notNull(realmName, "realmName cannot be null");
111115
this.authenticationManager = authenticationManager;
112116
this.requestMatcher = requestMatcher;
113117
// @formatter:off
@@ -140,9 +144,12 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
140144
validateClientIdentifier(authenticationRequest);
141145
Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest);
142146
this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authenticationResult);
147+
filterChain.doFilter(request, response);
148+
}
149+
else {
150+
this.authenticationFailureHandler.onAuthenticationFailure(request, response,
151+
new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT));
143152
}
144-
filterChain.doFilter(request, response);
145-
146153
}
147154
catch (OAuth2AuthenticationException ex) {
148155
if (this.logger.isTraceEnabled()) {
@@ -204,20 +211,11 @@ private void onAuthenticationFailure(HttpServletRequest request, HttpServletResp
204211

205212
SecurityContextHolder.clearContext();
206213

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-
217214
OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
218215
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
219216
if (OAuth2ErrorCodes.INVALID_CLIENT.equals(error.getErrorCode())) {
220217
httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
218+
httpResponse.getHeaders().set("WWW-Authenticate", "Basic realm=\"" + this.realmName + "\"");
221219
}
222220
else {
223221
httpResponse.setStatusCode(HttpStatus.BAD_REQUEST);

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public class OAuth2ClientAuthenticationFilterTests {
8181
public void setUp() {
8282
this.authenticationManager = mock(AuthenticationManager.class);
8383
this.requestMatcher = new AntPathRequestMatcher(this.filterProcessesUrl, HttpMethod.POST.name());
84-
this.filter = new OAuth2ClientAuthenticationFilter(this.authenticationManager, this.requestMatcher);
84+
this.filter = new OAuth2ClientAuthenticationFilter(this.authenticationManager, this.requestMatcher, "realm");
8585
this.authenticationConverter = mock(AuthenticationConverter.class);
8686
this.filter.setAuthenticationConverter(this.authenticationConverter);
8787
}
@@ -93,14 +93,14 @@ public void cleanup() {
9393

9494
@Test
9595
public void constructorWhenAuthenticationManagerNullThenThrowIllegalArgumentException() {
96-
assertThatThrownBy(() -> new OAuth2ClientAuthenticationFilter(null, this.requestMatcher))
96+
assertThatThrownBy(() -> new OAuth2ClientAuthenticationFilter(null, this.requestMatcher, "realm"))
9797
.isInstanceOf(IllegalArgumentException.class)
9898
.hasMessage("authenticationManager cannot be null");
9999
}
100100

101101
@Test
102102
public void constructorWhenRequestMatcherNullThenThrowIllegalArgumentException() {
103-
assertThatThrownBy(() -> new OAuth2ClientAuthenticationFilter(this.authenticationManager, null))
103+
assertThatThrownBy(() -> new OAuth2ClientAuthenticationFilter(this.authenticationManager, null, "realm"))
104104
.isInstanceOf(IllegalArgumentException.class)
105105
.hasMessage("requestMatcher cannot be null");
106106
}

0 commit comments

Comments
 (0)