Jelajahi Sumber

Merge device-client into messages-client

Issue gh-1189
Joe Grandja 2 tahun lalu
induk
melakukan
e8c0837fef
20 mengubah file dengan 86 tambahan dan 345 penghapusan
  1. 0 28
      samples/device-client/samples-device-client.gradle
  2. 0 32
      samples/device-client/src/main/java/sample/DeviceClientApplication.java
  3. 0 56
      samples/device-client/src/main/java/sample/config/SecurityConfig.java
  4. 0 71
      samples/device-client/src/main/java/sample/config/WebClientConfig.java
  5. 0 52
      samples/device-client/src/main/java/sample/web/DeviceControllerAdvice.java
  6. 0 29
      samples/device-client/src/main/resources/application.yml
  7. 0 35
      samples/device-client/src/main/resources/templates/authorized.html
  8. 0 0
      samples/messages-client/gradle.properties
  9. 9 10
      samples/messages-client/src/main/java/sample/config/SecurityConfig.java
  10. 17 3
      samples/messages-client/src/main/java/sample/config/WebClientConfig.java
  11. 7 1
      samples/messages-client/src/main/java/sample/web/AuthorizationController.java
  12. 34 15
      samples/messages-client/src/main/java/sample/web/DeviceController.java
  13. 0 3
      samples/messages-client/src/main/java/sample/web/authentication/DeviceCodeOAuth2AuthorizedClientProvider.java
  14. 0 0
      samples/messages-client/src/main/java/sample/web/authentication/OAuth2DeviceAccessTokenResponseClient.java
  15. 1 1
      samples/messages-client/src/main/java/sample/web/authentication/OAuth2DeviceGrantRequest.java
  16. 7 1
      samples/messages-client/src/main/resources/application.yml
  17. 0 0
      samples/messages-client/src/main/resources/static/assets/css/style.css
  18. 3 3
      samples/messages-client/src/main/resources/templates/device-activate.html
  19. 4 4
      samples/messages-client/src/main/resources/templates/device-authorize.html
  20. 4 1
      samples/messages-client/src/main/resources/templates/index.html

+ 0 - 28
samples/device-client/samples-device-client.gradle

@@ -1,28 +0,0 @@
-plugins {
-	id "org.springframework.boot" version "3.0.0"
-	id "io.spring.dependency-management" version "1.0.11.RELEASE"
-	id "java"
-}
-
-group = project.rootProject.group
-version = project.rootProject.version
-sourceCompatibility = "17"
-
-repositories {
-	mavenCentral()
-	maven { url "https://repo.spring.io/milestone" }
-}
-
-dependencies {
-	implementation "org.springframework.boot:spring-boot-starter-web"
-	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 "org.webjars:webjars-locator-core"
-	implementation "org.webjars:bootstrap:3.4.1"
-	implementation "org.webjars:jquery:3.4.1"
-
-	testImplementation "org.springframework.boot:spring-boot-starter-test"
-	testImplementation "org.springframework.security:spring-security-test"
-}

+ 0 - 32
samples/device-client/src/main/java/sample/DeviceClientApplication.java

@@ -1,32 +0,0 @@
-/*
- * Copyright 2020-2023 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;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-/**
- * @author Steve Riesenberg
- * @since 1.1
- */
-@SpringBootApplication
-public class DeviceClientApplication {
-
-	public static void main(String[] args) {
-		SpringApplication.run(DeviceClientApplication.class, args);
-	}
-
-}

+ 0 - 56
samples/device-client/src/main/java/sample/config/SecurityConfig.java

