7979import org .springframework .security .oauth2 .server .authorization .OAuth2ClientRegistration ;
8080import org .springframework .security .oauth2 .server .authorization .authentication .OAuth2ClientRegistrationAuthenticationProvider ;
8181import org .springframework .security .oauth2 .server .authorization .authentication .OAuth2ClientRegistrationAuthenticationToken ;
82+ import org .springframework .security .oauth2 .server .authorization .authentication .OAuth2ClientRegistrationAuthenticationValidator ;
8283import org .springframework .security .oauth2 .server .authorization .client .JdbcRegisteredClientRepository ;
8384import org .springframework .security .oauth2 .server .authorization .client .JdbcRegisteredClientRepository .RegisteredClientParametersMapper ;
8485import 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