Skip to content

Commit 8ec24ed

Browse files
addcontentjgrandja
authored andcommitted
Add authentication validator for dynamic client registration
Signed-off-by: Kelvin Mbogo <addcontent08@gmail.com>
1 parent 7cf3c54 commit 8ec24ed

8 files changed

Lines changed: 819 additions & 43 deletions

File tree

docs/src/main/java/sample/registration/SecurityConfig.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@
1515
*/
1616
package sample.registration;
1717

18+
import java.util.List;
19+
import java.util.function.Consumer;
20+
1821
import org.springframework.context.annotation.Bean;
1922
import org.springframework.context.annotation.Configuration;
23+
import org.springframework.security.authentication.AuthenticationProvider;
2024
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2125
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
2226
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
27+
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
28+
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationValidator;
2329
import org.springframework.security.web.SecurityFilterChain;
2430

2531
import static sample.registration.CustomClientMetadataConfig.configureCustomClientMetadataConverters;
@@ -41,7 +47,7 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
4147
.oidc((oidc) ->
4248
oidc.clientRegistrationEndpoint((clientRegistrationEndpoint) -> // <1>
4349
clientRegistrationEndpoint
44-
.authenticationProviders(configureCustomClientMetadataConverters()) // <2>
50+
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(configureCustomClientMetadataConverters())) // <2>
4551
)
4652
)
4753
)
@@ -54,4 +60,16 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
5460
return http.build();
5561
}
5662

63+
private static Consumer<List<AuthenticationProvider>> scopePermissiveValidatorCustomizer() {
64+
return (authenticationProviders) -> authenticationProviders.forEach((authenticationProvider) -> {
65+
if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider provider) {
66+
provider.setAuthenticationValidator(
67+
OidcClientRegistrationAuthenticationValidator.DEFAULT_REDIRECT_URI_VALIDATOR.andThen(
68+
OidcClientRegistrationAuthenticationValidator.DEFAULT_POST_LOGOUT_REDIRECT_URI_VALIDATOR)
69+
.andThen(OidcClientRegistrationAuthenticationValidator.DEFAULT_JWK_SET_URI_VALIDATOR)
70+
.andThen(OidcClientRegistrationAuthenticationValidator.SIMPLE_SCOPE_VALIDATOR));
71+
}
72+
});
73+
}
74+
5775
}

etc/nohttp/allowlist.lines

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
^http://webblaze.cs.berkeley.edu/.*
77
^http://www.w3.org/2000/09/xmldsig.*
88
^http://www.gnu.org/.*
9-
^http://www.gradle.org
9+
^http://www.gradle.org
10+
^http://169.254.169.254/keys
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2020-2026 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.oauth2.server.authorization.oidc.authentication;
17+
18+
import java.util.Collections;
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
import java.util.function.Consumer;
22+
23+
import org.springframework.lang.Nullable;
24+
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationContext;
25+
import org.springframework.util.Assert;
26+
27+
/**
28+
* An {@link OAuth2AuthenticationContext} that holds an
29+
* {@link OidcClientRegistrationAuthenticationToken} and additional information and is
30+
* used when validating the OpenID Connect 1.0 Client Registration Request parameters.
31+
*
32+
* @author addcontent _
33+
* @since 1.5.7
34+
* @see OAuth2AuthenticationContext
35+
* @see OidcClientRegistrationAuthenticationToken
36+
* @see OidcClientRegistrationAuthenticationProvider#setAuthenticationValidator(Consumer)
37+
*/
38+
public final class OidcClientRegistrationAuthenticationContext implements OAuth2AuthenticationContext {
39+
40+
private final Map<Object, Object> context;
41+
42+
private OidcClientRegistrationAuthenticationContext(Map<Object, Object> context) {
43+
this.context = Collections.unmodifiableMap(new HashMap<>(context));
44+
}
45+
46+
@SuppressWarnings("unchecked")
47+
@Nullable
48+
@Override
49+
public <V> V get(Object key) {
50+
return hasKey(key) ? (V) this.context.get(key) : null;
51+
}
52+
53+
@Override
54+
public boolean hasKey(Object key) {
55+
Assert.notNull(key, "key cannot be null");
56+
return this.context.containsKey(key);
57+
}
58+
59+
/**
60+
* Constructs a new {@link Builder} with the provided
61+
* {@link OidcClientRegistrationAuthenticationToken}.
62+
* @param authentication the {@link OidcClientRegistrationAuthenticationToken}
63+
* @return the {@link Builder}
64+
*/
65+
public static Builder with(OidcClientRegistrationAuthenticationToken authentication) {
66+
return new Builder(authentication);
67+
}
68+
69+
/**
70+
* A builder for {@link OidcClientRegistrationAuthenticationContext}.
71+
*/
72+
public static final class Builder extends AbstractBuilder<OidcClientRegistrationAuthenticationContext, Builder> {
73+
74+
private Builder(OidcClientRegistrationAuthenticationToken authentication) {
75+
super(authentication);
76+
}
77+
78+
/**
79+
* Builds a new {@link OidcClientRegistrationAuthenticationContext}.
80+
* @return the {@link OidcClientRegistrationAuthenticationContext}
81+
*/
82+
@Override
83+
public OidcClientRegistrationAuthenticationContext build() {
84+
return new OidcClientRegistrationAuthenticationContext(getContext());
85+
}
86+
87+
}
88+
89+
}

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2024 the original author or authors.
2+
* Copyright 2020-2026 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,14 +15,12 @@
1515
*/
1616
package org.springframework.security.oauth2.server.authorization.oidc.authentication;
1717