@@ -1,56 +0,0 @@
-/*
- * Copyright 2020-2023 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 org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-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.WebSecurityCustomizer;
-import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
-
-/**
- * @author Steve Riesenberg
- * @since 1.1
- */
-@Configuration(proxyBeanMethods = false)
-@EnableWebSecurity
-public class SecurityConfig {
-
-	@Bean
-	public WebSecurityCustomizer webSecurityCustomizer() {
-		return (web) -> web.ignoring().requestMatchers("/webjars/**", "/assets/**");
-	}
-
-	@Bean
-	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
-		// @formatter:off
-		http
-			.authorizeHttpRequests((authorize) -> authorize
-				.requestMatchers("/", "/authorize").permitAll()
-				.anyRequest().authenticated()
-			)
-			.exceptionHandling((exceptions) -> exceptions
-				.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"))
-			)
-			.oauth2Client(Customizer.withDefaults());
-		// @formatter:on
-		return http.build();
-	}
-
-}

+ 0 - 71
samples/device-client/src/main/java/sample/config/WebClientConfig.java

@@ -1,71 +0,0 @@
-/*
- * Copyright 2020-2023 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 sample.web.authentication.DeviceCodeOAuth2AuthorizedClientProvider;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-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.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.web.reactive.function.client.WebClient;
-
-/**
- * @author Steve Riesenberg
- * @since 1.1
- */
-@Configuration
-public class WebClientConfig {
-
-	@Bean
-	public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
-		ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
-				new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
-		// @formatter:off
-		return WebClient.builder()
-				.apply(oauth2Client.oauth2Configuration())
-				.build();
-		// @formatter:on
-	}
-
-	@Bean
-	public OAuth2AuthorizedClientManager authorizedClientManager(
-			ClientRegistrationRepository clientRegistrationRepository,
-			OAuth2AuthorizedClientRepository authorizedClientRepository) {
-
-		OAuth2AuthorizedClientProvider authorizedClientProvider =
-				OAuth2AuthorizedClientProviderBuilder.builder()
-						.provider(new DeviceCodeOAuth2AuthorizedClientProvider())
-						.authorizationCode()
-						.refreshToken()
-						.build();
-		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;
-	}
-
-}

+ 0 - 52
samples/device-client/src/main/java/sample/web/DeviceControllerAdvice.java

@@ -1,52 +0,0 @@
-/*
- * Copyright 2020-2023 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.web;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
-import org.springframework.security.oauth2.core.OAuth2Error;
-import org.springframework.web.bind.annotation.ControllerAdvice;
-import org.springframework.web.bind.annotation.ExceptionHandler;
-
-/**
- * @author Steve Riesenberg
- * @since 1.1
- */
-@ControllerAdvice
-public class DeviceControllerAdvice {
-
-	private static final Set<String> DEVICE_GRANT_ERRORS = new HashSet<>(Arrays.asList(
-			"authorization_pending",
-			"slow_down",
-			"access_denied",
-			"expired_token"
-	));
-
-	@ExceptionHandler(OAuth2AuthorizationException.class)
-	public ResponseEntity<OAuth2Error> handleError(OAuth2AuthorizationException ex) {
-		String errorCode = ex.getError().getErrorCode();
-		if (DEVICE_GRANT_ERRORS.contains(errorCode)) {
-			return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ex.getError());
-		}
-		return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getError());
-	}
-
-}

+ 0 - 29
samples/device-client/src/main/resources/application.yml

@@ -1,29 +0,0 @@
-server:
-  port: 8080
-
-logging:
-  level:
-    root: INFO
-    org.springframework.security: trace
-
-spring:
-  thymeleaf:
-    cache: false
-  security:
-    oauth2:
-      client:
-        registration:
-          messaging-client-device-grant:
-            provider: spring
-            client-id: device-messaging-client
-            client-authentication-method: none
-            authorization-grant-type: urn:ietf:params:oauth:grant-type:device_code
-            scope: message.read,message.write
-            client-name: messaging-client-device-grant
-        provider:
-          spring:
-            issuer-uri: http://localhost:9000
-            authorization-uri: ${spring.security.oauth2.client.provider.spring.issuer-uri}/oauth2/device_authorization
-
-messages:
-  base-uri: http://127.0.0.1:8090/messages

