Merge device-client into messages-client
Issue gh-1189
This commit is contained in:
@@ -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"
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -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;
|
||||
}
|
||||
@@ -35,7 +35,7 @@ public final class OAuth2DeviceGrantRequest extends AbstractOAuth2AuthorizationG
|
||||
}
|
||||
|
||||
public String getDeviceCode() {
|
||||
return deviceCode;
|
||||
return this.deviceCode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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> <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>
|
||||
|
||||
Reference in New Issue
Block a user