Skip to content

Commit ad58264

Browse files
Kehrlannjgrandja
authored andcommitted
Improve customizing OIDC Client Registration endpoint
Related spring-projectsgh-696 Closes spring-projectsgh-946
1 parent 286ecad commit ad58264

File tree

5 files changed

+498
-61
lines changed

5 files changed

+498
-61
lines changed

docs/src/docs/asciidoc/protocol-endpoints.adoc

+17-1
Original file line numberDiff line numberDiff line change
@@ -367,12 +367,26 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
367367
authorizationServerConfigurer
368368
.oidc(oidc ->
369369
oidc
370-
.clientRegistrationEndpoint(Customizer.withDefaults())
370+
.clientRegistrationEndpoint(clientRegistrationEndpoint ->
371+
clientRegistrationEndpoint
372+
.clientRegistrationRequestConverter(clientRegistrationRequestConverter) <1>
373+
.clientRegistrationRequestConverters(clientRegistrationRequestConvertersConsumers) <2>
374+
.authenticationProvider(authenticationProvider) <3>
375+
.authenticationProviders(authenticationProvidersConsumer) <4>
376+
.clientRegistrationResponseHandler(clientRegistrationResponseHandler) <5>
377+
.errorResponseHandler(errorResponseHandler) <6>
378+
)
371379
);
372380
373381
return http.build();
374382
}
375383
----
384+
<1> `clientRegistrationRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract a https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest[Client Registration Request] or https://openid.net/specs/openid-connect-registration-1_0.html#ReadRequest[Client Read Request] from `HttpServletRequest` to an instance of `OidcClientRegistrationAuthenticationToken`.
385+
<2> `clientRegistrationRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
386+
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OidcClientRegistrationAuthenticationToken`.
387+
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
388+
<5> `clientRegistrationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OidcClientRegistrationAuthenticationToken` and returning the https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse[Client Registration Response] or https://openid.net/specs/openid-connect-registration-1_0.html#ReadResponse[Client Read Response].
389+
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError[Client Registration Error Response] or https://openid.net/specs/openid-connect-registration-1_0.html#ReadError[Client Read Error Response].
376390

377391
[NOTE]
378392
The OpenID Connect 1.0 Client Registration endpoint is disabled by default because many deployments do not require dynamic client registration.
@@ -387,6 +401,8 @@ The OpenID Connect 1.0 Client Registration endpoint is disabled by default becau
387401

388402
* `*AuthenticationConverter*` -- An `OidcClientRegistrationAuthenticationConverter`.
389403
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OidcClientRegistrationAuthenticationProvider` and `OidcClientConfigurationAuthenticationProvider`.
404+
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OidcClientRegistrationAuthenticationToken` and returns the Client Registration or Client Read response.
405+
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` and returns the `OAuth2Error` response.
390406

391407
The OpenID Connect 1.0 Client Registration endpoint is an https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration[OAuth2 protected resource], which *REQUIRES* an access token to be sent as a bearer token in the Client Registration (or Client Read) request.
392408

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

+159-11
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,53 @@
1515
*/
1616
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
1717

18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.function.Consumer;
21+
22+
import javax.servlet.http.HttpServletRequest;
23+
1824
import org.springframework.http.HttpMethod;
1925
import org.springframework.security.authentication.AuthenticationManager;
26+
import org.springframework.security.authentication.AuthenticationProvider;
2027
import org.springframework.security.config.annotation.ObjectPostProcessor;
2128
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
29+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
30+
import org.springframework.security.oauth2.core.OAuth2Error;
31+
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
2232
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider;
2333
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
34+
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
2435
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
36+
import org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcClientRegistrationAuthenticationConverter;
2537
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
38+
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
2639
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
40+
import org.springframework.security.web.authentication.AuthenticationConverter;
41+
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
42+
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
2743
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
2844
import org.springframework.security.web.util.matcher.OrRequestMatcher;
2945
import org.springframework.security.web.util.matcher.RequestMatcher;
46+
import org.springframework.util.Assert;
3047

