Improve customizing OIDC Client Registration endpoint
Related gh-696 Closes gh-946
This commit is contained in:
committed by
Joe Grandja
parent
2ba711c83a
commit
efbfdc234c
@@ -15,29 +15,53 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||
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.web.OidcClientRegistrationEndpointFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcClientRegistrationAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
|
||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||
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.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Configurer for OpenID Connect Dynamic Client Registration 1.0 Endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.2.0
|
||||
* @see OidcConfigurer#clientRegistrationEndpoint
|
||||
* @see OidcClientRegistrationEndpointFilter
|
||||
*/
|
||||
public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAuth2Configurer {
|
||||
private RequestMatcher requestMatcher;
|
||||
private final List<AuthenticationConverter> clientRegistrationRequestConverters = new ArrayList<>();
|
||||
private Consumer<List<AuthenticationConverter>> clientRegistrationRequestConvertersConsumer = (authenticationConverters) -> {};
|
||||
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
|
||||
private Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer = (authenticationProviders) -> {};
|
||||
private AuthenticationSuccessHandler clientRegistrationResponseHandler;
|
||||
private AuthenticationFailureHandler errorResponseHandler;
|
||||
|
||||
/**
|
||||
* Restrict for internal use only.
|
||||
@@ -46,6 +70,93 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut
|
||||
super(objectPostProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationConverter} used when attempting to extract the OIDC Client Registration Request
|
||||
* from {@link HttpServletRequest} to an instance of {@link OidcClientRegistrationAuthenticationToken} used for
|
||||
* creating the Client Registration or returning the Client Read Response.
|
||||
*
|
||||
* @param clientRegistrationRequestConverter the {@link AuthenticationConverter} used when attempting to extract an
|
||||
* OIDC Client Registration Request from {@link HttpServletRequest}
|
||||
* @return the {@link OidcClientRegistrationEndpointConfigurer} for further configuration
|
||||
* @since 0.4.0
|
||||
*/
|
||||
public OidcClientRegistrationEndpointConfigurer clientRegistrationRequestConverter(
|
||||
AuthenticationConverter clientRegistrationRequestConverter) {
|
||||
Assert.notNull(clientRegistrationRequestConverter, "clientRegistrationRequestConverter cannot be null");
|
||||
this.clientRegistrationRequestConverters.add(clientRegistrationRequestConverter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code Consumer} providing access to the {@code List} of default
|
||||
* and (optionally) added {@link #clientRegistrationRequestConverter(AuthenticationConverter) AuthenticationConverter}'s
|
||||
* allowing the ability to add, remove, or customize a specific {@link AuthenticationConverter}.
|
||||
*
|
||||
* @param clientRegistrationRequestConvertersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationConverter}'s
|
||||
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
|
||||
* @since 0.4.0
|
||||
*/
|
||||
public OidcClientRegistrationEndpointConfigurer clientRegistrationRequestConverters(Consumer<List<AuthenticationConverter>> clientRegistrationRequestConvertersConsumer) {
|
||||
Assert.notNull(clientRegistrationRequestConvertersConsumer, "clientRegistrationRequestConvertersConsumer cannot be null");
|
||||
this.clientRegistrationRequestConvertersConsumer = clientRegistrationRequestConvertersConsumer;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an {@link AuthenticationProvider} used for authenticating a type of {@link OidcClientRegistrationAuthenticationToken}.
|
||||
*
|
||||
* @param authenticationProvider a {@link AuthenticationProvider} used for authenticating a type of {@link OidcClientRegistrationAuthenticationToken}
|
||||
* @return the {@link OidcClientRegistrationEndpointConfigurer} for further configuration
|
||||
* @since 0.4.0
|
||||
*/
|
||||
public OidcClientRegistrationEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
|
||||
Assert.notNull(authenticationProvider, "authenticationProvider cannot be null");
|
||||
this.authenticationProviders.add(authenticationProvider);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code Consumer} providing access to the {@code List} of default
|
||||
* and (optionally) added {@link #authenticationProvider(AuthenticationProvider) AuthenticationProvider}'s
|
||||
* allowing the ability to add, remove, or customize a specific {@link AuthenticationProvider}.
|
||||
*
|
||||
* @param authenticationProvidersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationProvider}'s
|
||||
* @return the {@link OidcClientRegistrationEndpointConfigurer} for further configuration
|
||||
* @since 0.4.0
|
||||
*/
|
||||
public OidcClientRegistrationEndpointConfigurer authenticationProviders(
|
||||
Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer) {
|
||||
Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null");
|
||||
this.authenticationProvidersConsumer = authenticationProvidersConsumer;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OidcClientRegistrationAuthenticationToken} and
|
||||
* returning the {@link OidcUserInfo User Info Response}.
|
||||
*
|
||||
* @param clientRegistrationResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OidcClientRegistrationAuthenticationToken}
|
||||
* @return the {@link OidcClientRegistrationEndpointConfigurer} for further configuration
|
||||
* @since 0.4.0
|
||||
*/
|
||||
public OidcClientRegistrationEndpointConfigurer clientRegistrationResponseHandler(AuthenticationSuccessHandler clientRegistrationResponseHandler) {
|
||||
this.clientRegistrationResponseHandler = clientRegistrationResponseHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException} and
|
||||
* returning the {@link OAuth2Error Error Response}.
|
||||
*
|
||||
* @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
|
||||
* @return the {@link OidcClientRegistrationEndpointConfigurer} for further configuration
|
||||
* @since 0.4.0
|
||||
*/
|
||||
public OidcClientRegistrationEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
|
||||
this.errorResponseHandler = errorResponseHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
@@ -54,18 +165,15 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut
|
||||
new AntPathRequestMatcher(authorizationServerSettings.getOidcClientRegistrationEndpoint(), HttpMethod.GET.name())
|
||||
);
|
||||
|
||||
OidcClientRegistrationAuthenticationProvider oidcClientRegistrationAuthenticationProvider =
|
||||
new OidcClientRegistrationAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity),
|
||||
OAuth2ConfigurerUtils.getTokenGenerator(httpSecurity));
|
||||
httpSecurity.authenticationProvider(postProcess(oidcClientRegistrationAuthenticationProvider));
|
||||
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
|
||||
|
||||
OidcClientConfigurationAuthenticationProvider oidcClientConfigurationAuthenticationProvider =
|
||||
new OidcClientConfigurationAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity));
|
||||
httpSecurity.authenticationProvider(postProcess(oidcClientConfigurationAuthenticationProvider));
|
||||
if (!this.authenticationProviders.isEmpty()) {
|
||||
authenticationProviders.addAll(0, this.authenticationProviders);
|
||||
}
|
||||
this.authenticationProvidersConsumer.accept(authenticationProviders);
|
||||
|
||||
authenticationProviders.forEach(authenticationProvider ->
|
||||
httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,6 +185,22 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut
|
||||
new OidcClientRegistrationEndpointFilter(
|
||||
authenticationManager,
|
||||
authorizationServerSettings.getOidcClientRegistrationEndpoint());
|
||||
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.clientRegistrationRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.clientRegistrationRequestConverters);
|
||||
}
|
||||
this.clientRegistrationRequestConvertersConsumer.accept(authenticationConverters);
|
||||
oidcClientRegistrationEndpointFilter.setAuthenticationConverter(
|
||||
new DelegatingAuthenticationConverter(authenticationConverters));
|
||||
|
||||
if (this.clientRegistrationResponseHandler != null) {
|
||||
oidcClientRegistrationEndpointFilter
|
||||
.setAuthenticationSuccessHandler(this.clientRegistrationResponseHandler);
|
||||
}
|
||||
if (this.errorResponseHandler != null) {
|
||||
oidcClientRegistrationEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
|
||||
}
|
||||
httpSecurity.addFilterAfter(postProcess(oidcClientRegistrationEndpointFilter), FilterSecurityInterceptor.class);
|
||||
}
|
||||
|
||||
@@ -85,4 +209,28 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut
|
||||
return this.requestMatcher;
|
||||
}
|
||||
|
||||
private static List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
|
||||
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
|
||||
|
||||
OidcClientRegistrationAuthenticationProvider oidcClientRegistrationAuthenticationProvider =
|
||||
new OidcClientRegistrationAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity),
|
||||
OAuth2ConfigurerUtils.getTokenGenerator(httpSecurity));
|
||||
authenticationProviders.add(oidcClientRegistrationAuthenticationProvider);
|
||||
|
||||
OidcClientConfigurationAuthenticationProvider oidcClientConfigurationAuthenticationProvider =
|
||||
new OidcClientConfigurationAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity));
|
||||
authenticationProviders.add(oidcClientConfigurationAuthenticationProvider);
|
||||
return authenticationProviders;
|
||||
}
|
||||
|
||||
private static List<AuthenticationConverter> createDefaultAuthenticationConverters() {
|
||||
List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
|
||||
authenticationConverters.add(new OidcClientRegistrationAuthenticationConverter());
|
||||
return authenticationConverters;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ 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;
|
||||
@@ -40,6 +42,8 @@ import org.springframework.security.oauth2.server.authorization.oidc.authenticat
|
||||
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.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
@@ -53,6 +57,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
||||
*
|
||||
* @author Ovidiu Popa
|
||||
* @author Joe Grandja
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.1
|
||||
* @see OidcClientRegistration
|
||||
* @see OidcClientRegistrationAuthenticationConverter
|
||||
@@ -69,11 +74,13 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final RequestMatcher clientRegistrationEndpointMatcher;
|
||||
private AuthenticationConverter authenticationConverter = new OidcClientRegistrationAuthenticationConverter();
|
||||
private final HttpMessageConverter<OidcClientRegistration> clientRegistrationHttpMessageConverter =
|
||||
new OidcClientRegistrationHttpMessageConverter();
|
||||
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
|
||||
new OAuth2ErrorHttpMessageConverter();
|
||||
private AuthenticationConverter authenticationConverter;
|
||||
private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendClientRegistrationResponse;
|
||||
private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OidcClientRegistrationEndpointFilter} using the provided parameters.
|
||||
@@ -99,7 +106,6 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
|
||||
new AntPathRequestMatcher(
|
||||
clientRegistrationEndpointUri, HttpMethod.POST.name()),
|
||||
createClientConfigurationMatcher(clientRegistrationEndpointUri));
|
||||
this.authenticationConverter = new OidcClientRegistrationAuthenticationConverter();
|
||||
}
|
||||
|
||||
private static RequestMatcher createClientConfigurationMatcher(String clientRegistrationEndpointUri) {
|
||||
@@ -130,33 +136,77 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
|
||||
OidcClientRegistrationAuthenticationToken clientRegistrationAuthenticationResult =
|
||||
(OidcClientRegistrationAuthenticationToken) this.authenticationManager.authenticate(clientRegistrationAuthentication);
|
||||
|
||||
HttpStatus httpStatus = HttpStatus.OK;
|
||||
if (clientRegistrationAuthentication.getClientRegistration() != null) {
|
||||
httpStatus = HttpStatus.CREATED;
|
||||
}
|
||||
|
||||
sendClientRegistrationResponse(response, httpStatus, clientRegistrationAuthenticationResult.getClientRegistration());
|
||||
|
||||
this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, clientRegistrationAuthenticationResult);
|
||||
} catch (OAuth2AuthenticationException ex) {
|
||||
sendErrorResponse(response, ex.getError());
|
||||
this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
|
||||
} catch (Exception ex) {
|
||||
OAuth2Error error = new OAuth2Error(
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
"OpenID Client Registration Error: " + ex.getMessage(),
|
||||
"https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError");
|
||||
sendErrorResponse(response, error);
|
||||
this.authenticationFailureHandler.onAuthenticationFailure(request, response,
|
||||
new OAuth2AuthenticationException(error));
|
||||
} finally {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendClientRegistrationResponse(HttpServletResponse response, HttpStatus httpStatus, OidcClientRegistration clientRegistration) throws IOException {
|
||||
/**
|
||||
* Sets the {@link AuthenticationConverter} used when attempting to extract the OIDC Client Registration Request
|
||||
* from {@link HttpServletRequest} to an instance of {@link OidcClientRegistrationAuthenticationToken} used for
|
||||
* creating the Client Registration or returning the Client Read Response.
|
||||
*
|
||||
* @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract an
|
||||
* OIDC Client Registration Request from {@link HttpServletRequest}
|
||||
* @since 0.4.0
|
||||
*/
|
||||
public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
|
||||
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||
this.authenticationConverter = authenticationConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OidcClientRegistrationAuthenticationToken}
|
||||
* and returning the {@link OidcClientRegistration Client Registration Response}.
|
||||
*
|
||||
* @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OidcClientRegistrationAuthenticationToken}
|
||||
* @see 0.4.0
|
||||
*/
|
||||
public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
|
||||
Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null");
|
||||
this.authenticationSuccessHandler = authenticationSuccessHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationFailureHandler} used for handling an
|
||||
* {@link OAuth2AuthenticationException} and returning the {@link OAuth2Error Error
|
||||
* Response}.
|
||||
* @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used
|
||||
* for handling an {@link OAuth2AuthenticationException}
|
||||
* @since 0.4.0
|
||||
*/
|
||||
public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
|
||||
Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
|
||||
this.authenticationFailureHandler = authenticationFailureHandler;
|
||||
}
|
||||
|
||||
private void sendClientRegistrationResponse(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication authentication) throws IOException {
|
||||
OidcClientRegistration clientRegistration = ((OidcClientRegistrationAuthenticationToken) authentication)
|
||||
.getClientRegistration();
|
||||
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
||||
httpResponse.setStatusCode(httpStatus);
|
||||
if (HttpMethod.POST.name().equals(request.getMethod())) {
|
||||
httpResponse.setStatusCode(HttpStatus.CREATED);
|
||||
}
|
||||
else {
|
||||
httpResponse.setStatusCode(HttpStatus.OK);
|
||||
}
|
||||
this.clientRegistrationHttpMessageConverter.write(clientRegistration, null, httpResponse);
|
||||
}
|
||||
|
||||
private void sendErrorResponse(HttpServletResponse response, OAuth2Error error) throws IOException {
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user