Эх сурвалжийг харах

Update demo-client sample

Joe Grandja 1 сар өмнө
parent
commit
cc580866e6

+ 1 - 3
samples/demo-client/samples-demo-client.gradle

@@ -22,10 +22,8 @@ dependencies {
 	implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
 	implementation "org.springframework.boot:spring-boot-starter-security"
 	implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
-	implementation "org.springframework:spring-webflux"
-	implementation "io.projectreactor.netty:reactor-netty"
 	implementation "org.apache.httpcomponents.client5:httpclient5"
-	implementation "org.webjars:webjars-locator-core"
+	implementation "org.webjars:webjars-locator-lite"
 	implementation "org.webjars:bootstrap:5.2.3"
 	implementation "org.webjars:popper.js:2.9.3"
 	implementation "org.webjars:jquery:3.6.4"

+ 13 - 2
samples/demo-client/src/main/java/sample/authorization/DeviceCodeOAuth2AuthorizedClientProvider.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020-2023 the original author or authors.
+ * Copyright 2020-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -36,6 +36,8 @@ import org.springframework.security.oauth2.core.OAuth2Token;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.util.Assert;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
 
 /**
  * @author Steve Riesenberg
@@ -107,7 +109,16 @@ public final class DeviceCodeOAuth2AuthorizedClientProvider implements OAuth2Aut
 	public static Function<OAuth2AuthorizeRequest, Map<String, Object>> deviceCodeContextAttributesMapper() {
 		return (authorizeRequest) -> {
 			HttpServletRequest request = authorizeRequest.getAttribute(HttpServletRequest.class.getName());
-			Assert.notNull(request, "request cannot be null");
+			if (request == null) {
+				ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
+						.getRequestAttributes();
+				if (requestAttributes != null) {
+					request = requestAttributes.getRequest();
+				}
+			}
+			if (request == null) {
+				return Collections.emptyMap();
+			}
 
 			// Obtain device code from request
 			String deviceCode = request.getParameter(OAuth2ParameterNames.DEVICE_CODE);

+ 2 - 0
samples/demo-client/src/main/java/sample/authorization/OAuth2DeviceAccessTokenResponseClient.java

@@ -40,6 +40,7 @@ public final class OAuth2DeviceAccessTokenResponseClient implements OAuth2Access
 	private RestClient restClient;
 
 	public OAuth2DeviceAccessTokenResponseClient() {
+		// @formatter:off
 		this.restClient = RestClient.builder()
 				.messageConverters((messageConverters) -> {
 					messageConverters.clear();
@@ -48,6 +49,7 @@ public final class OAuth2DeviceAccessTokenResponseClient implements OAuth2Access
 				})
 				.defaultStatusHandler(new OAuth2ErrorResponseErrorHandler())
 				.build();
+		// @formatter:on
 	}
 
 	public void setRestClient(RestClient restClient) {

+ 135 - 0
samples/demo-client/src/main/java/sample/config/RestClientConfig.java

@@ -28,13 +28,33 @@ import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
 import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
 import org.apache.hc.core5.http.config.Registry;
 import org.apache.hc.core5.http.config.RegistryBuilder;
+import sample.authorization.DeviceCodeOAuth2AuthorizedClientProvider;
 
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.ssl.SslBundle;
 import org.springframework.boot.ssl.SslBundles;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.client.ClientHttpRequestFactory;
 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.http.converter.FormHttpMessageConverter;
+import org.springframework.security.oauth2.client.OAuth2AuthorizationFailureHandler;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
+import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
+import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
+import org.springframework.security.oauth2.client.endpoint.RestClientClientCredentialsTokenResponseClient;
+import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
+import org.springframework.security.oauth2.client.web.client.OAuth2ClientHttpRequestInterceptor;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestClient;
 
 /**
  * @author Joe Grandja
@@ -43,6 +63,91 @@ import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
 @Configuration(proxyBeanMethods = false)
 public class RestClientConfig {
 
+	@Bean("default-client-rest-client")
+	public RestClient defaultClientRestClient(
+			OAuth2AuthorizedClientRepository authorizedClientRepository,
+			OAuth2AuthorizedClientManager authorizedClientManager,
+			@Qualifier("default-client-http-request-factory") Supplier<ClientHttpRequestFactory> clientHttpRequestFactory) {
+
+		OAuth2ClientHttpRequestInterceptor requestInterceptor =
+				new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
+		OAuth2AuthorizationFailureHandler authorizationFailureHandler =
+				OAuth2ClientHttpRequestInterceptor.authorizationFailureHandler(authorizedClientRepository);
+		requestInterceptor.setAuthorizationFailureHandler(authorizationFailureHandler);
+		// @formatter:off
+		return RestClient.builder()
+				.requestFactory(clientHttpRequestFactory.get())
+				.requestInterceptor(requestInterceptor)
+				.build();
+		// @formatter:on
+	}
+
+	@Bean("self-signed-demo-client-rest-client")
+	public RestClient selfSignedDemoClientRestClient(
+			ClientRegistrationRepository clientRegistrationRepository,
+			OAuth2AuthorizedClientRepository authorizedClientRepository,
+			@Qualifier("self-signed-demo-client-http-request-factory") Supplier<ClientHttpRequestFactory> clientHttpRequestFactory) {
+
+		RestClient restClient = accessTokenRestClient(clientHttpRequestFactory);
+
+		// @formatter:off
+		OAuth2AuthorizedClientProvider authorizedClientProvider =
+				OAuth2AuthorizedClientProviderBuilder.builder()
+						.clientCredentials(clientCredentials ->
+								clientCredentials.accessTokenResponseClient(
+										createClientCredentialsTokenResponseClient(restClient)))
+						.build();
+		// @formatter:on
+
+		DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
+				clientRegistrationRepository, authorizedClientRepository);
+		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
+
+		OAuth2ClientHttpRequestInterceptor requestInterceptor =
+				new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
+		OAuth2AuthorizationFailureHandler authorizationFailureHandler =
+				OAuth2ClientHttpRequestInterceptor.authorizationFailureHandler(authorizedClientRepository);
+		requestInterceptor.setAuthorizationFailureHandler(authorizationFailureHandler);
+
+		// @formatter:off
+		return RestClient.builder()
+				.requestFactory(clientHttpRequestFactory.get())
+				.requestInterceptor(requestInterceptor)
+				.build();
+		// @formatter:on
+	}
+
+	@Bean
+	public OAuth2AuthorizedClientManager authorizedClientManager(
+			ClientRegistrationRepository clientRegistrationRepository,
+			OAuth2AuthorizedClientRepository authorizedClientRepository,
+			@Qualifier("default-client-http-request-factory") Supplier<ClientHttpRequestFactory> clientHttpRequestFactory) {
+
+		RestClient restClient = accessTokenRestClient(clientHttpRequestFactory);
+
+		// @formatter:off
+		OAuth2AuthorizedClientProvider authorizedClientProvider =
+				OAuth2AuthorizedClientProviderBuilder.builder()
+						.authorizationCode()
+						.refreshToken()
+						.clientCredentials(clientCredentials ->
+								clientCredentials.accessTokenResponseClient(
+										createClientCredentialsTokenResponseClient(restClient)))
+						.provider(new DeviceCodeOAuth2AuthorizedClientProvider())
+						.build();
+		// @formatter:on
+
+		DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
+				clientRegistrationRepository, authorizedClientRepository);
+		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
+
+		// Set a contextAttributesMapper to obtain device_code from the request
+		authorizedClientManager.setContextAttributesMapper(DeviceCodeOAuth2AuthorizedClientProvider
+				.deviceCodeContextAttributesMapper());
+
+		return authorizedClientManager;
+	}
+
 	@Bean("default-client-http-request-factory")
 	Supplier<ClientHttpRequestFactory> defaultClientHttpRequestFactory(SslBundles sslBundles) {
 		return () -> {
@@ -82,4 +187,34 @@ public class RestClientConfig {
 		};
 	}
 
+	private static RestClient accessTokenRestClient(Supplier<ClientHttpRequestFactory> clientHttpRequestFactory) {
+		// @formatter:off
+		return RestClient.builder()
+				.requestFactory(clientHttpRequestFactory.get())
+				.messageConverters((messageConverters) -> {
+					messageConverters.clear();
+					messageConverters.add(new FormHttpMessageConverter());
+					messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter());
+				})
+				.defaultStatusHandler(new OAuth2ErrorResponseErrorHandler())
+				.build();
+		// @formatter:on
+
+	}
+
+	private static OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> createClientCredentialsTokenResponseClient(
+			RestClient restClient) {
+		RestClientClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient =
+				new RestClientClientCredentialsTokenResponseClient();
+		clientCredentialsTokenResponseClient.addParametersConverter(authorizationGrantRequest -> {
+			MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
+			// client_id parameter is required for tls_client_auth method
+			parameters.add(OAuth2ParameterNames.CLIENT_ID, authorizationGrantRequest.getClientRegistration().getClientId());
+			return parameters;
+		});
+		clientCredentialsTokenResponseClient.setRestClient(restClient);
+
+		return clientCredentialsTokenResponseClient;
+	}
+
 }

+ 2 - 8
samples/demo-client/src/main/java/sample/config/SecurityConfig.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020-2024 the original author or authors.
+ * Copyright 2020-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 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.WebSecurityCustomizer;
 import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler;
 import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
 import org.springframework.security.web.SecurityFilterChain;
@@ -37,11 +36,6 @@ import static org.springframework.security.config.Customizer.withDefaults;
 @Configuration(proxyBeanMethods = false)
 public class SecurityConfig {
 
-	@Bean
-	public WebSecurityCustomizer webSecurityCustomizer() {
-		return (web) -> web.ignoring().requestMatchers("/webjars/**", "/assets/**");
-	}
-
 	// @formatter:off
 	@Bean
 	public SecurityFilterChain securityFilterChain(HttpSecurity http,
@@ -49,7 +43,7 @@ public class SecurityConfig {
 		http
 			.authorizeHttpRequests(authorize ->
 				authorize
-					.requestMatchers("/jwks", "/logged-out").permitAll()
+					.requestMatchers("/webjars/**", "/assets/**", "/jwks", "/logged-out").permitAll()
 					.anyRequest().authenticated()
 			)
 			.oauth2Login(oauth2Login ->

+ 0 - 193
samples/demo-client/src/main/java/sample/config/WebClientConfig.java

@@ -1,193 +0,0 @@
-/*
- * Copyright 2020-2025 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package sample.config;
-
-import java.util.function.Supplier;
-
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.TrustManagerFactory;
-
-import io.netty.handler.ssl.SslContext;
-import io.netty.handler.ssl.SslContextBuilder;
-import reactor.netty.http.client.HttpClient;
-import reactor.netty.tcp.SslProvider;
-import sample.authorization.DeviceCodeOAuth2AuthorizedClientProvider;
-
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.boot.ssl.SslBundle;
-import org.springframework.boot.ssl.SslBundles;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.http.client.ClientHttpRequestFactory;
-import org.springframework.http.client.reactive.ClientHttpConnector;
-import org.springframework.http.client.reactive.ReactorClientHttpConnector;
-import org.springframework.http.converter.FormHttpMessageConverter;
-import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
-import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
-import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
-import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
-import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
-import org.springframework.security.oauth2.client.endpoint.RestClientClientCredentialsTokenResponseClient;
-import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
-import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
-import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
-import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
-import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
-import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
-import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.client.RestClient;
-import org.springframework.web.reactive.function.client.WebClient;
-
-/**
- * @author Joe Grandja
- * @author Steve Riesenberg
- * @since 0.0.1
- */
-@Configuration(proxyBeanMethods = false)
-public class WebClientConfig {
-
-	@Bean("default-client-web-client")
-	public WebClient defaultClientWebClient(
-			OAuth2AuthorizedClientManager authorizedClientManager,
-			SslBundles sslBundles) throws Exception {
-
-		ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
-				new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
-		// @formatter:off
-		return WebClient.builder()
-				.clientConnector(createClientConnector(sslBundles.getBundle("demo-client")))
-				.apply(oauth2Client.oauth2Configuration())
-				.build();
-		// @formatter:on
-	}
-
-	@Bean("self-signed-demo-client-web-client")
-	public WebClient selfSignedDemoClientWebClient(
-			ClientRegistrationRepository clientRegistrationRepository,
-			OAuth2AuthorizedClientRepository authorizedClientRepository,
-			@Qualifier("self-signed-demo-client-http-request-factory") Supplier<ClientHttpRequestFactory> clientHttpRequestFactory,
-			SslBundles sslBundles) throws Exception {
-
-		// @formatter:off
-		RestClient restClient = RestClient.builder()
-				.requestFactory(clientHttpRequestFactory.get())
-				.messageConverters((messageConverters) -> {
-					messageConverters.clear();
-					messageConverters.add(new FormHttpMessageConverter());
-					messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter());
-				})
-				.defaultStatusHandler(new OAuth2ErrorResponseErrorHandler())
-				.build();
-		// @formatter:on
-
-		// @formatter:off
-		OAuth2AuthorizedClientProvider authorizedClientProvider =
-				OAuth2AuthorizedClientProviderBuilder.builder()
-						.clientCredentials(clientCredentials ->
-								clientCredentials.accessTokenResponseClient(
-										createClientCredentialsTokenResponseClient(restClient)))
-						.build();
-		// @formatter:on
-
-		DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
-				clientRegistrationRepository, authorizedClientRepository);
-		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
-
-		ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
-				new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
-		// @formatter:off
-		return WebClient.builder()
-				.clientConnector(createClientConnector(sslBundles.getBundle("self-signed-demo-client")))
-				.apply(oauth2Client.oauth2Configuration())
-				.build();
-		// @formatter:on
-	}
-
-	@Bean
-	public OAuth2AuthorizedClientManager authorizedClientManager(
-			ClientRegistrationRepository clientRegistrationRepository,
-			OAuth2AuthorizedClientRepository authorizedClientRepository,
-			@Qualifier("default-client-http-request-factory") Supplier<ClientHttpRequestFactory> clientHttpRequestFactory) {
-
-		// @formatter:off
-		RestClient restClient = RestClient.builder()
-				.requestFactory(clientHttpRequestFactory.get())
-				.messageConverters((messageConverters) -> {
-					messageConverters.clear();
-					messageConverters.add(new FormHttpMessageConverter());
-					messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter());
-				})
-				.defaultStatusHandler(new OAuth2ErrorResponseErrorHandler())
-				.build();
-		// @formatter:on
-
-		// @formatter:off
-		OAuth2AuthorizedClientProvider authorizedClientProvider =
-				OAuth2AuthorizedClientProviderBuilder.builder()
-						.authorizationCode()
-						.refreshToken()
-						.clientCredentials(clientCredentials ->
-								clientCredentials.accessTokenResponseClient(
-										createClientCredentialsTokenResponseClient(restClient)))
-						.provider(new DeviceCodeOAuth2AuthorizedClientProvider())
-						.build();
-		// @formatter:on
-
-		DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
-				clientRegistrationRepository, authorizedClientRepository);
-		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
-
-		// Set a contextAttributesMapper to obtain device_code from the request
-		authorizedClientManager.setContextAttributesMapper(DeviceCodeOAuth2AuthorizedClientProvider
-				.deviceCodeContextAttributesMapper());
-
-		return authorizedClientManager;
-	}
-
-	private static ClientHttpConnector createClientConnector(SslBundle sslBundle) throws Exception {
-		KeyManagerFactory keyManagerFactory = sslBundle.getManagers().getKeyManagerFactory();
-		TrustManagerFactory trustManagerFactory = sslBundle.getManagers().getTrustManagerFactory();
-
-		// @formatter:off
-		SslContext sslContext = SslContextBuilder.forClient()
-				.keyManager(keyManagerFactory)
-				.trustManager(trustManagerFactory)
-				.build();
-		// @formatter:on
-
-		SslProvider sslProvider = SslProvider.builder().sslContext(sslContext).build();
-		HttpClient httpClient = HttpClient.create().secure(sslProvider);
-		return new ReactorClientHttpConnector(httpClient);
-	}
-
-	private static OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> createClientCredentialsTokenResponseClient(
-			RestClient restClient) {
-		RestClientClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient =
-				new RestClientClientCredentialsTokenResponseClient();
-		clientCredentialsTokenResponseClient.addParametersConverter(authorizationGrantRequest -> {
-			MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
-			// client_id parameter is required for tls_client_auth method
-			parameters.add(OAuth2ParameterNames.CLIENT_ID, authorizationGrantRequest.getClientRegistration().getClientId());
-			return parameters;
-		});
-		clientCredentialsTokenResponseClient.setRestClient(restClient);
-
-		return clientCredentialsTokenResponseClient;
-	}
-
-}

