Skip to content

Commit 19b3cae

Browse files
addcontentjgrandja
authored andcommitted
Add authentication validator for dynamic client registration
Signed-off-by: Kelvin Mbogo <addcontent08@gmail.com>
1 parent 4a6e0a1 commit 19b3cae

13 files changed

Lines changed: 1487 additions & 79 deletions

File tree

config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientRegistrationTests.java

Lines changed: 140 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
import org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration;
8080
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationProvider;
8181
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationToken;
82+
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationValidator;
8283
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
8384
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper;
8485
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
@@ -411,6 +412,102 @@ public void requestWhenClientRegistersWithSecretExpirationThenClientRegistration
411412
.isCloseTo(expectedSecretExpiryDate, allowedDelta);
412413
}
413414

415+
@Test
416+
public void requestWhenProtocolRelativeRedirectUriThenBadRequest() throws Exception {
417+
this.spring.register(DefaultValidatorConfiguration.class).autowire();
418+
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
419+
{
420+
"client_name": "client-name",
421+
"redirect_uris": ["//client.example.com/path"],
422+
"grant_types": ["authorization_code"]
423+
}
424+
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
425+
}
426+
427+
@Test
428+
public void requestWhenJavascriptSchemeRedirectUriThenBadRequest() throws Exception {
429+
this.spring.register(DefaultValidatorConfiguration.class).autowire();
430+
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
431+
{
432+
"client_name": "client-name",
433+
"redirect_uris": ["javascript:alert(document.cookie)"],
434+
"grant_types": ["authorization_code"]
435+
}
436+
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
437+
}
438+
439+
@Test
440+
public void requestWhenDataSchemeRedirectUriThenBadRequest() throws Exception {
441+
this.spring.register(DefaultValidatorConfiguration.class).autowire();
442+
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
443+
{
444+
"client_name": "client-name",
445+
"redirect_uris": ["data:text/html,<h1>content</h1>"],
446+
"grant_types": ["authorization_code"]
447+
}
448+
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
449+
}
450+
451+
@Test
452+
public void requestWhenHttpJwkSetUriThenBadRequest() throws Exception {
453+
this.spring.register(DefaultValidatorConfiguration.class).autowire();
454+
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
455+
{
456+
"client_name": "client-name",
457+
"redirect_uris": ["https://client.example.com"],
458+
"grant_types": ["authorization_code"],
459+
"jwks_uri": "http://169.254.169.254/keys",
460+
"token_endpoint_auth_method": "private_key_jwt"
461+
}
462+
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
463+
}
464+
465+
@Test
466+
public void requestWhenArbitraryScopeThenBadRequest() throws Exception {
467+
this.spring.register(DefaultValidatorConfiguration.class).autowire();
468+
assertThat(requestWhenInvalidClientMetadataThenBadRequest("""
469+
{
470+
"client_name": "client-name",
471+
"redirect_uris": ["https://client.example.com"],
472+
"grant_types": ["client_credentials"],
473+
"scope": "read write"
474+
}
475+
""")).isEqualTo(HttpStatus.BAD_REQUEST.value());
476+
}
477+
478+
private int requestWhenInvalidClientMetadataThenBadRequest(String json) throws Exception {
479+
String clientRegistrationScope = "client.create";
480+
// @formatter:off
481+
RegisteredClient clientRegistrar = RegisteredClient.withId("client-registrar-" + System.nanoTime())
482+
.clientId("client-registrar-" + System.nanoTime())
483+
.clientSecret("{noop}secret")
484+
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
485+
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
486+
.scope(clientRegistrationScope)
487+
.build();
488+
// @formatter:on
489+
this.registeredClientRepository.save(clientRegistrar);
490+
491+
MvcResult tokenResult = this.mvc
492+
.perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI))
493+
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
494+
.param(OAuth2ParameterNames.SCOPE, clientRegistrationScope)
495+
.with(httpBasic(clientRegistrar.getClientId(), "secret")))
496+
.andExpect(status().isOk())
497+
.andReturn();
498+
OAuth2AccessToken accessToken = readAccessTokenResponse(tokenResult.getResponse()).getAccessToken();
499+
500+
HttpHeaders httpHeaders = new HttpHeaders();
501+
httpHeaders.setBearerAuth(accessToken.getTokenValue());
502+
503+
MvcResult registerResult = this.mvc
504+
.perform(post(ISSUER.concat(DEFAULT_OAUTH2_CLIENT_REGISTRATION_ENDPOINT_URI)).headers(httpHeaders)
505+
.contentType(MediaType.APPLICATION_JSON)
506+
.content(json))
507+
.andReturn();
508+
return registerResult.getResponse().getStatus();
509+
}
510+
414511
private OAuth2ClientRegistration registerClient(OAuth2ClientRegistration clientRegistration) throws Exception {
415512
// ***** (1) Obtain the "initial" access token used for registering the client
416513

@@ -496,6 +593,17 @@ private static OAuth2ClientRegistration readClientRegistrationResponse(MockHttpS
496593
return clientRegistrationHttpMessageConverter.read(OAuth2ClientRegistration.class, httpResponse);
497594
}
498595

596+
private static Consumer<List<AuthenticationProvider>> scopePermissiveValidatorCustomizer() {
597+
return (authenticationProviders) -> authenticationProviders.forEach((authenticationProvider) -> {
598+
if (authenticationProvider instanceof OAuth2ClientRegistrationAuthenticationProvider provider) {
599+
provider.setAuthenticationValidator(
600+
OAuth2ClientRegistrationAuthenticationValidator.DEFAULT_REDIRECT_URI_VALIDATOR
601+
.andThen(OAuth2ClientRegistrationAuthenticationValidator.DEFAULT_JWK_SET_URI_VALIDATOR)
602+
.andThen(OAuth2ClientRegistrationAuthenticationValidator.SIMPLE_SCOPE_VALIDATOR));
603+
}
604+
});
605+
}
606+
499607
@EnableWebSecurity
500608
@Configuration(proxyBeanMethods = false)
501609
static class CustomClientRegistrationConfiguration extends AuthorizationServerConfiguration {
@@ -512,7 +620,7 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
512620
.clientRegistrationRequestConverter(authenticationConverter)
513621
.clientRegistrationRequestConverters(authenticationConvertersConsumer)
514622
.authenticationProvider(authenticationProvider)
515-
.authenticationProviders(authenticationProvidersConsumer)
623+
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(authenticationProvidersConsumer))
516624
.clientRegistrationResponseHandler(authenticationSuccessHandler)
517625
.errorResponseHandler(authenticationFailureHandler)
518626
)
@@ -539,7 +647,7 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
539647
authorizationServer
540648
.clientRegistrationEndpoint((clientRegistration) ->
541649
clientRegistration
542-
.authenticationProviders(configureClientRegistrationConverters())
650+
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(configureClientRegistrationConverters()))
543651
)
544652
)
545653
.authorizeHttpRequests((authorize) ->
@@ -577,7 +685,7 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
577685
authorizationServer
578686
.clientRegistrationEndpoint((clientRegistration) ->
579687
clientRegistration
580-
.authenticationProviders(configureClientRegistrationConverters())
688+
.authenticationProviders(scopePermissiveValidatorCustomizer().andThen(configureClientRegistrationConverters()))
581689
)
582690
)
583691
.authorizeHttpRequests((authorize) ->
@@ -614,6 +722,7 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
614722
.clientRegistrationEndpoint((clientRegistration) ->
615723
clientRegistration
616724
.openRegistrationAllowed(true)
725+
.authenticationProviders(scopePermissiveValidatorCustomizer())
617726
)
618727
)
619728
.authorizeHttpRequests((authorize) ->
@@ -629,10 +738,13 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
629738

630739
@EnableWebSecurity
631740
@Configuration(proxyBeanMethods = false)
632-
static class AuthorizationServerConfiguration {
741+
static class DefaultValidatorConfiguration extends AuthorizationServerConfiguration {
633742

743+
// Override with Customizer.withDefaults() so the default (strict)
744+
// OAuth2ClientRegistrationAuthenticationValidator is in effect.
634745
// @formatter:off
635746
@Bean
747+
@Override
636748
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
637749
http
638750
.oauth2AuthorizationServer((authorizationServer) ->
@@ -646,6 +758,30 @@ SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) th
646758
}
647759
// @formatter:on
648760

761+
}
762+
763+
@EnableWebSecurity
764+
@Configuration(proxyBeanMethods = false)
765+
static class AuthorizationServerConfiguration {
766+
767+
// @formatter:off
768+
@Bean
769+
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
770+
http
771+
.oauth2AuthorizationServer((authorizationServer) ->
772+
authorizationServer
773+
.clientRegistrationEndpoint((clientRegistration) ->
774+
clientRegistration
775+
.authenticationProviders(scopePermissiveValidatorCustomizer())
776+
)
777+
)
778+
.authorizeHttpRequests((authorize) ->
779+
authorize.anyRequest().authenticated()
780+
);
781+
return http.build();
782+
}
783+
// @formatter:on
784+
649785
@Bean
650786
RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) {
651787
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();

0 commit comments

Comments
 (0)