3148
/**
3249
* Configurer for OpenID Connect Dynamic Client Registration 1.0 Endpoint.
3350
*
3451
* @author Joe Grandja
52+
* @author Daniel Garnier-Moiroux
3553
* @since 0.2.0
3654
* @see OidcConfigurer#clientRegistrationEndpoint
3755
* @see OidcClientRegistrationEndpointFilter
3856
*/
3957
public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAuth2Configurer {
4058
private RequestMatcher requestMatcher;
59+
private final List<AuthenticationConverter> clientRegistrationRequestConverters = new ArrayList<>();
60+
private Consumer<List<AuthenticationConverter>> clientRegistrationRequestConvertersConsumer = (authenticationConverters) -> {};
61+
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
62+
private Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer = (authenticationProviders) -> {};
63+
private AuthenticationSuccessHandler clientRegistrationResponseHandler;
64+
private AuthenticationFailureHandler errorResponseHandler;
4165

4266
/**
4367
* Restrict for internal use only.
@@ -46,6 +70,93 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut
4670
super(objectPostProcessor);
4771
}
4872

73+
/**
74+
* Sets the {@link AuthenticationConverter} used when attempting to extract the OIDC Client Registration Request
75+
* from {@link HttpServletRequest} to an instance of {@link OidcClientRegistrationAuthenticationToken} used for
76+
* creating the Client Registration or returning the Client Read Response.
77+
*
78+
* @param clientRegistrationRequestConverter the {@link AuthenticationConverter} used when attempting to extract an
79+
* OIDC Client Registration Request from {@link HttpServletRequest}
80+
* @return the {@link OidcClientRegistrationEndpointConfigurer} for further configuration
81+
* @since 0.4.0
82+
*/
83+
public OidcClientRegistrationEndpointConfigurer clientRegistrationRequestConverter(
84+
AuthenticationConverter clientRegistrationRequestConverter) {
85+
Assert.notNull(clientRegistrationRequestConverter, "clientRegistrationRequestConverter cannot be null");
86+
this.clientRegistrationRequestConverters.add(clientRegistrationRequestConverter);
87+
return this;
88+
}
89+
90+
/**
91+
* Sets the {@code Consumer} providing access to the {@code List} of default
92+
* and (optionally) added {@link #clientRegistrationRequestConverter(AuthenticationConverter) AuthenticationConverter}'s
93+
* allowing the ability to add, remove, or customize a specific {@link AuthenticationConverter}.
94+
*
95+
* @param clientRegistrationRequestConvertersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationConverter}'s
96+
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
97+
* @since 0.4.0
98+
*/
99+
public OidcClientRegistrationEndpointConfigurer clientRegistrationRequestConverters(Consumer<List<AuthenticationConverter>> clientRegistrationRequestConvertersConsumer) {
100+
Assert.notNull(clientRegistrationRequestConvertersConsumer, "clientRegistrationRequestConvertersConsumer cannot be null");
101+
this.clientRegistrationRequestConvertersConsumer = clientRegistrationRequestConvertersConsumer;
102+
return this;
103+
}
104+
105+
/**
106+
* Adds an {@link AuthenticationProvider} used for authenticating a type of {@link OidcClientRegistrationAuthenticationToken}.
107+
*
108+
* @param authenticationProvider a {@link AuthenticationProvider} used for authenticating a type of {@link OidcClientRegistrationAuthenticationToken}
109+
* @return the {@link OidcClientRegistrationEndpointConfigurer} for further configuration
110+
* @since 0.4.0
111+
*/
112+
public OidcClientRegistrationEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
113+
Assert.notNull(authenticationProvider, "authenticationProvider cannot be null");
114+
this.authenticationProviders.add(authenticationProvider);
115+
return this;
116+
}
117+
118+
/**
119+
* Sets the {@code Consumer} providing access to the {@code List} of default
120+
* and (optionally) added {@link #authenticationProvider(AuthenticationProvider) AuthenticationProvider}'s
121+
* allowing the ability to add, remove, or customize a specific {@link AuthenticationProvider}.
122+
*
123+
* @param authenticationProvidersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationProvider}'s
124+
* @return the {@link OidcClientRegistrationEndpointConfigurer} for further configuration
125+
* @since 0.4.0
126+
*/
127+
public OidcClientRegistrationEndpointConfigurer authenticationProviders(
128+
Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer) {
129+
Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null");
130+
this.authenticationProvidersConsumer = authenticationProvidersConsumer;
131+
return this;
132+
}
133+
134+
/**
135+
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OidcClientRegistrationAuthenticationToken} and
136+
* returning the {@link OidcUserInfo User Info Response}.
137+
*
138+
* @param clientRegistrationResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OidcClientRegistrationAuthenticationToken}
139+
* @return the {@link OidcClientRegistrationEndpointConfigurer} for further configuration
140+
* @since 0.4.0
141+
*/
142+
public OidcClientRegistrationEndpointConfigurer clientRegistrationResponseHandler(AuthenticationSuccessHandler clientRegistrationResponseHandler) {
143+
this.clientRegistrationResponseHandler = clientRegistrationResponseHandler;
144+
return this;
145+
}
146+
147+
/**
148+
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException} and
149+
* returning the {@link OAuth2Error Error Response}.
150+
*
151+
* @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
152+
* @return the {@link OidcClientRegistrationEndpointConfigurer} for further configuration
153+
* @since 0.4.0
154+
*/
155+
public OidcClientRegistrationEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
156+
this.errorResponseHandler = errorResponseHandler;
157+
return this;
158+
}
159+
49160
@Override
50161
void init(HttpSecurity httpSecurity) {
51162
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
@@ -54,18 +165,15 @@ void init(HttpSecurity httpSecurity) {
54165
new AntPathRequestMatcher(authorizationServerSettings.getOidcClientRegistrationEndpoint(), HttpMethod.GET.name())
55166
);
56167

57-
OidcClientRegistrationAuthenticationProvider oidcClientRegistrationAuthenticationProvider =
58-
new OidcClientRegistrationAuthenticationProvider(
59-
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
60-
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity),
61-
OAuth2ConfigurerUtils.getTokenGenerator(httpSecurity));
62-
httpSecurity.authenticationProvider(postProcess(oidcClientRegistrationAuthenticationProvider));
168+
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
63169