+ 27 - 43
samples/demo-client/src/main/java/sample/web/AuthorizationController.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020-2024 the original author or authors.
+ * Copyright 2020-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,8 +19,6 @@ import jakarta.servlet.http.HttpServletRequest;
 
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
-import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.stereotype.Controller;
@@ -28,11 +26,11 @@ import org.springframework.ui.Model;
 import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.reactive.function.client.WebClient;
-import org.springframework.web.reactive.function.client.WebClientResponseException;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.client.RestClientResponseException;
+
+import static org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId;
 
-import static org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId;
-import static org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient;
 
 /**
  * @author Joe Grandja
@@ -40,34 +38,30 @@ import static org.springframework.security.oauth2.client.web.reactive.function.c
  */
 @Controller
 public class AuthorizationController {
-	private final WebClient defaultClientWebClient;
-	private final WebClient selfSignedDemoClientWebClient;
+	private final RestClient defaultClientRestClient;
+	private final RestClient selfSignedDemoClientRestClient;
 	private final String messagesBaseUri;
 	private final String userMessagesBaseUri;
 
 	public AuthorizationController(
-			@Qualifier("default-client-web-client") WebClient defaultClientWebClient,
-			@Qualifier("self-signed-demo-client-web-client") WebClient selfSignedDemoClientWebClient,
+			@Qualifier("default-client-rest-client") RestClient defaultClientRestClient,
+			@Qualifier("self-signed-demo-client-rest-client") RestClient selfSignedDemoClientRestClient,
 			@Value("${messages.base-uri}") String messagesBaseUri,
 			@Value("${user-messages.base-uri}") String userMessagesBaseUri) {
-		this.defaultClientWebClient = defaultClientWebClient;
-		this.selfSignedDemoClientWebClient = selfSignedDemoClientWebClient;
+		this.defaultClientRestClient = defaultClientRestClient;
+		this.selfSignedDemoClientRestClient = selfSignedDemoClientRestClient;
 		this.messagesBaseUri = messagesBaseUri;
 		this.userMessagesBaseUri = userMessagesBaseUri;
 	}
 
 	@GetMapping(value = "/authorize", params = "grant_type=authorization_code")
-	public String authorizationCodeGrant(Model model,
-			@RegisteredOAuth2AuthorizedClient("messaging-client-authorization-code")
-					OAuth2AuthorizedClient authorizedClient) {
-
-		String[] messages = this.defaultClientWebClient
+	public String authorizationCodeGrant(Model model) {
+		String[] messages = this.defaultClientRestClient
 				.get()
 				.uri(this.messagesBaseUri)
-				.attributes(oauth2AuthorizedClient(authorizedClient))
+				.attributes(clientRegistrationId("messaging-client-authorization-code"))
 				.retrieve()
-				.bodyToMono(String[].class)
-				.block();
+				.body(String[].class);
 		model.addAttribute("messages", messages);
 
 		return "index";
@@ -91,14 +85,12 @@ public class AuthorizationController {
 
 	@GetMapping(value = "/authorize", params = {"grant_type=client_credentials", "client_auth=client_secret"})
 	public String clientCredentialsGrantUsingClientSecret(Model model) {
-
-		String[] messages = this.defaultClientWebClient
+		String[] messages = this.defaultClientRestClient
 				.get()
 				.uri(this.messagesBaseUri)
 				.attributes(clientRegistrationId("messaging-client-client-credentials"))
 				.retrieve()
-				.bodyToMono(String[].class)
-				.block();
+				.body(String[].class);
 		model.addAttribute("messages", messages);
 
 		return "index";
@@ -106,14 +98,12 @@ public class AuthorizationController {
 
 	@GetMapping(value = "/authorize", params = {"grant_type=client_credentials", "client_auth=mtls"})
 	public String clientCredentialsGrantUsingMutualTLS(Model model) {
-
-		String[] messages = this.defaultClientWebClient
+		String[] messages = this.defaultClientRestClient
 				.get()
 				.uri(this.messagesBaseUri)
 				.attributes(clientRegistrationId("mtls-demo-client-client-credentials"))
 				.retrieve()
-				.bodyToMono(String[].class)
-				.block();
+				.body(String[].class);
 		model.addAttribute("messages", messages);
 
 		return "index";
@@ -121,14 +111,12 @@ public class AuthorizationController {
 
 	@GetMapping(value = "/authorize", params = {"grant_type=client_credentials", "client_auth=self_signed_mtls"})
 	public String clientCredentialsGrantUsingSelfSignedMutualTLS(Model model) {
-
-		String[] messages = this.selfSignedDemoClientWebClient
+		String[] messages = this.selfSignedDemoClientRestClient
 				.get()
 				.uri(this.messagesBaseUri)
 				.attributes(clientRegistrationId("mtls-self-signed-demo-client-client-credentials"))
 				.retrieve()
-				.bodyToMono(String[].class)
-				.block();
+				.body(String[].class);
 		model.addAttribute("messages", messages);
 
 		return "index";
@@ -136,14 +124,12 @@ public class AuthorizationController {
 
 	@GetMapping(value = "/authorize", params = {"grant_type=token_exchange", "use_case=delegation"})
 	public String tokenExchangeGrantUsingDelegation(Model model) {
-
-		String[] messages = this.defaultClientWebClient
+		String[] messages = this.defaultClientRestClient
 				.get()
 				.uri(this.userMessagesBaseUri + "?use_case=delegation")
 				.attributes(clientRegistrationId("user-client-authorization-code"))
 				.retrieve()
-				.bodyToMono(String[].class)
-				.block();
+				.body(String[].class);
 		model.addAttribute("messages", messages);
 
 		return "index";
@@ -151,14 +137,12 @@ public class AuthorizationController {
 
 	@GetMapping(value = "/authorize", params = {"grant_type=token_exchange", "use_case=impersonation"})
 	public String tokenExchangeGrantUsingImpersonation(Model model) {
-
-		String[] messages = this.defaultClientWebClient
+		String[] messages = this.defaultClientRestClient
 				.get()
 				.uri(this.userMessagesBaseUri + "?use_case=impersonation")
 				.attributes(clientRegistrationId("user-client-authorization-code"))
 				.retrieve()
-				.bodyToMono(String[].class)
-				.block();
+				.body(String[].class);
 		model.addAttribute("messages", messages);
 
 		return "index";
@@ -169,8 +153,8 @@ public class AuthorizationController {
 		return "device-activate";
 	}
 
-	@ExceptionHandler(WebClientResponseException.class)
-	public String handleError(Model model, WebClientResponseException ex) {
+	@ExceptionHandler(RestClientResponseException.class)
+	public String handleError(Model model, RestClientResponseException ex) {
 		model.addAttribute("error", ex.getMessage());
 		return "index";
 	}

+ 13 - 19
samples/demo-client/src/main/java/sample/web/DeviceController.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020-2024 the original author or authors.
+ * Copyright 2020-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -45,10 +45,9 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.reactive.function.BodyInserters;
-import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.client.RestClient;
 
-import static org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient;
+import static org.springframework.security.oauth2.client.web.client.RequestAttributeClientRegistrationIdResolver.clientRegistrationId;
 
 /**
  * @author Steve Riesenberg
@@ -69,17 +68,17 @@ public class DeviceController {
 
 	private final ClientRegistrationRepository clientRegistrationRepository;
 
-	private final WebClient webClient;
+	private final RestClient restClient;
 
 	private final String messagesBaseUri;
 
 	public DeviceController(
 			ClientRegistrationRepository clientRegistrationRepository,
-			@Qualifier("default-client-web-client") WebClient webClient,
+			@Qualifier("default-client-rest-client") RestClient restClient,
 			@Value("${messages.base-uri}") String messagesBaseUri) {
 
 		this.clientRegistrationRepository = clientRegistrationRepository;
-		this.webClient = webClient;
+		this.restClient = restClient;
 		this.messagesBaseUri = messagesBaseUri;
 	}
 
@@ -100,7 +99,7 @@ public class DeviceController {
 
 		// @formatter:off
 		Map<String, Object> responseParameters =
-				this.webClient.post()
+				this.restClient.post()
 						.uri(deviceAuthorizationUri)
 						.headers(headers -> {
 							/*
@@ -119,10 +118,9 @@ public class DeviceController {
 							}
 						})
 						.contentType(MediaType.APPLICATION_FORM_URLENCODED)
-						.body(BodyInserters.fromFormData(requestParameters))
+						.body(requestParameters)
 						.retrieve()
-						.bodyToMono(TYPE_REFERENCE)
-						.block();
+						.body(TYPE_REFERENCE);
 		// @formatter:on
 
 		Objects.requireNonNull(responseParameters, "Device Authorization Response cannot be null");
@@ -177,16 +175,12 @@ public class DeviceController {
 	}
 
 	@GetMapping("/device_authorized")
-	public String authorized(Model model,
-			@RegisteredOAuth2AuthorizedClient("messaging-client-device-code")
-					OAuth2AuthorizedClient authorizedClient) {
-
-		String[] messages = this.webClient.get()
+	public String authorized(Model model) {
+		String[] messages = this.restClient.get()
 				.uri(this.messagesBaseUri)
-				.attributes(oauth2AuthorizedClient(authorizedClient))
+				.attributes(clientRegistrationId("messaging-client-device-code"))
 				.retrieve()
-				.bodyToMono(String[].class)
-				.block();
+				.body(String[].class);
 		model.addAttribute("messages", messages);
 
 		return "index";