+ 0 - 35
samples/device-client/src/main/resources/templates/authorized.html

@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
-    <head>
-        <meta charset="utf-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
-        <title>Device Grant Example</title>
-        <link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.css" th:href="@{/webjars/bootstrap/css/bootstrap.css}" />
-        <link rel="stylesheet" href="/assets/css/style.css" th:href="@{/assets/css/style.css}" />
-    </head>
-    <body>
-        <div class="jumbotron">
-            <div class="container">
-                <div class="row">
-                    <div class="col-md-8">
-                        <h2>Success!</h2>
-                        <p>This device has been activated.</p>
-                    </div>
-                    <div class="col-md-4">
-                        <img src="https://cdn.pixabay.com/photo/2017/07/03/15/20/technology-2468063_1280.png" class="img-responsive" alt="Devices">
-                    </div>
-                    <div class="col-md-12" th:if="${messages}">
-                        <h4>Messages:</h4>
-                        <table class="table table-condensed">
-                            <tbody>
-                            <tr class="row" th:each="message : ${messages}">
-                                <td th:text="${message}">message</td>
-                            </tr>
-                            </tbody>
-                        </table>
-                    </div>
-                </div>
-            </div>
-        </div>
-    </body>
-</html>

+ 0 - 0
samples/device-client/gradle.properties → samples/messages-client/gradle.properties


+ 9 - 10
samples/messages-client/src/main/java/sample/config/SecurityConfig.java

