Skip to content

Commit

Permalink
draft: initial implementation of customization functions in auth erro…
Browse files Browse the repository at this point in the history
…r response handler

fixes gh-1369
  • Loading branch information
ddubson committed Jan 19, 2024
1 parent 01441f9 commit 25ac17c
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2022 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.
Expand All @@ -15,33 +15,29 @@
*/
package org.springframework.security.oauth2.server.authorization.oidc.web;

import java.io.IOException;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.core.log.LogMessage;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcClientRegistrationAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ErrorAuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
Expand All @@ -53,12 +49,16 @@
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.function.BiConsumer;

/**
* A {@code Filter} that processes OpenID Connect 1.0 Dynamic Client Registration (and Client Read) Requests.
*
* @author Ovidiu Popa
* @author Joe Grandja
* @author Daniel Garnier-Moiroux
* @author Dmitriy Dubson
* @since 0.1.1
* @see OidcClientRegistration
* @see OidcClientRegistrationAuthenticationConverter
Expand All @@ -77,11 +77,21 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
private final RequestMatcher clientRegistrationEndpointMatcher;
private final HttpMessageConverter<OidcClientRegistration> clientRegistrationHttpMessageConverter =
new OidcClientRegistrationHttpMessageConverter();
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
new OAuth2ErrorHttpMessageConverter();
private AuthenticationConverter authenticationConverter = new OidcClientRegistrationAuthenticationConverter();
private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendClientRegistrationResponse;
private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse;
BiConsumer<OAuth2AuthenticationException, ServletServerHttpResponse> authenticationFailureHttpResponseCustomizer = (e, httpResponse) -> {
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
if (OAuth2ErrorCodes.INVALID_TOKEN.equals(e.getError().getErrorCode())) {
httpStatus = HttpStatus.UNAUTHORIZED;
} else if (OAuth2ErrorCodes.INSUFFICIENT_SCOPE.equals(e.getError().getErrorCode())) {
httpStatus = HttpStatus.FORBIDDEN;
} else if (OAuth2ErrorCodes.INVALID_CLIENT.equals(e.getError().getErrorCode())) {
httpStatus = HttpStatus.UNAUTHORIZED;
}
httpResponse.setStatusCode(httpStatus);
};

private AuthenticationFailureHandler authenticationFailureHandler = new OAuth2ErrorAuthenticationFailureHandler();

/**
* Constructs an {@code OidcClientRegistrationEndpointFilter} using the provided parameters.
Expand All @@ -90,6 +100,7 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
*/
public OidcClientRegistrationEndpointFilter(AuthenticationManager authenticationManager) {
this(authenticationManager, DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI);
((OAuth2ErrorAuthenticationFailureHandler) authenticationFailureHandler).setHttpResponseCustomizer(authenticationFailureHttpResponseCustomizer);
}

/**
Expand Down Expand Up @@ -206,20 +217,4 @@ private void sendClientRegistrationResponse(HttpServletRequest request, HttpServ
this.clientRegistrationHttpMessageConverter.write(clientRegistration, null, httpResponse);
}

private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authenticationException) throws IOException {
OAuth2Error error = ((OAuth2AuthenticationException) authenticationException).getError();
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
if (OAuth2ErrorCodes.INVALID_TOKEN.equals(error.getErrorCode())) {
httpStatus = HttpStatus.UNAUTHORIZED;
} else if (OAuth2ErrorCodes.INSUFFICIENT_SCOPE.equals(error.getErrorCode())) {
httpStatus = HttpStatus.FORBIDDEN;
} else if (OAuth2ErrorCodes.INVALID_CLIENT.equals(error.getErrorCode())) {
httpStatus = HttpStatus.UNAUTHORIZED;
}
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
httpResponse.setStatusCode(httpStatus);
this.errorHttpResponseConverter.write(error, null, httpResponse);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2022 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.
Expand All @@ -16,6 +16,7 @@
package org.springframework.security.oauth2.server.authorization.oidc.web;

import java.io.IOException;
import java.util.function.BiConsumer;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
Expand All @@ -29,7 +30,6 @@
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
Expand All @@ -39,6 +39,7 @@
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcUserInfoHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ErrorAuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
Expand All @@ -54,6 +55,7 @@
* @author Ido Salomon
* @author Steve Riesenberg
* @author Daniel Garnier-Moiroux
* @author Dmitriy Dubson
* @since 0.2.1
* @see OidcUserInfo
* @see OidcUserInfoAuthenticationProvider
Expand All @@ -70,11 +72,20 @@ public final class OidcUserInfoEndpointFilter extends OncePerRequestFilter {
private final RequestMatcher userInfoEndpointMatcher;
private final HttpMessageConverter<OidcUserInfo> userInfoHttpMessageConverter =
new OidcUserInfoHttpMessageConverter();
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
new OAuth2ErrorHttpMessageConverter();
private AuthenticationConverter authenticationConverter = this::createAuthentication;
private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendUserInfoResponse;
private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse;

BiConsumer<OAuth2AuthenticationException, ServletServerHttpResponse> authenticationFailureHttpResponseCustomizer = (e, httpResponse) -> {
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
if (e.getError().getErrorCode().equals(OAuth2ErrorCodes.INVALID_TOKEN)) {
httpStatus = HttpStatus.UNAUTHORIZED;
} else if (e.getError().getErrorCode().equals(OAuth2ErrorCodes.INSUFFICIENT_SCOPE)) {
httpStatus = HttpStatus.FORBIDDEN;
}
httpResponse.setStatusCode(httpStatus);
};

private AuthenticationFailureHandler authenticationFailureHandler = new OAuth2ErrorAuthenticationFailureHandler();

/**
* Constructs an {@code OidcUserInfoEndpointFilter} using the provided parameters.
Expand All @@ -83,6 +94,7 @@ public final class OidcUserInfoEndpointFilter extends OncePerRequestFilter {
*/
public OidcUserInfoEndpointFilter(AuthenticationManager authenticationManager) {
this(authenticationManager, DEFAULT_OIDC_USER_INFO_ENDPOINT_URI);
((OAuth2ErrorAuthenticationFailureHandler) authenticationFailureHandler).setHttpResponseCustomizer(authenticationFailureHttpResponseCustomizer);
}

