From 1cda1ca36dadb3870b274ca5aed34d613e6ac397 Mon Sep 17 00:00:00 2001
From: Steve Riesenberg <5248162+sjohnr@users.noreply.github.com>
Date: Wed, 1 May 2024 17:46:12 -0500
Subject: [PATCH] Add impersonation sample for token exchange
Closes gh-1604
---
.../config/AuthorizationServerConfig.java | 1 +
.../sample/web/AuthorizationController.java | 21 ++++++++++++++++---
.../resources/templates/page-templates.html | 3 ++-
.../sample/config/TokenExchangeConfig.java | 7 +++++++
.../main/java/sample/web/UserController.java | 20 ++++++++++++------
.../src/main/resources/application.yml | 11 ++++++++--
6 files changed, 51 insertions(+), 12 deletions(-)
diff --git a/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java b/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java
index 0608737f..f0d8b328 100644
--- a/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java
+++ b/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java
@@ -165,6 +165,7 @@ public class AuthorizationServerConfig {
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(new AuthorizationGrantType("urn:ietf:params:oauth:grant-type:token-exchange"))
.scope("message.read")
+ .scope("message.write")
.build();
RegisteredClient mtlsDemoClient = RegisteredClient.withId(UUID.randomUUID().toString())
diff --git a/samples/demo-client/src/main/java/sample/web/AuthorizationController.java b/samples/demo-client/src/main/java/sample/web/AuthorizationController.java
index 38e12769..965c0932 100644
--- a/samples/demo-client/src/main/java/sample/web/AuthorizationController.java
+++ b/samples/demo-client/src/main/java/sample/web/AuthorizationController.java
@@ -134,12 +134,27 @@ public class AuthorizationController {
return "index";
}
- @GetMapping(value = "/authorize", params = "grant_type=token_exchange")
- public String tokenExchangeGrant(Model model) {
+ @GetMapping(value = "/authorize", params = {"grant_type=token_exchange", "use_case=delegation"})
+ public String tokenExchangeGrantUsingDelegation(Model model) {
String[] messages = this.defaultClientWebClient
.get()
- .uri(this.userMessagesBaseUri)
+ .uri(this.userMessagesBaseUri + "?use_case=delegation")
+ .attributes(clientRegistrationId("user-client-authorization-code"))
+ .retrieve()
+ .bodyToMono(String[].class)
+ .block();
+ model.addAttribute("messages", messages);
+
+ return "index";
+ }
+
+ @GetMapping(value = "/authorize", params = {"grant_type=token_exchange", "use_case=impersonation"})
+ public String tokenExchangeGrantUsingImpersonation(Model model) {
+
+ String[] messages = this.defaultClientWebClient
+ .get()
+ .uri(this.userMessagesBaseUri + "?use_case=impersonation")
.attributes(clientRegistrationId("user-client-authorization-code"))
.retrieve()
.bodyToMono(String[].class)
diff --git a/samples/demo-client/src/main/resources/templates/page-templates.html b/samples/demo-client/src/main/resources/templates/page-templates.html
index a22d3c5f..653919ca 100644
--- a/samples/demo-client/src/main/resources/templates/page-templates.html
+++ b/samples/demo-client/src/main/resources/templates/page-templates.html
@@ -27,7 +27,8 @@
Client Credentials (client_secret_basic)
Client Credentials (tls_client_auth)
Client Credentials (self_signed_tls_client_auth)
- Token Exchange
+ Token Exchange (delegation)
+ Token Exchange (impersonation)
Device Code
diff --git a/samples/users-resource/src/main/java/sample/config/TokenExchangeConfig.java b/samples/users-resource/src/main/java/sample/config/TokenExchangeConfig.java
index 48ea043a..2c64424c 100644
--- a/samples/users-resource/src/main/java/sample/config/TokenExchangeConfig.java
+++ b/samples/users-resource/src/main/java/sample/config/TokenExchangeConfig.java
@@ -41,6 +41,8 @@ public class TokenExchangeConfig {
private static final String ACTOR_TOKEN_CLIENT_REGISTRATION_ID = "messaging-client-client-credentials";
+ private static final String IMPERSONATION_CLIENT_REGISTRATION_ID = "messaging-client-token-exchange-with-impersonation";
+
@Bean
public OAuth2AuthorizedClientProvider tokenExchange(
ClientRegistrationRepository clientRegistrationRepository,
@@ -87,6 +89,11 @@ public class TokenExchangeConfig {
OAuth2AuthorizedClientManager authorizedClientManager, String clientRegistrationId) {
return (context) -> {
+ // Do not provide an actor token for impersonation use case
+ if (IMPERSONATION_CLIENT_REGISTRATION_ID.equals(context.getClientRegistration().getRegistrationId())) {
+ return null;
+ }
+
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest =
OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId)
diff --git a/samples/users-resource/src/main/java/sample/web/UserController.java b/samples/users-resource/src/main/java/sample/web/UserController.java
index 20a654b8..ef46f03d 100644
--- a/samples/users-resource/src/main/java/sample/web/UserController.java
+++ b/samples/users-resource/src/main/java/sample/web/UserController.java
@@ -21,10 +21,8 @@ import java.util.List;
import java.util.Objects;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
-import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestClient;
@@ -44,11 +42,21 @@ public class UserController {
.build();
}
- @GetMapping("/user/messages")
- public List getMessages(@AuthenticationPrincipal Jwt jwt,
- @RegisteredOAuth2AuthorizedClient("messaging-client-token-exchange")
+ @GetMapping(value = "/user/messages", params = "use_case=delegation")
+ public List getMessagesWithDelegation(
+ @RegisteredOAuth2AuthorizedClient("messaging-client-token-exchange-with-delegation")
OAuth2AuthorizedClient authorizedClient) {
+ return getUserMessages(authorizedClient);
+ }
+ @GetMapping(value = "/user/messages", params = "use_case=impersonation")
+ public List getMessagesWithImpersonation(
+ @RegisteredOAuth2AuthorizedClient("messaging-client-token-exchange-with-impersonation")
+ OAuth2AuthorizedClient authorizedClient) {
+ return getUserMessages(authorizedClient);
+ }
+
+ private List getUserMessages(OAuth2AuthorizedClient authorizedClient) {
// @formatter:off
String[] messages = Objects.requireNonNull(
this.restClient.get()
@@ -60,7 +68,7 @@ public class UserController {
// @formatter:on
List userMessages = new ArrayList<>(Arrays.asList(messages));
- userMessages.add("%s has %d unread messages".formatted(jwt.getSubject(), messages.length));
+ userMessages.add("%s has %d unread messages".formatted(authorizedClient.getPrincipalName(), messages.length));
return userMessages;
}
diff --git a/samples/users-resource/src/main/resources/application.yml b/samples/users-resource/src/main/resources/application.yml
index 71acccef..c76aacfa 100644
--- a/samples/users-resource/src/main/resources/application.yml
+++ b/samples/users-resource/src/main/resources/application.yml
@@ -19,13 +19,20 @@ spring:
client-secret: secret
authorization-grant-type: client_credentials
client-name: messaging-client-client-credentials
- messaging-client-token-exchange:
+ messaging-client-token-exchange-with-delegation:
+ provider: spring
+ client-id: token-client
+ client-secret: token
+ authorization-grant-type: urn:ietf:params:oauth:grant-type:token-exchange
+ scope: message.read,message.write
+ client-name: messaging-client-token-exchange-with-delegation
+ messaging-client-token-exchange-with-impersonation:
provider: spring
client-id: token-client
client-secret: token
authorization-grant-type: urn:ietf:params:oauth:grant-type:token-exchange
scope: message.read
- client-name: messaging-client-token-exchange
+ client-name: messaging-client-token-exchange-with-impersonation
provider:
spring:
issuer-uri: http://localhost:9000