64-
OidcClientConfigurationAuthenticationProvider oidcClientConfigurationAuthenticationProvider =
65-
new OidcClientConfigurationAuthenticationProvider(
66-
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
67-
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity));
68-
httpSecurity.authenticationProvider(postProcess(oidcClientConfigurationAuthenticationProvider));
170+
if (!this.authenticationProviders.isEmpty()) {
171+
authenticationProviders.addAll(0, this.authenticationProviders);
172+
}
173+
this.authenticationProvidersConsumer.accept(authenticationProviders);
174+
175+
authenticationProviders.forEach(authenticationProvider ->
176+
httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
69177
}
70178

71179
@Override
@@ -77,6 +185,22 @@ void configure(HttpSecurity httpSecurity) {
77185
new OidcClientRegistrationEndpointFilter(
78186
authenticationManager,
79187
authorizationServerSettings.getOidcClientRegistrationEndpoint());
188+
189+
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
190+
if (!this.clientRegistrationRequestConverters.isEmpty()) {
191+
authenticationConverters.addAll(0, this.clientRegistrationRequestConverters);
192+
}
193+
this.clientRegistrationRequestConvertersConsumer.accept(authenticationConverters);
194+
oidcClientRegistrationEndpointFilter.setAuthenticationConverter(
195+
new DelegatingAuthenticationConverter(authenticationConverters));
196+
197+
if (this.clientRegistrationResponseHandler != null) {
198+
oidcClientRegistrationEndpointFilter
199+
.setAuthenticationSuccessHandler(this.clientRegistrationResponseHandler);
200+
}
201+
if (this.errorResponseHandler != null) {
202+
oidcClientRegistrationEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
203+
}
80204
httpSecurity.addFilterAfter(postProcess(oidcClientRegistrationEndpointFilter), FilterSecurityInterceptor.class);
81205
}
82206

@@ -85,4 +209,28 @@ RequestMatcher getRequestMatcher() {
85209
return this.requestMatcher;
86210
}
87211

212+
private static List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
213+
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
214+
215+
OidcClientRegistrationAuthenticationProvider oidcClientRegistrationAuthenticationProvider =
216+
new OidcClientRegistrationAuthenticationProvider(
217+
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
218+
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity),
219+
OAuth2ConfigurerUtils.getTokenGenerator(httpSecurity));
220+
authenticationProviders.add(oidcClientRegistrationAuthenticationProvider);
221+
222+
OidcClientConfigurationAuthenticationProvider oidcClientConfigurationAuthenticationProvider =
223+
new OidcClientConfigurationAuthenticationProvider(
224+
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
225+
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity));
226+
authenticationProviders.add(oidcClientConfigurationAuthenticationProvider);
227+
return authenticationProviders;
228+
}
229+
230+
private static List<AuthenticationConverter> createDefaultAuthenticationConverters() {
231+
List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
232+
authenticationConverters.add(new OidcClientRegistrationAuthenticationConverter());
233+
return authenticationConverters;
234+
}
235+
88236
}

0 commit comments

Comments
 (0)