/**
Expand Down Expand Up @@ -184,18 +196,4 @@ private void sendUserInfoResponse(HttpServletRequest request, HttpServletRespons
this.userInfoHttpMessageConverter.write(userInfoAuthenticationToken.getUserInfo(), null, httpResponse);
}

private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authenticationException) throws IOException {
OAuth2Error error = ((OAuth2AuthenticationException) authenticationException).getError();
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_TOKEN)) {
httpStatus = HttpStatus.UNAUTHORIZED;
} else if (error.getErrorCode().equals(OAuth2ErrorCodes.INSUFFICIENT_SCOPE)) {
httpStatus = HttpStatus.FORBIDDEN;
}
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
httpResponse.setStatusCode(httpStatus);
this.errorHttpResponseConverter.write(error, null, httpResponse);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2022 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.
Expand All @@ -15,17 +15,13 @@
*/
package org.springframework.security.oauth2.server.authorization.web;

import java.io.IOException;
import java.util.Arrays;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.core.convert.converter.Converter;
import org.springframework.core.log.LogMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationDetailsSource;
Expand All @@ -37,6 +33,7 @@
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.authentication.ClientSecretAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.JwtClientAssertionAuthenticationProvider;
Expand All @@ -46,6 +43,7 @@
import org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretPostAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.JwtClientAssertionAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ErrorAuthenticationFailureHandler;
import org.springframework.security.oauth2.server.authorization.web.authentication.PublicClientAuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
Expand All @@ -55,11 +53,17 @@
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.function.BiConsumer;

/**
* A {@code Filter} that processes an authentication request for an OAuth 2.0 Client.
*
* @author Joe Grandja
* @author Patryk Kostrzewa
* @author Dmitriy Dubson
* @since 0.0.1
* @see AuthenticationManager
* @see JwtClientAssertionAuthenticationConverter
Expand All @@ -75,13 +79,29 @@
public final class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter {
private final AuthenticationManager authenticationManager;
private final RequestMatcher requestMatcher;
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter();
private final AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource =
new WebAuthenticationDetailsSource();
private AuthenticationConverter authenticationConverter;
private AuthenticationSuccessHandler authenticationSuccessHandler = this::onAuthenticationSuccess;
private AuthenticationFailureHandler authenticationFailureHandler = this::onAuthenticationFailure;

private final OAuth2ErrorAuthenticationFailureHandler failureHandler = new OAuth2ErrorAuthenticationFailureHandler();

BiConsumer<OAuth2AuthenticationException, ServletServerHttpResponse> authenticationFailureHttpResponseCustomizer = (e, httpResponse) -> {
// 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.
if (OAuth2ErrorCodes.INVALID_CLIENT.equals(e.getError().getErrorCode())) {
httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
} else {
httpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
}
};

/**
* Constructs an {@code OAuth2ClientAuthenticationFilter} using the provided parameters.
*
Expand All @@ -100,6 +120,13 @@ public OAuth2ClientAuthenticationFilter(AuthenticationManager authenticationMana
new ClientSecretBasicAuthenticationConverter(),
new ClientSecretPostAuthenticationConverter(),
new PublicClientAuthenticationConverter()));

// We don't want to reveal too much information to the caller so just return the error code
Converter<OAuth2Error, Map<String, String>> errorParametersConverter = (OAuth2Error error) -> Map.of(OAuth2ParameterNames.ERROR, error.getErrorCode());
OAuth2ErrorHttpMessageConverter errorHttpMessageConverter = new OAuth2ErrorHttpMessageConverter();
errorHttpMessageConverter.setErrorParametersConverter(errorParametersConverter);

failureHandler.setHttpResponseCustomizer(authenticationFailureHttpResponseCustomizer);
}

@Override
Expand Down Expand Up @@ -178,28 +205,9 @@ private void onAuthenticationSuccess(HttpServletRequest request, HttpServletResp
}

private void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException {

AuthenticationException exception) throws IOException, ServletException {
SecurityContextHolder.clearContext();

// 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) exception).getError();
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
if (OAuth2ErrorCodes.INVALID_CLIENT.equals(error.getErrorCode())) {
httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
} else {
httpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
}
// We don't want to reveal too much information to the caller so just return the error code
OAuth2Error errorResponse = new OAuth2Error(error.getErrorCode());
this.errorHttpResponseConverter.write(errorResponse, null, httpResponse);
failureHandler.onAuthenticationFailure(request, response, exception);
}

private static void validateClientIdentifier(Authentication authentication) {
Expand Down
Loading

0 comments on commit 25ac17c

Please sign in to comment.