18-
import java.net.URI;
19-
import java.net.URISyntaxException;
2018
import java.util.Collection;
2119
import java.util.Collections;
2220
import java.util.HashSet;
23-
import java.util.List;
2421
import java.util.Map;
2522
import java.util.Set;
23+
import java.util.function.Consumer;
2624

2725
import org.apache.commons.logging.Log;
2826
import org.apache.commons.logging.LogFactory;
@@ -59,7 +57,6 @@
5957
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
6058
import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
6159
import org.springframework.util.Assert;
62-
import org.springframework.util.CollectionUtils;
6360
import org.springframework.util.StringUtils;
6461

6562
/**
@@ -101,6 +98,8 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
10198

10299
private PasswordEncoder passwordEncoder;
103100

101+
private Consumer<OidcClientRegistrationAuthenticationContext> authenticationValidator;
102+
104103
/**
105104
* Constructs an {@code OidcClientRegistrationAuthenticationProvider} using the
106105
* provided parameters.
@@ -121,6 +120,7 @@ public OidcClientRegistrationAuthenticationProvider(RegisteredClientRepository r
121120
this.clientRegistrationConverter = new RegisteredClientOidcClientRegistrationConverter();
122121
this.registeredClientConverter = new OidcClientRegistrationRegisteredClientConverter();
123122
this.passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
123+
this.authenticationValidator = new OidcClientRegistrationAuthenticationValidator();
124124
}
125125

126126
@Override
@@ -209,20 +209,35 @@ public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
209209
this.passwordEncoder = passwordEncoder;
210210
}
211211

212+
/**
213+
* Sets the {@code Consumer} providing access to the
214+
* {@link OidcClientRegistrationAuthenticationContext} and is responsible for
215+
* validating specific OpenID Connect 1.0 Client Registration Request parameters
216+
* associated in the {@link OidcClientRegistrationAuthenticationToken}. The default
217+
* authentication validator is {@link OidcClientRegistrationAuthenticationValidator}.
218+
*
219+
* <p>
220+
* <b>NOTE:</b> The authentication validator MUST throw
221+
* {@link OAuth2AuthenticationException} if validation fails.
222+
* @param authenticationValidator the {@code Consumer} providing access to the
223+
* {@link OidcClientRegistrationAuthenticationContext} and is responsible for
224+
* validating specific OpenID Connect 1.0 Client Registration Request parameters
225+
* @since 1.5.7
226+
*/
227+
public void setAuthenticationValidator(
228+
Consumer<OidcClientRegistrationAuthenticationContext> authenticationValidator) {
229+
Assert.notNull(authenticationValidator, "authenticationValidator cannot be null");
230+
this.authenticationValidator = authenticationValidator;
231+
}
232+
212233
private OidcClientRegistrationAuthenticationToken registerClient(
213234
OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication,
214235
OAuth2Authorization authorization) {
215236

216-
if (!isValidRedirectUris(clientRegistrationAuthentication.getClientRegistration().getRedirectUris())) {
217-
throwInvalidClientRegistration(OAuth2ErrorCodes.INVALID_REDIRECT_URI,
218-
OidcClientMetadataClaimNames.REDIRECT_URIS);
219-
}
220-
221-
if (!isValidRedirectUris(
222-
clientRegistrationAuthentication.getClientRegistration().getPostLogoutRedirectUris())) {
223-
throwInvalidClientRegistration("invalid_client_metadata",
224-
OidcClientMetadataClaimNames.POST_LOGOUT_REDIRECT_URIS);
225-
}
237+
OidcClientRegistrationAuthenticationContext authenticationContext = OidcClientRegistrationAuthenticationContext
238+
.with(clientRegistrationAuthentication)
239+
.build();
240+
this.authenticationValidator.accept(authenticationContext);
226241

227242
if (!isValidTokenEndpointAuthenticationMethod(clientRegistrationAuthentication.getClientRegistration())) {
228243
throwInvalidClientRegistration("invalid_client_metadata",
@@ -354,26 +369,6 @@ else if (authorizedScope.size() != requiredScope.size()) {
354369
}
355370
}
356371

357-
private static boolean isValidRedirectUris(List<String> redirectUris) {
358-
if (CollectionUtils.isEmpty(redirectUris)) {
359-
return true;
360-
}
361-
362-
for (String redirectUri : redirectUris) {
363-
try {
364-
URI validRedirectUri = new URI(redirectUri);
365-
if (validRedirectUri.getFragment() != null) {
366-
return false;
367-
}
368-
}
369-
catch (URISyntaxException ex) {
370-
return false;
371-
}
372-
}
373-
374-
return true;
375-
}
376-
377372
private static boolean isValidTokenEndpointAuthenticationMethod(OidcClientRegistration clientRegistration) {
378373
String authenticationMethod = clientRegistration.getTokenEndpointAuthenticationMethod();
379374
String authenticationSigningAlgorithm = clientRegistration.getTokenEndpointAuthenticationSigningAlgorithm();

0 commit comments

Comments
 (0)