diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java index 8afee07b66..2a55f12862 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java @@ -255,8 +255,16 @@ public final class OAuth2AuthorizationServerConfigurer */ public OAuth2AuthorizationServerConfigurer deviceAuthorizationEndpoint( Customizer deviceAuthorizationEndpointCustomizer) { - deviceAuthorizationEndpointCustomizer - .customize(getConfigurer(OAuth2DeviceAuthorizationEndpointConfigurer.class)); + OAuth2DeviceAuthorizationEndpointConfigurer deviceAuthorizationEndpointConfigurer = getConfigurer( + OAuth2DeviceAuthorizationEndpointConfigurer.class); + if (deviceAuthorizationEndpointConfigurer == null) { + addConfigurer(OAuth2DeviceAuthorizationEndpointConfigurer.class, + new OAuth2DeviceAuthorizationEndpointConfigurer(this::postProcess)); + deviceAuthorizationEndpointConfigurer = getConfigurer(OAuth2DeviceAuthorizationEndpointConfigurer.class); + deviceVerificationEndpoint((configurer) -> { + }); // Ensure the Device Verification Endpoint is enabled + } + deviceAuthorizationEndpointCustomizer.customize(deviceAuthorizationEndpointConfigurer); return this; } @@ -268,7 +276,16 @@ public final class OAuth2AuthorizationServerConfigurer */ public OAuth2AuthorizationServerConfigurer deviceVerificationEndpoint( Customizer deviceVerificationEndpointCustomizer) { - deviceVerificationEndpointCustomizer.customize(getConfigurer(OAuth2DeviceVerificationEndpointConfigurer.class)); + OAuth2DeviceVerificationEndpointConfigurer deviceVerificationEndpointConfigurer = getConfigurer( + OAuth2DeviceVerificationEndpointConfigurer.class); + if (deviceVerificationEndpointConfigurer == null) { + addConfigurer(OAuth2DeviceVerificationEndpointConfigurer.class, + new OAuth2DeviceVerificationEndpointConfigurer(this::postProcess)); + deviceVerificationEndpointConfigurer = getConfigurer(OAuth2DeviceVerificationEndpointConfigurer.class); + deviceAuthorizationEndpoint((configurer) -> { + }); // Ensure the Device Authorization Endpoint is enabled + } + deviceVerificationEndpointCustomizer.customize(deviceVerificationEndpointConfigurer); return this; } @@ -386,9 +403,11 @@ public final class OAuth2AuthorizationServerConfigurer preferredMatchers.add(getRequestMatcher(OAuth2TokenEndpointConfigurer.class)); preferredMatchers.add(getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class)); preferredMatchers.add(getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class)); - preferredMatchers.add(getRequestMatcher(OAuth2DeviceAuthorizationEndpointConfigurer.class)); - RequestMatcher preferredMatcher = getRequestMatcher( - OAuth2PushedAuthorizationRequestEndpointConfigurer.class); + RequestMatcher preferredMatcher = getRequestMatcher(OAuth2DeviceAuthorizationEndpointConfigurer.class); + if (preferredMatcher != null) { + preferredMatchers.add(preferredMatcher); + } + preferredMatcher = getRequestMatcher(OAuth2PushedAuthorizationRequestEndpointConfigurer.class); if (preferredMatcher != null) { preferredMatchers.add(preferredMatcher); } @@ -478,10 +497,6 @@ public final class OAuth2AuthorizationServerConfigurer new OAuth2TokenIntrospectionEndpointConfigurer(this::postProcess)); configurers.put(OAuth2TokenRevocationEndpointConfigurer.class, new OAuth2TokenRevocationEndpointConfigurer(this::postProcess)); - configurers.put(OAuth2DeviceAuthorizationEndpointConfigurer.class, - new OAuth2DeviceAuthorizationEndpointConfigurer(this::postProcess)); - configurers.put(OAuth2DeviceVerificationEndpointConfigurer.class, - new OAuth2DeviceVerificationEndpointConfigurer(this::postProcess)); return configurers; } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2DeviceCodeGrantTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2DeviceCodeGrantTests.java index be512e23fd..e3fb73f2ea 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2DeviceCodeGrantTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2DeviceCodeGrantTests.java @@ -34,7 +34,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -47,8 +47,9 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.crypto.password.NoOpPasswordEncoder; @@ -82,6 +83,7 @@ import org.springframework.security.oauth2.server.authorization.client.Registere import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.LinkedMultiValueMap; @@ -645,9 +647,25 @@ public class OAuth2DeviceCodeGrantTests { } @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) + @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfiguration { + // @formatter:off + @Bean + SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + http + .oauth2AuthorizationServer((authorizationServer) -> + authorizationServer + .deviceAuthorizationEndpoint(Customizer.withDefaults()) + .deviceVerificationEndpoint(Customizer.withDefaults()) + ) + .authorizeHttpRequests((authorize) -> + authorize.anyRequest().authenticated() + ); + return http.build(); + } + // @formatter:on + @Bean RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { return new JdbcRegisteredClientRepository(jdbcOperations); @@ -675,6 +693,11 @@ public class OAuth2DeviceCodeGrantTests { return jwkSource; } + @Bean + AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().build(); + } + @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); @@ -683,9 +706,25 @@ public class OAuth2DeviceCodeGrantTests { } @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) + @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { + // @formatter:off + @Bean + SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + http + .oauth2AuthorizationServer((authorizationServer) -> + authorizationServer + .deviceAuthorizationEndpoint(Customizer.withDefaults()) + .deviceVerificationEndpoint(Customizer.withDefaults()) + ) + .authorizeHttpRequests((authorize) -> + authorize.anyRequest().authenticated() + ); + return http.build(); + } + // @formatter:on + @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build();