@@ -15,7 +15,6 @@
  */
 package sample.config;
 
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -31,23 +30,22 @@ import static org.springframework.security.config.Customizer.withDefaults;
 /**
  * @author Joe Grandja
  * @author Dmitriy Dubson
+ * @author Steve Riesenberg
  * @since 0.0.1
  */
 @EnableWebSecurity
 @Configuration(proxyBeanMethods = false)
 public class SecurityConfig {
 
-	@Autowired
-	private ClientRegistrationRepository clientRegistrationRepository;
-
 	@Bean
-	WebSecurityCustomizer webSecurityCustomizer() {
-		return (web) -> web.ignoring().requestMatchers("/webjars/**");
+	public WebSecurityCustomizer webSecurityCustomizer() {
+		return (web) -> web.ignoring().requestMatchers("/webjars/**", "/assets/**");
 	}
 
 	// @formatter:off
 	@Bean
-	SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+	public SecurityFilterChain securityFilterChain(HttpSecurity http,
+			ClientRegistrationRepository clientRegistrationRepository) throws Exception {
 		http
 			.authorizeHttpRequests(authorize ->
 				authorize
@@ -58,14 +56,15 @@ public class SecurityConfig {
 				oauth2Login.loginPage("/oauth2/authorization/messaging-client-oidc"))
 			.oauth2Client(withDefaults())
 			.logout(logout ->
-				logout.logoutSuccessHandler(oidcLogoutSuccessHandler()));
+				logout.logoutSuccessHandler(oidcLogoutSuccessHandler(clientRegistrationRepository)));
 		return http.build();
 	}
 	// @formatter:on
 
-	private LogoutSuccessHandler oidcLogoutSuccessHandler() {
+	private LogoutSuccessHandler oidcLogoutSuccessHandler(
+			ClientRegistrationRepository clientRegistrationRepository) {
 		OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
-				new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);
+				new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
 
 		// Set the location that the End-User's User Agent will be redirected to
 		// after the logout has been performed at the Provider

+ 17 - 3
samples/messages-client/src/main/java/sample/config/WebClientConfig.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2023 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.
@@ -15,6 +15,8 @@
  */
 package sample.config;
 
+import sample.web.authentication.DeviceCodeOAuth2AuthorizedClientProvider;
+
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
@@ -28,35 +30,47 @@ import org.springframework.web.reactive.function.client.WebClient;
 
 /**
  * @author Joe Grandja
+ * @author Steve Riesenberg
  * @since 0.0.1
  */
 @Configuration
 public class WebClientConfig {
 
 	@Bean
-	WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
+	public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
 		ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
 				new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
+		// @formatter:off
 		return WebClient.builder()
 				.apply(oauth2Client.oauth2Configuration())
 				.build();
+		// @formatter:on
 	}
 
 	@Bean
-	OAuth2AuthorizedClientManager authorizedClientManager(
+	public OAuth2AuthorizedClientManager authorizedClientManager(
 			ClientRegistrationRepository clientRegistrationRepository,
 			OAuth2AuthorizedClientRepository authorizedClientRepository) {
 
+		// @formatter:off
 		OAuth2AuthorizedClientProvider authorizedClientProvider =
 				OAuth2AuthorizedClientProviderBuilder.builder()
 						.authorizationCode()
 						.refreshToken()
 						.clientCredentials()
+						.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;
 	}
+
 }

+ 7 - 1
samples/messages-client/src/main/java/sample/web/AuthorizationController.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020-2022 the original author or authors.
+ * Copyright 2020-2023 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.
@@ -93,4 +93,10 @@ public class AuthorizationController {
 
 		return "index";
 	}
+
+	@GetMapping(value = "/authorize", params = "grant_type=device_code")
+	public String deviceCodeGrant() {
+		return "device-activate";
+	}
+
 }

+ 34 - 15
samples/device-client/src/main/java/sample/web/DeviceController.java → samples/messages-client/src/main/java/sample/web/DeviceController.java

@@ -16,8 +16,11 @@
 package sample.web;
 
 import java.time.Instant;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
@@ -37,7 +40,9 @@ import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2Aut
 import org.springframework.security.oauth2.client.registration.ClientRegistration;
 import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
 import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
 import org.springframework.security.oauth2.core.OAuth2DeviceCode;
+import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
 import org.springframework.security.web.context.SecurityContextRepository;
@@ -46,6 +51,7 @@ import org.springframework.ui.Model;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 import org.springframework.util.StringUtils;
+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;
@@ -61,6 +67,13 @@ import static org.springframework.security.oauth2.client.web.reactive.function.c
 @Controller
 public class DeviceController {
 
+	private static final Set<String> DEVICE_GRANT_ERRORS = new HashSet<>(Arrays.asList(
+			"authorization_pending",
+			"slow_down",
+			"access_denied",
+			"expired_token"
+	));
+
 	private static final ParameterizedTypeReference<Map<String, Object>> TYPE_REFERENCE =
 			new ParameterizedTypeReference<>() {};
 
@@ -84,17 +97,12 @@ public class DeviceController {
 		this.messagesBaseUri = messagesBaseUri;
 	}
 
-	@GetMapping("/")
-	public String index() {
-		return "index";
-	}
-
-	@GetMapping("/authorize")
+	@GetMapping("/device_authorize")
 	public String authorize(Model model, HttpServletRequest request, HttpServletResponse response) {
 		// @formatter:off
 		ClientRegistration clientRegistration =
 				this.clientRegistrationRepository.findByRegistrationId(
-						"messaging-client-device-grant");
+						"messaging-client-device-code");
 		// @formatter:on
 
 		MultiValueMap<String, String> requestParameters = new LinkedMultiValueMap<>();
@@ -102,10 +110,12 @@ public class DeviceController {
 		requestParameters.add(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(
 				clientRegistration.getScopes(), " "));
 
+		String deviceAuthorizationUri = (String) clientRegistration.getProviderDetails().getConfigurationMetadata().get("device_authorization_endpoint");
+
 		// @formatter:off
 		Map<String, Object> responseParameters =
 				this.webClient.post()
-						.uri(clientRegistration.getProviderDetails().getAuthorizationUri())
+						.uri(deviceAuthorizationUri)
 						.headers(headers -> {
 							/*
 							 * This sample demonstrates the use of a public client that does not
@@ -146,15 +156,15 @@ public class DeviceController {
 		model.addAttribute("verificationUriComplete", responseParameters.get(
 				OAuth2ParameterNames.VERIFICATION_URI_COMPLETE));
 
-		return "authorize";
+		return "device-authorize";
 	}
 
 	/**
-	 * @see DeviceControllerAdvice
+	 * @see #handleError(OAuth2AuthorizationException)
 	 */
-	@PostMapping("/authorize")
+	@PostMapping("/device_authorize")
 	public ResponseEntity<Void> poll(@RequestParam(OAuth2ParameterNames.DEVICE_CODE) String deviceCode,
-			@RegisteredOAuth2AuthorizedClient("messaging-client-device-grant")
+			@RegisteredOAuth2AuthorizedClient("messaging-client-device-code")
 					OAuth2AuthorizedClient authorizedClient) {
 
 		/*
@@ -175,9 +185,18 @@ public class DeviceController {
 		return ResponseEntity.status(HttpStatus.OK).build();
 	}
 
-	@GetMapping("/authorized")
+	@ExceptionHandler(OAuth2AuthorizationException.class)
+	public ResponseEntity<OAuth2Error> handleError(OAuth2AuthorizationException ex) {
+		String errorCode = ex.getError().getErrorCode();
+		if (DEVICE_GRANT_ERRORS.contains(errorCode)) {
+			return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ex.getError());
+		}
+		return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getError());
+	}
+
+	@GetMapping("/device_authorized")
 	public String authorized(Model model,
-			@RegisteredOAuth2AuthorizedClient("messaging-client-device-grant")
+			@RegisteredOAuth2AuthorizedClient("messaging-client-device-code")
 					OAuth2AuthorizedClient authorizedClient) {
 
 		String[] messages = this.webClient.get()
@@ -188,7 +207,7 @@ public class DeviceController {
 				.block();
 		model.addAttribute("messages", messages);
 
-		return "authorized";
+		return "index";
 	}
 
 	private void saveSecurityContext(OAuth2DeviceCode deviceCode, HttpServletRequest request,

+ 0 - 3
samples/device-client/src/main/java/sample/web/authentication/DeviceCodeOAuth2AuthorizedClientProvider.java → samples/messages-client/src/main/java/sample/web/authentication/DeviceCodeOAuth2AuthorizedClientProvider.java

@@ -50,9 +50,6 @@ public final class DeviceCodeOAuth2AuthorizedClientProvider implements OAuth2Aut
 
 	private Clock clock = Clock.systemUTC();
 
-	public DeviceCodeOAuth2AuthorizedClientProvider() {
-	}
-
 	public void setAccessTokenResponseClient(OAuth2AccessTokenResponseClient<OAuth2DeviceGrantRequest> accessTokenResponseClient) {
 		this.accessTokenResponseClient = accessTokenResponseClient;
 	}

+ 0 - 0
samples/device-client/src/main/java/sample/web/authentication/OAuth2DeviceAccessTokenResponseClient.java → samples/messages-client/src/main/java/sample/web/authentication/OAuth2DeviceAccessTokenResponseClient.java


+ 1 - 1
samples/device-client/src/main/java/sample/web/authentication/OAuth2DeviceGrantRequest.java → samples/messages-client/src/main/java/sample/web/authentication/OAuth2DeviceGrantRequest.java

@@ -35,7 +35,7 @@ public final class OAuth2DeviceGrantRequest extends AbstractOAuth2AuthorizationG
 	}
 
 	public String getDeviceCode() {
-		return deviceCode;
+		return this.deviceCode;
 	}
 
 }

+ 7 - 1
samples/messages-client/src/main/resources/application.yml

@@ -7,7 +7,6 @@ logging:
     org.springframework.web: INFO
     org.springframework.security: INFO
     org.springframework.security.oauth2: INFO
-#    org.springframework.boot.autoconfigure: DEBUG
 
 spring:
   thymeleaf:
@@ -39,6 +38,13 @@ spring:
             authorization-grant-type: client_credentials
             scope: message.read,message.write
             client-name: messaging-client-client-credentials
+          messaging-client-device-code:
+            provider: spring
+            client-id: device-messaging-client
+            client-authentication-method: none
+            authorization-grant-type: urn:ietf:params:oauth:grant-type:device_code
+            scope: message.read,message.write
+            client-name: messaging-client-device-code
         provider:
           spring:
             issuer-uri: http://localhost:9000

+ 0 - 0
samples/device-client/src/main/resources/static/assets/css/style.css → samples/messages-client/src/main/resources/static/assets/css/style.css


+ 3 - 3
samples/device-client/src/main/resources/templates/index.html → samples/messages-client/src/main/resources/templates/device-activate.html

@@ -13,8 +13,8 @@
                 <div class="row">
                     <div class="col-md-8">
                         <h2>Activation Required</h2>
-                        <p>You must activate this device. Please log in to continue.</p>
-                        <a th:href="@{/authorize}" class="btn btn-primary" role="button">Log In</a>
+                        <p>You must activate this device.</p>
+                        <a th:href="@{/device_authorize}" class="btn btn-primary" role="button">Activate</a>
                     </div>
                     <div class="col-md-4">
                         <img src="https://cdn.pixabay.com/photo/2017/07/03/15/20/technology-2468063_1280.png" class="img-responsive" alt="Devices">
@@ -23,4 +23,4 @@
             </div>
         </div>
     </body>
-</html>
+</html>

+ 4 - 4
samples/device-client/src/main/resources/templates/authorize.html → samples/messages-client/src/main/resources/templates/device-authorize.html

@@ -17,7 +17,7 @@
                         <p class="gap">Activation Code</p>
                         <div class="well">
                             <span class="code" th:text="${userCode}"></span>
-                            <form id="authorize-form" th:action="@{/authorize}" method="post">
+                            <form id="authorize-form" th:action="@{/device_authorize}" method="post">
                                 <input type="hidden" id="device_code" name="device_code" th:value="${deviceCode}" />
                             </form>
                         </div>
@@ -35,7 +35,7 @@
                 let csrfToken = $('[name=_csrf]').val();
                 if (deviceCode) {
                     $.ajax({
-                        url: '/authorize',
+                        url: '/device_authorize',
                         method: 'POST',
                         data: {
                             device_code: deviceCode,
@@ -58,7 +58,7 @@
                             clear();
                             location.href = '/';
                         }
-                    }).done(() => window.location.href = '/authorized');
+                    }).done(() => window.location.href = '/device_authorized');
                 }
             }
 
@@ -84,4 +84,4 @@
             window.addEventListener('load', schedule);
         </script>
     </body>
-</html>
+</html>

+ 4 - 1
samples/messages-client/src/main/resources/templates/index.html

@@ -30,11 +30,14 @@
                 </div>
                 <ul class="list-group">
                     <li class="list-group-item">
-                        <a href="/authorize?grant_type=authorization_code" th:href="@{/authorize?grant_type=authorization_code}"><span style="font-size:medium">Authorization Code</span>&nbsp;&nbsp;<small class="text-muted">(Login to Spring Authorization Server using: user1/password)</small></a>
+                        <a href="/authorize?grant_type=authorization_code" th:href="@{/authorize?grant_type=authorization_code}"><span style="font-size:medium">Authorization Code</span></a>
                     </li>
                     <li class="list-group-item">
                         <a href="/authorize?grant_type=client_credentials" th:href="@{/authorize?grant_type=client_credentials}"><span style="font-size:medium">Client Credentials</span></a>
                     </li>
+                    <li class="list-group-item">
+                        <a href="/authorize?grant_type=device_code" th:href="@{/authorize?grant_type=device_code}"><span style="font-size:medium">Device Code</span></a>
+                    </li>
                 </ul>
                 <div th:if="${messages}" class="panel-footer">
                     <h4>Messages:</h4>