diff --git a/pom.xml b/pom.xml
index 58d9e6ba..2f67e235 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,12 +5,12 @@
com.solace.spring.boot
solace-spring-boot-bom
- 2.0.0
+ 2.1.0
com.solace.spring.cloud
solace-spring-cloud-build
- 4.2.1-SNAPSHOT
+ 4.3.0-SNAPSHOT
pom
Solace Spring Cloud Build
@@ -21,20 +21,20 @@
SolaceProducts
- 2023.0.2
+ 2023.0.3
- 2.0.0
+ 2.1.0
- 3.3.1
+ 3.3.2
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
- 1.0.2
+ 1.1.2
false
true
diff --git a/solace-spring-cloud-bom/README.md b/solace-spring-cloud-bom/README.md
index 6cf1d239..80dd102e 100644
--- a/solace-spring-cloud-bom/README.md
+++ b/solace-spring-cloud-bom/README.md
@@ -28,6 +28,7 @@ Consult the table below to determine which version of the BOM you need to use:
| 2022.0.4 | 3.1.0, 3.2.0 | 3.1.x |
| 2023.0.1 | 4.0.0, 4.1.0 | 3.2.x |
| 2023.0.2 | 4.2.0 | 3.3.x |
+| 2023.0.3 | 4.3.0 | 3.3.x |
## Including the BOM
@@ -40,7 +41,7 @@ In addition to showing how to include the BOM, the following snippets also shows
com.solace.spring.cloud
solace-spring-cloud-bom
- 4.2.0
+ 4.3.0
pom
import
@@ -68,7 +69,7 @@ apply plugin: 'io.spring.dependency-management'
dependencyManagement {
imports {
- mavenBom "com.solace.spring.cloud:solace-spring-cloud-bom:4.2.0"
+ mavenBom "com.solace.spring.cloud:solace-spring-cloud-bom:4.3.0"
}
}
@@ -80,7 +81,7 @@ dependencies {
### Using it with Gradle 5
```groovy
dependencies {
- implementation(platform("com.solace.spring.cloud:solace-spring-cloud-bom:4.2.0"))
+ implementation(platform("com.solace.spring.cloud:solace-spring-cloud-bom:4.3.0"))
implementation("com.solace.spring.cloud:spring-cloud-starter-stream-solace")
}
```
diff --git a/solace-spring-cloud-bom/pom.xml b/solace-spring-cloud-bom/pom.xml
index 6ae493fa..349d34c4 100644
--- a/solace-spring-cloud-bom/pom.xml
+++ b/solace-spring-cloud-bom/pom.xml
@@ -5,13 +5,13 @@
com.solace.spring.cloud
solace-spring-cloud-build
- 4.2.1-SNAPSHOT
+ 4.3.0-SNAPSHOT
../pom.xml
solace-spring-cloud-bom
pom
- 4.2.1-SNAPSHOT
+ 4.3.0-SNAPSHOT
Solace Spring Cloud BOM
BOM for Solace Spring Cloud
diff --git a/solace-spring-cloud-parent/pom.xml b/solace-spring-cloud-parent/pom.xml
index ba8260d7..5f3d7c57 100644
--- a/solace-spring-cloud-parent/pom.xml
+++ b/solace-spring-cloud-parent/pom.xml
@@ -5,7 +5,7 @@
com.solace.spring.cloud
solace-spring-cloud-build
- 4.2.1-SNAPSHOT
+ 4.3.0-SNAPSHOT
../pom.xml
diff --git a/solace-spring-cloud-starters/solace-spring-cloud-stream-starter/README.adoc b/solace-spring-cloud-starters/solace-spring-cloud-stream-starter/README.adoc
index 99a16d77..1babd286 100644
--- a/solace-spring-cloud-starters/solace-spring-cloud-stream-starter/README.adoc
+++ b/solace-spring-cloud-starters/solace-spring-cloud-stream-starter/README.adoc
@@ -1,5 +1,5 @@
= Spring Cloud Stream Binder for Solace PubSub+
-:revnumber: 5.2.0
+:revnumber: 5.3.0
:toc: preamble
:toclevels: 3
:icons: font
@@ -175,6 +175,11 @@ Additional session properties not available under the usual `solace.java` prefix
See https://github.com/SolaceProducts/solace-spring-boot/tree/master/solace-spring-boot-starters/solace-java-spring-boot-starter#updating-your-application-properties[JCSMP Spring Boot Auto-Configuration documentation] for more info about `solace.java.apiProperties`.
====
+[TIP]
+====
+The Solace session can be configured to use OAuth2 authentication. See https://github.com/SolaceDev/solace-spring-boot/tree/master/solace-spring-boot-starters/solace-java-spring-boot-starter#using-oauth2-authentication-scheme-with-solace-java-api[JCSMP Spring Boot: Using OAuth2 Authentication Scheme] for more info.
+====
+
==== Solace Consumer Properties
The following properties are available for Solace consumers only and must be prefixed with `spring.cloud.stream.solace.bindings.<bindingName>.consumer.` where `bindingName` looks something like `functionName-in-0` as defined in https://docs.spring.io/spring-cloud-stream/docs/{scst-version}/reference/html/spring-cloud-stream.html#_functional_binding_names[Functional Binding Names].
diff --git a/solace-spring-cloud-starters/solace-spring-cloud-stream-starter/pom.xml b/solace-spring-cloud-starters/solace-spring-cloud-stream-starter/pom.xml
index 19086828..f0777abc 100644
--- a/solace-spring-cloud-starters/solace-spring-cloud-stream-starter/pom.xml
+++ b/solace-spring-cloud-starters/solace-spring-cloud-stream-starter/pom.xml
@@ -5,12 +5,12 @@
com.solace.spring.cloud
solace-spring-cloud-parent
- 4.2.1-SNAPSHOT
+ 4.3.0-SNAPSHOT
../../solace-spring-cloud-parent/pom.xml
spring-cloud-starter-stream-solace
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
jar
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/pom.xml b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/pom.xml
index 1219b38c..88602e52 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/pom.xml
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/pom.xml
@@ -5,12 +5,12 @@
com.solace.spring.cloud
solace-spring-cloud-parent
- 4.2.1-SNAPSHOT
+ 4.3.0-SNAPSHOT
../../solace-spring-cloud-parent/pom.xml
spring-cloud-stream-binder-solace-core
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
jar
Solace Spring Cloud Stream Binder Core
@@ -82,6 +82,12 @@
org.springframework.cloud
spring-cloud-stream-test-binder
test
+
+
+ com.vaadin.external.google
+ android-json
+
+
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/health/handlers/SolaceSessionEventHandler.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/health/handlers/SolaceSessionEventHandler.java
index f95b6a12..ac9091c8 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/health/handlers/SolaceSessionEventHandler.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/main/java/com/solace/spring/cloud/stream/binder/health/handlers/SolaceSessionEventHandler.java
@@ -1,16 +1,22 @@
package com.solace.spring.cloud.stream.binder.health.handlers;
import com.solace.spring.cloud.stream.binder.health.indicators.SessionHealthIndicator;
+import com.solacesystems.jcsmp.DefaultSolaceOAuth2SessionEventHandler;
+import com.solacesystems.jcsmp.JCSMPProperties;
import com.solacesystems.jcsmp.SessionEventArgs;
-import com.solacesystems.jcsmp.SessionEventHandler;
+import com.solacesystems.jcsmp.SolaceSessionOAuth2TokenProvider;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.lang.Nullable;
-public class SolaceSessionEventHandler implements SessionEventHandler {
+public class SolaceSessionEventHandler extends DefaultSolaceOAuth2SessionEventHandler {
private final SessionHealthIndicator sessionHealthIndicator;
private static final Log logger = LogFactory.getLog(SolaceSessionEventHandler.class);
- public SolaceSessionEventHandler(SessionHealthIndicator sessionHealthIndicator) {
+ public SolaceSessionEventHandler(JCSMPProperties jcsmpProperties,
+ @Nullable SolaceSessionOAuth2TokenProvider solaceSessionOAuth2TokenProvider,
+ SessionHealthIndicator sessionHealthIndicator) {
+ super(jcsmpProperties, solaceSessionOAuth2TokenProvider);
this.sessionHealthIndicator = sessionHealthIndicator;
}
@@ -19,6 +25,7 @@ public void handleEvent(SessionEventArgs eventArgs) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Received Solace JCSMP Session event [%s]", eventArgs));
}
+ super.handleEvent(eventArgs);
switch (eventArgs.getEvent()) {
case RECONNECTED -> this.sessionHealthIndicator.up();
case DOWN_ERROR -> this.sessionHealthIndicator.down(eventArgs);
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/inbound/acknowledge/JCSMPAcknowledgementCallbackFactoryIT.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/inbound/acknowledge/JCSMPAcknowledgementCallbackFactoryIT.java
index fc1ccdbf..0cb88601 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/inbound/acknowledge/JCSMPAcknowledgementCallbackFactoryIT.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/inbound/acknowledge/JCSMPAcknowledgementCallbackFactoryIT.java
@@ -193,7 +193,7 @@ public void testRejectFail(@Values(ints = {1, 255}) int numMessages,
logger.info(String.format("Disabling egress for queue %s", queue.getName()));
sempV2Api.config().updateMsgVpnQueue((String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME),
- queue.getName(), new ConfigMsgVpnQueue().egressEnabled(false), null);
+ queue.getName(), new ConfigMsgVpnQueue().egressEnabled(false), null, null);
retryAssert(() -> assertFalse(sempV2Api.monitor()
.getMsgVpnQueue(vpnName, queue.getName(), null)
.getData()
@@ -214,7 +214,7 @@ public void testRejectFail(@Values(ints = {1, 255}) int numMessages,
logger.info(String.format("Enabling egress for queue %s", queue.getName()));
sempV2Api.config().updateMsgVpnQueue((String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME),
- queue.getName(), new ConfigMsgVpnQueue().egressEnabled(true), null);
+ queue.getName(), new ConfigMsgVpnQueue().egressEnabled(true), null, null);
retryAssert(() -> assertTrue(sempV2Api.monitor()
.getMsgVpnQueue(vpnName, queue.getName(), null)
.getData()
@@ -277,7 +277,7 @@ public void testRejectWithErrorQueueFail(@Values(ints = {1, 255}) int numMessage
logger.info(String.format("Disabling ingress for error queue %s",
errorQueueInfrastructure.getErrorQueueName()));
sempV2Api.config().updateMsgVpnQueue((String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME),
- errorQueueInfrastructure.getErrorQueueName(), new ConfigMsgVpnQueue().ingressEnabled(false), null);
+ errorQueueInfrastructure.getErrorQueueName(), new ConfigMsgVpnQueue().ingressEnabled(false), null, null);
retryAssert(() -> assertFalse(sempV2Api.monitor()
.getMsgVpnQueue(vpnName, errorQueueInfrastructure.getErrorQueueName(), null)
.getData()
@@ -349,7 +349,7 @@ public void testRequeueFail(@Values(ints = {1, 255}) int numMessages,
logger.info(String.format("Disabling egress for queue %s", queue.getName()));
sempV2Api.config().updateMsgVpnQueue((String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME),
- queue.getName(), new ConfigMsgVpnQueue().egressEnabled(false), null);
+ queue.getName(), new ConfigMsgVpnQueue().egressEnabled(false), null, null);
retryAssert(() -> assertFalse(sempV2Api.monitor()
.getMsgVpnQueue(vpnName, queue.getName(), null)
.getData()
@@ -370,7 +370,7 @@ public void testRequeueFail(@Values(ints = {1, 255}) int numMessages,
logger.info(String.format("Enabling egress for queue %s", queue.getName()));
sempV2Api.config().updateMsgVpnQueue((String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME),
- queue.getName(), new ConfigMsgVpnQueue().egressEnabled(true), null);
+ queue.getName(), new ConfigMsgVpnQueue().egressEnabled(true), null, null);
retryAssert(() -> assertTrue(sempV2Api.monitor()
.getMsgVpnQueue(vpnName, queue.getName(), null)
.getData()
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/ErrorQueueRepublishCorrelationKeyIT.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/ErrorQueueRepublishCorrelationKeyIT.java
index f2f0c1b1..d5c7d76e 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/ErrorQueueRepublishCorrelationKeyIT.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/ErrorQueueRepublishCorrelationKeyIT.java
@@ -172,7 +172,7 @@ public void testHandleErrorAsyncRetry(boolean isDurable, JCSMPSession jcsmpSessi
logger.info(String.format("Shutting down ingress for queue %s", errorQueue.getName()));
sempV2Api.config().updateMsgVpnQueue(vpnName, errorQueue.getName(),
- new ConfigMsgVpnQueue().ingressEnabled(false), null);
+ new ConfigMsgVpnQueue().ingressEnabled(false), null, null);
retryAssert(() -> assertFalse(sempV2Api.monitor()
.getMsgVpnQueue(vpnName, errorQueue.getName(), null)
.getData()
@@ -183,7 +183,7 @@ public void testHandleErrorAsyncRetry(boolean isDurable, JCSMPSession jcsmpSessi
if (key.getErrorQueueDeliveryAttempt() == errorQueueInfrastructure.getMaxDeliveryAttempts()) {
logger.info(String.format("Starting ingress for queue %s", errorQueue.getName()));
sempV2Api.config().updateMsgVpnQueue(vpnName, errorQueue.getName(),
- new ConfigMsgVpnQueue().ingressEnabled(true), null);
+ new ConfigMsgVpnQueue().ingressEnabled(true), null, null);
retryAssert(() -> assertTrue(sempV2Api.monitor()
.getMsgVpnQueue(vpnName, errorQueue.getName(), null)
.getData()
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/FlowReceiverContainerIT.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/FlowReceiverContainerIT.java
index 21ad5ae1..acb85770 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/FlowReceiverContainerIT.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder-core/src/test/java/com/solace/spring/cloud/stream/binder/util/FlowReceiverContainerIT.java
@@ -773,7 +773,7 @@ public void testReceiveInterruptedByFlowReconnect(@Values(booleans = {false, tru
logger.info(String.format("Disabling egress to queue %s", queue.getName()));
sempV2Api.config().updateMsgVpnQueue(vpnName, queue.getName(), new ConfigMsgVpnQueue().egressEnabled(false),
- null);
+ null, null);
retryAssert(() -> assertFalse(sempV2Api.monitor()
.getMsgVpnQueue(vpnName, queue.getName(), null)
.getData()
@@ -784,7 +784,7 @@ public void testReceiveInterruptedByFlowReconnect(@Values(booleans = {false, tru
logger.info(String.format("Enabling egress to queue %s", queue.getName()));
sempV2Api.config().updateMsgVpnQueue(vpnName, queue.getName(), new ConfigMsgVpnQueue().egressEnabled(true),
- null);
+ null, null);
retryAssert(() -> assertTrue(sempV2Api.monitor()
.getMsgVpnQueue(vpnName, queue.getName(), null)
.getData()
@@ -871,7 +871,7 @@ public void testAcknowledgeAfterFlowReconnect(JCSMPSession jcsmpSession, Queue q
logger.info(String.format("Disabling egress to queue %s", queue.getName()));
sempV2Api.config().updateMsgVpnQueue(vpnName, queue.getName(), new ConfigMsgVpnQueue().egressEnabled(false),
- null);
+ null, null);
retryAssert(() -> assertFalse(sempV2Api.monitor()
.getMsgVpnQueue(vpnName, queue.getName(), null)
.getData()
@@ -881,7 +881,7 @@ public void testAcknowledgeAfterFlowReconnect(JCSMPSession jcsmpSession, Queue q
logger.info(String.format("Enabling egress to queue %s", queue.getName()));
sempV2Api.config().updateMsgVpnQueue(vpnName, queue.getName(), new ConfigMsgVpnQueue().egressEnabled(true),
- null);
+ null, null);
retryAssert(() -> assertTrue(sempV2Api.monitor()
.getMsgVpnQueue(vpnName, queue.getName(), null)
.getData()
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/pom.xml b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/pom.xml
index 60a8c1a5..cbb2d929 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/pom.xml
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/pom.xml
@@ -5,12 +5,12 @@
com.solace.spring.cloud
solace-spring-cloud-parent
- 4.2.1-SNAPSHOT
+ 4.3.0-SNAPSHOT
../../solace-spring-cloud-parent/pom.xml
spring-cloud-stream-binder-solace
- 5.2.1-SNAPSHOT
+ 5.3.0-SNAPSHOT
jar
Solace Spring Cloud Stream Binder
@@ -106,12 +106,23 @@
org.springframework.boot
spring-boot-starter-test
test
+
+
+ com.vaadin.external.google
+ android-json
+
+
org.springframework.boot
spring-boot-starter-web
test
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+ test
+
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/main/java/com/solace/spring/cloud/stream/binder/config/SolaceHealthIndicatorsConfiguration.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/main/java/com/solace/spring/cloud/stream/binder/config/SolaceHealthIndicatorsConfiguration.java
index 948ed4f3..dbd4d68a 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/main/java/com/solace/spring/cloud/stream/binder/config/SolaceHealthIndicatorsConfiguration.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/main/java/com/solace/spring/cloud/stream/binder/config/SolaceHealthIndicatorsConfiguration.java
@@ -6,6 +6,9 @@
import com.solace.spring.cloud.stream.binder.health.handlers.SolaceSessionEventHandler;
import com.solace.spring.cloud.stream.binder.health.indicators.SessionHealthIndicator;
import com.solace.spring.cloud.stream.binder.properties.SolaceSessionHealthProperties;
+import com.solacesystems.jcsmp.JCSMPProperties;
+import com.solacesystems.jcsmp.SolaceSessionOAuth2TokenProvider;
+import jakarta.annotation.Nullable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
@@ -40,11 +43,14 @@ public SolaceBinderHealthContributor solaceBinderHealthContributor(
}
@Bean
- public SolaceSessionEventHandler solaceSessionEventHandler(SolaceBinderHealthContributor healthContributor) {
+ public SolaceSessionEventHandler solaceSessionEventHandler(
+ JCSMPProperties jcsmpProperties,
+ @Nullable SolaceSessionOAuth2TokenProvider solaceSessionOAuth2TokenProvider,
+ SolaceBinderHealthContributor healthContributor) {
if (logger.isDebugEnabled()) {
logger.debug("Creating Solace Session Event Handler for monitoring Health");
}
- return new SolaceSessionEventHandler(healthContributor.getSolaceSessionHealthIndicator());
+ return new SolaceSessionEventHandler(jcsmpProperties, solaceSessionOAuth2TokenProvider, healthContributor.getSolaceSessionHealthIndicator());
}
}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/main/java/com/solace/spring/cloud/stream/binder/config/SolaceMessageChannelBinderConfiguration.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/main/java/com/solace/spring/cloud/stream/binder/config/SolaceMessageChannelBinderConfiguration.java
index c0d31be1..0c056c9a 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/main/java/com/solace/spring/cloud/stream/binder/config/SolaceMessageChannelBinderConfiguration.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/main/java/com/solace/spring/cloud/stream/binder/config/SolaceMessageChannelBinderConfiguration.java
@@ -9,13 +9,15 @@
import com.solacesystems.jcsmp.Context;
import com.solacesystems.jcsmp.ContextProperties;
import com.solacesystems.jcsmp.JCSMPException;
-import com.solacesystems.jcsmp.JCSMPFactory;
import com.solacesystems.jcsmp.JCSMPProperties;
import com.solacesystems.jcsmp.JCSMPSession;
+import com.solacesystems.jcsmp.SolaceSessionOAuth2TokenProvider;
+import com.solacesystems.jcsmp.SpringJCSMPFactory;
import com.solacesystems.jcsmp.impl.JCSMPBasicSession;
import jakarta.annotation.PostConstruct;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -29,7 +31,7 @@
import static com.solacesystems.jcsmp.XMLMessage.Outcome.REJECTED;
@Configuration
-@Import(SolaceHealthIndicatorsConfiguration.class)
+@Import({SolaceHealthIndicatorsConfiguration.class, OAuth2ClientAutoConfiguration.class})
@EnableConfigurationProperties({SolaceExtendedBindingProperties.class})
public class SolaceMessageChannelBinderConfiguration {
private final JCSMPProperties jcsmpProperties;
@@ -39,29 +41,37 @@ public class SolaceMessageChannelBinderConfiguration {
private JCSMPSession jcsmpSession;
private Context context;
+ @Nullable
+ private SolaceSessionOAuth2TokenProvider solaceSessionOAuth2TokenProvider;
+
private static final Log logger = LogFactory.getLog(SolaceMessageChannelBinderConfiguration.class);
public SolaceMessageChannelBinderConfiguration(JCSMPProperties jcsmpProperties,
SolaceExtendedBindingProperties solaceExtendedBindingProperties,
- @Nullable SolaceSessionEventHandler eventHandler) {
+ @Nullable SolaceSessionEventHandler eventHandler,
+ @Nullable SolaceSessionOAuth2TokenProvider solaceSessionOAuth2TokenProvider) {
this.jcsmpProperties = jcsmpProperties;
this.solaceExtendedBindingProperties = solaceExtendedBindingProperties;
this.solaceSessionEventHandler = eventHandler;
+ this.solaceSessionOAuth2TokenProvider = solaceSessionOAuth2TokenProvider;
}
@PostConstruct
private void initSession() throws JCSMPException {
- JCSMPProperties jcsmpProperties = (JCSMPProperties) this.jcsmpProperties.clone();
- jcsmpProperties.setProperty(JCSMPProperties.CLIENT_INFO_PROVIDER, new SolaceBinderClientInfoProvider());
+ JCSMPProperties solaceJcsmpProperties = (JCSMPProperties) this.jcsmpProperties.clone();
+ solaceJcsmpProperties.setProperty(JCSMPProperties.CLIENT_INFO_PROVIDER, new SolaceBinderClientInfoProvider());
try {
if (solaceSessionEventHandler != null) {
if (logger.isDebugEnabled()) {
logger.debug("Registering Solace Session Event handler on session");
}
- context = JCSMPFactory.onlyInstance().createContext(new ContextProperties());
- jcsmpSession = JCSMPFactory.onlyInstance().createSession(jcsmpProperties, context, solaceSessionEventHandler);
+
+ SpringJCSMPFactory springJCSMPFactory = new SpringJCSMPFactory(solaceJcsmpProperties, solaceSessionOAuth2TokenProvider);
+ context = springJCSMPFactory.createContext(new ContextProperties());
+ jcsmpSession = springJCSMPFactory.createSession(context, solaceSessionEventHandler);
} else {
- jcsmpSession = JCSMPFactory.onlyInstance().createSession(jcsmpProperties);
+ SpringJCSMPFactory springJCSMPFactory = new SpringJCSMPFactory(solaceJcsmpProperties, solaceSessionOAuth2TokenProvider);
+ jcsmpSession = springJCSMPFactory.createSession();
}
logger.info(String.format("Connecting JCSMP session %s", jcsmpSession.getSessionName()));
jcsmpSession.connect();
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/main/resources/META-INF/shared.beans b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/main/resources/META-INF/shared.beans
index a13d8ca4..875899cd 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/main/resources/META-INF/shared.beans
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/main/resources/META-INF/shared.beans
@@ -1,2 +1,5 @@
com.solace.spring.cloud.stream.binder.meter.SolaceMessageMeterBinder
-com.solace.spring.cloud.stream.binder.meter.SolaceMeterAccessor
\ No newline at end of file
+com.solace.spring.cloud.stream.binder.meter.SolaceMeterAccessor
+org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
+org.springframework.security.oauth2.client.OAuth2AuthorizedClientService
+org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/it/util/semp/SempClientException.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/it/util/semp/SempClientException.java
new file mode 100644
index 00000000..3a475da0
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/it/util/semp/SempClientException.java
@@ -0,0 +1,135 @@
+package com.solace.it.util.semp;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+public class SempClientException extends RuntimeException {
+
+ public SempClientException(String message) {
+ super(message);
+ }
+
+ public SempClientException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public SempClientException(Throwable cause) {
+ super(cause);
+ }
+
+
+ /**
+ * A class for raising errors when client authentication fails.
+ *
+ * @since 1.0
+ */
+ @ProviderType
+ public static class AuthenticationException extends SempClientException {
+
+ private static final long serialVersionUID = -4840876322728337412L;
+
+ /**
+ * Creates an instance of {@code AuthenticationException} when client authentication fails with
+ * an additional message.
+ *
+ * @param message the detailed message. The detailed message is saved for later retrieval by the
+ * {@link #getMessage()} method.
+ * @since 1.0
+ */
+ public AuthenticationException(String message) {
+ super(message);
+ }
+
+ /**
+ * Creates an instance of {@code AuthenticationException} when client authentication fails with
+ * an additional message and a {@code Throwable}.
+ *
+ * @param message the detailed message. The detailed message is saved for later retrieval by the
+ * {@link #getMessage()} method.
+ * @param t the cause that is saved for later retrieval by the {@link #getCause()} method.
+ * A {@code null} value is permitted, and indicates that the cause is
+ * non-existent or unknown.
+ * @since 1.0
+ */
+ public AuthenticationException(String message, Throwable t) {
+ super(message, t);
+ }
+ }
+
+ /**
+ * A class for raising errors when client authorizations fails, client authorizations unsupported
+ * or not enabled for the service
+ *
+ * @since 1.0
+ */
+ @ProviderType
+ public static class AuthorizationException extends SempClientException {
+
+ private static final long serialVersionUID = -2315053666285971354L;
+
+ /**
+ * Creates an instance of {@code AuthorizationException} when client authorizations fails with
+ * an additional message.
+ *
+ * @param message the detailed message. The detailed message is saved for later retrieval by the
+ * {@link #getMessage()} method.
+ * @since 1.0
+ */
+ public AuthorizationException(String message) {
+ super(message);
+ }
+
+ /**
+ * Creates an instance of {@code AuthorizationException} when client authorizations fails with
+ * an additional message and a {@code Throwable}.
+ *
+ * @param message the detailed message. The detailed message is saved for later retrieval by the
+ * {@link #getMessage()} method.
+ * @param t the cause that is saved for later retrieval by the {@link #getCause()} method.
+ * A {@code null} value is permitted, and indicates that the cause is
+ * non-existent or unknown.
+ * @since 1.0
+ */
+ public AuthorizationException(String message, Throwable t) {
+ super(message, t);
+ }
+ }
+
+ /**
+ * A class for raising errors when a remote resource like a queue, vpn is not found on a broker.
+ *
+ * @since 1.0
+ */
+ @ProviderType
+ public static class MissingResourceException extends SempClientException {
+
+ private static final long serialVersionUID = 3777381415318250678L;
+
+ /**
+ * Creates an instance of {@code MissingResourceException} with a detailed message of missing a
+ * resource situation.
+ *
+ * @param message the detailed message. The detailed message is saved for later retrieval by the
+ * {@link #getMessage()} method.
+ * @since 1.0
+ */
+ public MissingResourceException(String message) {
+ super(message);
+ }
+
+ /**
+ * Creates an instance of {@code MissingResourceException} with a detailed message of missing a
+ * * resource situation and a {@code Throwable}.
+ *
+ * @param message the detailed message. The detailed message is saved for later retrieval by the
+ * {@link #getMessage()} method.
+ * @param t the cause that is saved for later retrieval by the {@link #getCause()} method.
+ * A {@code null} value is permitted, and indicates that the cause is
+ * non-existent or unknown.
+ * @since 1.0
+ */
+ public MissingResourceException(String message, Throwable t) {
+ super(message, t);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/it/util/semp/config/BrokerConfiguratorBuilder.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/it/util/semp/config/BrokerConfiguratorBuilder.java
new file mode 100644
index 00000000..e8b2969a
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/it/util/semp/config/BrokerConfiguratorBuilder.java
@@ -0,0 +1,912 @@
+package com.solace.it.util.semp.config;
+
+import com.solace.it.util.semp.SempClientException;
+import com.solace.it.util.semp.SempClientException.AuthenticationException;
+import com.solace.it.util.semp.SempClientException.AuthorizationException;
+import com.solace.it.util.semp.SempClientException.MissingResourceException;
+import com.solace.test.integration.semp.v2.SempV2Api;
+import com.solace.test.integration.semp.v2.config.ApiClient;
+import com.solace.test.integration.semp.v2.config.ApiException;
+import com.solace.test.integration.semp.v2.config.api.AuthenticationOauthProfileApi;
+import com.solace.test.integration.semp.v2.config.api.AuthorizationGroupApi;
+import com.solace.test.integration.semp.v2.config.api.CertAuthorityApi;
+import com.solace.test.integration.semp.v2.config.api.ClientProfileApi;
+import com.solace.test.integration.semp.v2.config.api.ClientUsernameApi;
+import com.solace.test.integration.semp.v2.config.api.MsgVpnApi;
+import com.solace.test.integration.semp.v2.config.api.QueueApi;
+import com.solace.test.integration.semp.v2.config.auth.HttpBasicAuth;
+import com.solace.test.integration.semp.v2.config.model.ConfigCertAuthority;
+import com.solace.test.integration.semp.v2.config.model.ConfigCertAuthorityResponse;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpn;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpn.AuthenticationBasicTypeEnum;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnAclProfile;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnAclProfilesResponse;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnAuthenticationOauthProfile;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnAuthenticationOauthProfileResponse;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnAuthorizationGroup;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnAuthorizationGroupResponse;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnClientProfile;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnClientProfileResponse;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnClientUsername;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnClientUsernameResponse;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnQueue;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnQueue.AccessTypeEnum;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnQueue.PermissionEnum;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnQueueResponse;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnQueueSubscription;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnQueueSubscriptionResponse;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnQueuesResponse;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnResponse;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnsResponse;
+import com.solace.test.integration.semp.v2.config.model.ConfigSempMetaOnlyResponse;
+import java.util.Collection;
+import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+
+/**
+ * Builder for entity that can perform administrator level configuration tasks on a messaging
+ * broker
+ */
+public class BrokerConfiguratorBuilder {
+
+ static final Logger logger = LoggerFactory.getLogger(BrokerConfiguratorBuilder.class);
+
+ private final ApiClient theClient;
+
+ public static BrokerConfiguratorBuilder create(SempV2Api sempV2Api) {
+ return new BrokerConfiguratorBuilder(sempV2Api);
+ }
+
+ private BrokerConfiguratorBuilder(SempV2Api sempV2Api) {
+ this.theClient = sempV2Api.config().getApiClient();
+ }
+
+ /**
+ * Enables http request level logging.
+ *
+ * @return
+ */
+ public BrokerConfiguratorBuilder withDebugLog() {
+ this.theClient.setDebugging(true);
+ return this;
+ }
+
+ public BrokerConfiguratorBuilder withBasicAuth(String userName, String password) {
+ this.theClient.setUsername(userName);
+ this.theClient.setPassword(password);
+ return this;
+ }
+
+ public BrokerConfigurator build() {
+ return new BrokerConfigurator(this.theClient);
+ }
+
+ static T wrapAndRethrowException(ApiException e, String operation, ApiClient apiClient)
+ throws SempClientException {
+ final String userName = ((HttpBasicAuth) apiClient
+ .getAuthentication("basicAuth")).getUsername();
+ if (HttpStatus.NOT_FOUND.value() == e.getCode()) {
+ throw new MissingResourceException(e.getMessage());
+ } else if (HttpStatus.UNAUTHORIZED.value() == e.getCode()) {
+ throw new AuthenticationException(
+ String.format("Invalid credentials provided for user %s to perform %s", userName,
+ operation), e);
+ } else if (HttpStatus.FORBIDDEN.value() == e.getCode()) {
+ throw new AuthorizationException(
+ String.format("User %s not authorized to perform %s", userName, operation), e);
+ } else {
+ throw new SempClientException(String.format("%s failed", operation), e);
+ }
+ }
+
+ /**
+ * Entity that can perform administrator level configuration tasks on a messaging broker
+ */
+ public static class BrokerConfigurator {
+
+ private final ApiClient theClient;
+
+ private BrokerConfigurator(final ApiClient theClient) {
+ this.theClient = theClient;
+ }
+
+ public MessageVpns vpns() {
+ return new MessageVpns(this.theClient);
+ }
+
+ public CertAuthorities certAuthorities() {
+ return new CertAuthorities(this.theClient);
+ }
+
+ public Queues queues() {
+ return new Queues(this.theClient);
+ }
+ }
+
+ public static class CertAuthorities {
+
+ private final CertAuthorityApi certAuthorityApi;
+ private final ApiClient apiClient;
+
+ private CertAuthorities(ApiClient apiClient) {
+ this.certAuthorityApi = new CertAuthorityApi(apiClient);
+ this.apiClient = apiClient;
+ }
+
+ public void setupCertAuthority(String certAuthorityName, String certContent) {
+ final ConfigCertAuthority ca = new ConfigCertAuthority();
+ ca.certAuthorityName(certAuthorityName).certContent(certContent);
+ try {
+ final ConfigCertAuthorityResponse response = this.certAuthorityApi
+ .createCertAuthority(ca, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ return;
+ } else {
+ throw new SempClientException(
+ String.format("CertAuthority %s could not be upploaded, response code: %s",
+ certAuthorityName, response.getMeta().getResponseCode()));
+ }
+ } catch (ApiException e) {
+ BrokerConfiguratorBuilder
+ .wrapAndRethrowException(e, "Query VPNs", apiClient);
+ }
+ }
+ }
+
+ public static class MessageVpns {
+
+ private final String VPN_CREATE_FAIL = "Message vpn %s could not be created";
+ private final String VPN_UPDATE_FAIL = "Message vpn %s could not be updated";
+ private final String VPN_DELETE_FAIL = "Message vpn %s could not be deleted";
+
+ private final MsgVpnApi vpnApi;
+ private final ApiClient apiClient;
+
+ private MessageVpns(ApiClient apiClient) {
+ this.vpnApi = new MsgVpnApi(apiClient);
+ this.apiClient = apiClient;
+ }
+
+ /**
+ * Create Message VPN from a given entity
+ *
+ * @param vpn entity with vpn properties to be created
+ * @return instance of freshly created message vpn
+ */
+ public ConfigMsgVpn createVpn(ConfigMsgVpn vpn) {
+ try {
+ final ConfigMsgVpnResponse response = this.vpnApi.createMsgVpn(vpn, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ return response.getData();
+ } else {
+ throw new SempClientException(String.format(VPN_CREATE_FAIL, vpn.getMsgVpnName()));
+ }
+ } catch (ApiException e) {
+ return BrokerConfiguratorBuilder.wrapAndRethrowException(e, "Create VPN", this.apiClient);
+ }
+ }
+
+ /**
+ * updates Message VPN from a given entity
+ *
+ * @param vpn entity with vpn properties to be updated
+ * @return instance of freshly updated message vpn
+ */
+ public ConfigMsgVpn updateVpn(ConfigMsgVpn vpn) {
+ try {
+ final ConfigMsgVpnResponse response = this.vpnApi
+ .updateMsgVpn(vpn.getMsgVpnName(), vpn, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ return response.getData();
+ } else {
+ throw new SempClientException(String.format(VPN_UPDATE_FAIL, vpn.getMsgVpnName()));
+ }
+ } catch (ApiException e) {
+ return BrokerConfiguratorBuilder.wrapAndRethrowException(e, "Update VPN", this.apiClient);
+ }
+ }
+
+ /**
+ * Create Message VPN with default settings and no oauth
+ *
+ * @param msgVpnName name of the vpn to be created
+ * @return instance of freshly created message vpn
+ * @throws SempClientException thrown if something goes wrong; original exception is returned
+ * wrapped
+ */
+ public ConfigMsgVpn createVpn(String msgVpnName) {
+ return createVpn(msgVpnName, 50L, false);
+ }
+
+ public ConfigMsgVpn createVpn(String msgVpnName, Long msgSpool,
+ boolean internalBasicAuthEnabled) {
+ ConfigMsgVpn vpn = new ConfigMsgVpn().enabled(true)
+ .msgVpnName(msgVpnName)
+ .maxMsgSpoolUsage(msgSpool)
+ .authenticationBasicType(internalBasicAuthEnabled ? AuthenticationBasicTypeEnum.INTERNAL
+ : AuthenticationBasicTypeEnum.NONE);
+ return createVpn(vpn);
+ }
+
+ /**
+ * Creates basic vpn copy with disabled extended services like MQTT, Rest, AMQP
+ *
+ * @param from
+ * @param to
+ * @return
+ */
+ public ConfigMsgVpn copyVpn(String from, String to) {
+ ConfigMsgVpn vpn = queryVpn(from);
+ vpn.msgVpnName(to)
+ .serviceAmqpPlainTextEnabled(false)
+ .serviceAmqpTlsEnabled(false)
+ .serviceMqttPlainTextEnabled(false)
+ .serviceMqttTlsEnabled(false)
+ .serviceMqttWebSocketEnabled(false)
+ .serviceMqttTlsWebSocketEnabled(false)
+ .serviceRestIncomingPlainTextEnabled(false)
+ .serviceRestIncomingTlsEnabled(false);
+ return createVpn(vpn);
+ }
+
+ /**
+ * Deletes message vpn with a given name
+ *
+ * @param msgVpnName name of the vpn
+ */
+ public void deleteVpn(String msgVpnName) {
+ try {
+ final ConfigSempMetaOnlyResponse response = this.vpnApi.deleteMsgVpn(msgVpnName);
+ if (HttpStatus.OK.value() != response.getMeta().getResponseCode()) {
+ throw new SempClientException(String.format(VPN_DELETE_FAIL, msgVpnName));
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ /**
+ * Disables message vpn
+ *
+ * @param msgVpnName name of the vpn to be disabled
+ */
+ public void disableVpn(String msgVpnName) {
+ final ConfigMsgVpn vpn = queryVpn(msgVpnName);
+ vpn.enabled(false);
+ updateVpn(vpn);
+ }
+
+ /**
+ * Queries all vpns on a broker
+ *
+ * @return collection with all vpns on a broker
+ * @throws SempClientException thrown if something goes wrong; original exception is returned
+ * wrapped
+ */
+ public Collection queryAllVpns() throws SempClientException {
+ try {
+ final ConfigMsgVpnsResponse response = this.vpnApi
+ .getMsgVpns(null, null, null, null, null);
+ final Collection cl = response.getData();
+ whenNotFound(cl);
+ return cl;
+ } catch (ApiException e) {
+ return BrokerConfiguratorBuilder
+ .wrapAndRethrowException(e, "Query VPNs", this.apiClient);
+ }
+ }
+
+ public ConfigMsgVpn queryVpn(String msgVpnName) throws SempClientException {
+ try {
+ final ConfigMsgVpnResponse response = this.vpnApi
+ .getMsgVpn(msgVpnName, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ final ConfigMsgVpn cl = response.getData();
+ return cl;
+ } else {
+ throw new SempClientException(
+ String.format("Message vpn %s could not be found", msgVpnName));
+ }
+ } catch (ApiException e) {
+ return BrokerConfiguratorBuilder
+ .wrapAndRethrowException(e, "Query VPNs", this.apiClient);
+ }
+ }
+
+ /**
+ * Enables internal basic auth on for the given vpn
+ *
+ * @param msgVpnName name of the vpn
+ * @throws SempClientException thrown if something goes wrong; original exception is returned
+ * wrapped
+ */
+ public void enableBasicAuth(String msgVpnName) throws SempClientException {
+ final ConfigMsgVpn vpn = queryVpn(msgVpnName);
+ vpn.setAuthenticationBasicType(AuthenticationBasicTypeEnum.INTERNAL);
+ try {
+ final ConfigMsgVpnResponse response = this.vpnApi.updateMsgVpn(msgVpnName, vpn, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ logger.debug("Basic auth enabled for vpn: {}", msgVpnName);
+ return;
+ } else {
+ throw new SempClientException(
+ String.format("Message vpn %s could not be updated", msgVpnName));
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ /**
+ * Disables internal basic auth on for the given vpn
+ *
+ * @param msgVpnName name of the vpn
+ * @throws SempClientException thrown if something goes wrong; original exception is returned
+ * wrapped
+ */
+ public void disableBasicAuth(String msgVpnName) throws SempClientException {
+ final ConfigMsgVpn vpn = queryVpn(msgVpnName);
+ vpn.setAuthenticationBasicType(AuthenticationBasicTypeEnum.NONE);
+ try {
+ final ConfigMsgVpnResponse response = this.vpnApi.updateMsgVpn(msgVpnName, vpn, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ logger.debug("Basic auth disabled for vpn: {}", msgVpnName);
+ return;
+ } else {
+ throw new SempClientException(
+ String.format("Message vpn %s could not be updated", msgVpnName));
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ /**
+ * Enables internal client auth on for the given vpn
+ *
+ * @param msgVpnName name of the vpn
+ * @throws SempClientException thrown if something goes wrong; original exception is returned
+ * wrapped
+ */
+ public void enableClientCertAuth(String msgVpnName) throws SempClientException {
+ final ConfigMsgVpn vpn = queryVpn(msgVpnName);
+ vpn.setAuthenticationClientCertEnabled(true);
+ vpn.authenticationClientCertValidateDateEnabled(true);
+ vpn.authenticationClientCertAllowApiProvidedUsernameEnabled(true);
+ try {
+ final ConfigMsgVpnResponse response = this.vpnApi.updateMsgVpn(msgVpnName, vpn, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ logger.debug("Client certificate auth enabled for vpn: {}", msgVpnName);
+ return;
+ } else {
+ throw new SempClientException(
+ String.format("Message vpn %s could not be updated", msgVpnName));
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ /**
+ * Disables internal client auth on for the given vpn
+ *
+ * @param msgVpnName name of the vpn
+ * @throws SempClientException thrown if something goes wrong; original exception is returned
+ * wrapped
+ */
+ public void disableClientCertAuth(String msgVpnName) throws SempClientException {
+ final ConfigMsgVpn vpn = queryVpn(msgVpnName);
+ vpn.setAuthenticationClientCertEnabled(false);
+ try {
+ final ConfigMsgVpnResponse response = this.vpnApi.updateMsgVpn(msgVpnName, vpn, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ logger.debug("Basic auth disabled for vpn: {}", msgVpnName);
+ return;
+ } else {
+ throw new SempClientException(
+ String.format("Message vpn %s could not be updated", msgVpnName));
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ public void enableKerberosAuth(String msgVpnName, boolean allowApiProvidedUsername)
+ throws SempClientException {
+ final ConfigMsgVpn vpn = queryVpn(msgVpnName);
+ vpn.authenticationKerberosEnabled(true);
+ vpn.authenticationKerberosAllowApiProvidedUsernameEnabled(allowApiProvidedUsername);
+ try {
+ final ConfigMsgVpnResponse response = this.vpnApi.updateMsgVpn(msgVpnName, vpn, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ logger.debug("Kerberos auth enabled for vpn: {}", msgVpnName);
+ return;
+ } else {
+ throw new SempClientException(
+ String.format("Message vpn %s could not be updated", msgVpnName));
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ public void disableKerberosAuth(String msgVpnName)
+ throws SempClientException {
+ final ConfigMsgVpn vpn = queryVpn(msgVpnName);
+ vpn.authenticationKerberosEnabled(false);
+ try {
+ final ConfigMsgVpnResponse response = this.vpnApi.updateMsgVpn(msgVpnName, vpn, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ logger.debug("Kerberos auth disabled for vpn: {}", msgVpnName);
+ return;
+ } else {
+ throw new SempClientException(
+ String.format("Message vpn %s could not be updated", msgVpnName));
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ public Collection queryAclProfile(String msgVpnName)
+ throws SempClientException {
+ try {
+ final ConfigMsgVpnAclProfilesResponse response = this.vpnApi
+ .getMsgVpnAclProfiles(msgVpnName, null, null, null, null, null);
+ final Collection cl = response.getData();
+ whenNotFound(cl);
+ return cl;
+ } catch (ApiException e) {
+ return BrokerConfiguratorBuilder
+ .wrapAndRethrowException(e, "Query AlcProfiles", this.apiClient);
+ }
+ }
+
+ private void whenNotFound(Collection> collection)
+ throws SempClientException {
+ if (collection == null || collection.isEmpty()) {
+ final String userName = ((HttpBasicAuth) vpnApi
+ .getApiClient()
+ .getAuthentication("basicAuth"))
+ .getUsername();
+ throw new MissingResourceException("Can't find resource");
+ }
+ }
+
+ public void enableOAuthAuth(String msgVpnName) throws SempClientException {
+ final ConfigMsgVpn vpn = queryVpn(msgVpnName);
+ vpn.setAuthenticationOauthEnabled(true);
+ try {
+ final ConfigMsgVpnResponse response = this.vpnApi.updateMsgVpn(msgVpnName, vpn, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ logger.debug("OAuth authentication enabled for vpn: {}", msgVpnName);
+ return;
+ } else {
+ throw new SempClientException(String.format(VPN_UPDATE_FAIL, msgVpnName));
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ public void disableOAuthAuth(String msgVpnName) throws SempClientException {
+ final ConfigMsgVpn vpn = queryVpn(msgVpnName);
+ vpn.setAuthenticationOauthEnabled(false);
+ try {
+ final ConfigMsgVpnResponse response = this.vpnApi.updateMsgVpn(msgVpnName, vpn, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ logger.debug("OAuth authentication disabled for vpn: {}", msgVpnName);
+ return;
+ } else {
+ throw new SempClientException(String.format(VPN_UPDATE_FAIL, msgVpnName));
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ public ConfigMsgVpnClientUsername createClientUsername(String msgVpnName,
+ String clientUsername) {
+ try {
+ final ClientUsernameApi clientUsernameApi = new ClientUsernameApi(this.apiClient);
+ final ConfigMsgVpnClientUsername msgVpnClientUsername = new ConfigMsgVpnClientUsername()
+ .clientUsername(clientUsername)
+ .clientProfileName("default")
+ .aclProfileName("default")
+ .enabled(true);
+ ConfigMsgVpnClientUsernameResponse response = clientUsernameApi.createMsgVpnClientUsername(
+ msgVpnName, msgVpnClientUsername, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ return response.getData();
+ } else {
+ throw new SempClientException("MsgVpnClientUsername Creation Failed");
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ public void deleteClientUsername(String msgVpnName, String clientUsername) {
+ try {
+ final ClientUsernameApi clientUsernameApi = new ClientUsernameApi(this.apiClient);
+ final ConfigSempMetaOnlyResponse response = clientUsernameApi.deleteMsgVpnClientUsername(
+ msgVpnName, clientUsername);
+ if (HttpStatus.OK.value() != response.getMeta().getResponseCode()) {
+ throw new SempClientException(
+ String.format("Could not delete Client Username %s for Message VPN %s",
+ clientUsername, msgVpnName));
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ public ConfigMsgVpnClientUsername enableClientUsername(String msgVpnName,
+ String clientUsername) {
+ try {
+ final ClientUsernameApi clientUsernameApi = new ClientUsernameApi(this.apiClient);
+ final ConfigMsgVpnClientUsername msgVpnClientUsername = new ConfigMsgVpnClientUsername()
+ .clientUsername(clientUsername)
+ .enabled(true);
+ ConfigMsgVpnClientUsernameResponse response = clientUsernameApi.updateMsgVpnClientUsername(
+ msgVpnName, clientUsername, msgVpnClientUsername, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ return response.getData();
+ } else {
+ throw new SempClientException("MsgVpnClientUsername Creation Failed");
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ public ConfigMsgVpnClientProfile updateClientProfile(String msgVpnName,
+ String clientProfileName) {
+ try {
+ final ClientProfileApi clientProfileApi = new ClientProfileApi(this.apiClient);
+ ConfigMsgVpnClientProfile clientProfile = new ConfigMsgVpnClientProfile()
+ .clientProfileName(clientProfileName)
+ .allowGuaranteedEndpointCreateEnabled(true)
+ .allowGuaranteedMsgReceiveEnabled(true)
+ .allowGuaranteedMsgSendEnabled(true);
+ ConfigMsgVpnClientProfileResponse response = clientProfileApi.updateMsgVpnClientProfile(
+ msgVpnName, clientProfileName, clientProfile, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ return response.getData();
+ } else {
+ throw new SempClientException("MsgVpnClientUsername Creation Failed");
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ public ConfigMsgVpnAuthenticationOauthProfile createOAuthProfile(String msgVpnName,
+ ConfigMsgVpnAuthenticationOauthProfile profile) {
+ try {
+ AuthenticationOauthProfileApi oAuthProfileApi = new AuthenticationOauthProfileApi(
+ this.apiClient);
+ ConfigMsgVpnAuthenticationOauthProfileResponse response = oAuthProfileApi.createMsgVpnAuthenticationOauthProfile(
+ msgVpnName, profile, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ return response.getData();
+ } else {
+ throw new SempClientException("AuthenticationOauthProfileApi Creation Failed");
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ public void deleteOAuthProfile(String msgVpnName, String oAuthProfileName) {
+ try {
+ AuthenticationOauthProfileApi oAuthProfileApi = new AuthenticationOauthProfileApi(
+ this.apiClient);
+ final ConfigSempMetaOnlyResponse response = oAuthProfileApi.deleteMsgVpnAuthenticationOauthProfile(
+ msgVpnName, oAuthProfileName);
+ if (HttpStatus.OK.value() != response.getMeta().getResponseCode()) {
+ throw new SempClientException(
+ String.format("Could not be delete OAuth Profile %s for Message VPN %s",
+ oAuthProfileName, msgVpnName));
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ public ConfigMsgVpnAuthorizationGroup createAuthorizationGroup(String msgVpnName,
+ ConfigMsgVpnAuthorizationGroup request) {
+ try {
+ AuthorizationGroupApi authorizationGroupApi = new AuthorizationGroupApi(this.apiClient);
+ ConfigMsgVpnAuthorizationGroupResponse response = authorizationGroupApi.createMsgVpnAuthorizationGroup(
+ msgVpnName, request, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ return response.getData();
+ } else {
+ throw new SempClientException("AuthenticationOauthProfileApi Creation Failed");
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+
+ public void deleteAuthorizationGroup(String msgVpnName, String authorizationGroupName) {
+ try {
+ AuthorizationGroupApi authorizationGroupApi = new AuthorizationGroupApi(this.apiClient);
+ final ConfigSempMetaOnlyResponse response = authorizationGroupApi.deleteMsgVpnAuthorizationGroup(
+ msgVpnName, authorizationGroupName);
+ if (HttpStatus.OK.value() != response.getMeta().getResponseCode()) {
+ throw new SempClientException(
+ String.format("Could not be delete Authorization Group %s for Message VPN %s",
+ authorizationGroupName, msgVpnName));
+ }
+ } catch (ApiException e) {
+ throw new SempClientException(e);
+ }
+ }
+ }
+
+ /**
+ * Api to create/update queues and topic subscriptions
+ *
+ * @exclude
+ */
+ public static class Queues {
+
+ private final QueueApi queueApi;
+ private final ApiClient apiClient;
+
+ private Queues(ApiClient apiClient) {
+ this.queueApi = new QueueApi(apiClient);
+ this.apiClient = apiClient;
+ }
+
+ public void createQueue(String msgVpnName, String queueName, boolean exclusive)
+ throws SempClientException {
+ createQueue(msgVpnName, queueName, exclusive, PermissionEnum.DELETE);
+ }
+
+ public void createPartitionedQueue(String msgVpnName, String queueName, int partitionsCount)
+ throws SempClientException {
+ createQueue(msgVpnName, queueName, false, PermissionEnum.DELETE, partitionsCount);
+ }
+
+ public void updatePartitionCount(String msgVpnName, String queueName, int newPartitionsCount)
+ throws SempClientException {
+ final ConfigMsgVpnQueue queue = queryQueue(msgVpnName, queueName);
+ queue.setPartitionCount(newPartitionsCount);
+ updateQueue(queue);
+ }
+
+ public void createQueue(String msgVpnName, String queueName, boolean exclusive,
+ PermissionEnum permission) throws SempClientException {
+ createQueue(msgVpnName, queueName, exclusive, permission, 0);
+ }
+
+ public void createQueue(String msgVpnName, String queueName, boolean exclusive,
+ PermissionEnum permission, int partitionsCount) throws SempClientException {
+ final ConfigMsgVpnQueue queue = new ConfigMsgVpnQueue();
+ queue.setQueueName(queueName);
+ queue.setEgressEnabled(true);
+ queue.setIngressEnabled(true);
+ queue.setPermission(permission);
+ queue.setMaxBindCount(10000L);
+ queue.setMaxMsgSpoolUsage(1500L);
+
+ if (exclusive) {
+ queue.setAccessType(AccessTypeEnum.EXCLUSIVE);
+ } else {
+ queue.setAccessType(AccessTypeEnum.NON_EXCLUSIVE);
+ }
+
+ if (partitionsCount > 0) {
+ queue.setPartitionCount(partitionsCount);
+ }
+
+ try {
+ final ConfigMsgVpnQueueResponse response = this.queueApi
+ .createMsgVpnQueue(msgVpnName, queue, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ logger.debug("Queue {} created in vpn {}", queueName, msgVpnName);
+ return;
+ } else {
+ throw new SempClientException(
+ String.format("Queue %s could not be created", msgVpnName));
+ }
+ } catch (ApiException e) {
+ BrokerConfiguratorBuilder
+ .wrapAndRethrowException(e, "Creation of a queue", this.apiClient);
+ }
+ }
+
+ public void updateQueue(ConfigMsgVpnQueue q) throws SempClientException {
+ try {
+ final ConfigMsgVpnQueueResponse response = this.queueApi
+ .updateMsgVpnQueue(q.getMsgVpnName(), q.getQueueName(), q, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ logger.debug("Queue {} updated in vpn {}", q.getQueueName(), q.getMsgVpnName());
+ return;
+ } else {
+ throw new SempClientException(
+ String.format("Queue %s could not be updated", q.getMsgVpnName()));
+ }
+ } catch (ApiException e) {
+ BrokerConfiguratorBuilder
+ .wrapAndRethrowException(e, "Update of a queue", this.apiClient);
+ }
+ }
+
+ private ConfigMsgVpnQueue queryQueue(String msgVpnName, String queueName)
+ throws SempClientException {
+ try {
+ final ConfigMsgVpnQueueResponse response = this.queueApi.getMsgVpnQueue(msgVpnName,
+ queueName, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ final ConfigMsgVpnQueue vpnQueue = response.getData();
+ return vpnQueue;
+ } else {
+ throw new SempClientException(
+ String.format("Queue %s could not be found", msgVpnName));
+ }
+ } catch (ApiException e) {
+ return BrokerConfiguratorBuilder.wrapAndRethrowException(e, "Creation of a queue",
+ this.apiClient);
+ }
+ }
+
+ public void addSubscriptionToQueue(String msgVpnName, String queueName,
+ String subscriptionTopic)
+ throws SempClientException {
+ final ConfigMsgVpnQueueSubscription subscription = new ConfigMsgVpnQueueSubscription();
+ subscription.setMsgVpnName(msgVpnName);
+ subscription.setQueueName(queueName);
+ subscription.setSubscriptionTopic(subscriptionTopic);
+
+ try {
+ final ConfigMsgVpnQueueSubscriptionResponse response = this.queueApi
+ .createMsgVpnQueueSubscription(msgVpnName, queueName, subscription, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ logger.debug("Subscription is {} created for the queue {} in vpn {}", subscriptionTopic,
+ queueName, msgVpnName);
+ return;
+ } else {
+ throw new SempClientException(
+ String.format("Subscription %s could not be created", msgVpnName));
+ }
+ } catch (ApiException e) {
+ BrokerConfiguratorBuilder
+ .wrapAndRethrowException(e, "Creation of a subscription", this.apiClient);
+ }
+ }
+
+ public void disableEgressOnQueue(String msgVpnName, String queueName) {
+ try {
+ final ConfigMsgVpnQueuesResponse response = this.queueApi
+ .getMsgVpnQueues(msgVpnName, 10, null, null, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ final List queues = response.getData();
+ ConfigMsgVpnQueue q = queues.stream().filter(queue -> {
+ if (queueName.equals(queue.getQueueName())) {
+ return true;
+ }
+ return false;
+ }).findFirst().orElseThrow(() -> {
+ return new SempClientException(
+ String.format("Can't shutdown the queue %s", msgVpnName));
+ });
+
+ q.setEgressEnabled(false);
+ this.queueApi.updateMsgVpnQueue(msgVpnName, queueName, q, null, null);
+ } else {
+ throw new SempClientException(
+ String.format("Can't shutdown the queue %s", queueName));
+ }
+ } catch (ApiException e) {
+ BrokerConfiguratorBuilder
+ .wrapAndRethrowException(e, "Shutdown of the queue " + queueName, this.apiClient);
+ }
+ }
+
+ public void reenableEgressOnQueue(String msgVpnName, String queueName) {
+ try {
+ final ConfigMsgVpnQueuesResponse response = this.queueApi
+ .getMsgVpnQueues(msgVpnName, 10, null, null, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ final List queues = response.getData();
+ ConfigMsgVpnQueue q = queues.stream().filter(queue -> {
+ if (queueName.equals(queue.getQueueName())) {
+ return true;
+ }
+ return false;
+ }).findFirst().orElseThrow(() -> {
+ return new SempClientException(
+ String.format("Can't shutdown the queue %s", msgVpnName));
+ });
+ q.setEgressEnabled(true);
+ this.queueApi.updateMsgVpnQueue(msgVpnName, queueName, q, null, null);
+ } else {
+ throw new SempClientException(
+ String.format("Can't shutdown the queue %s", queueName));
+ }
+ } catch (ApiException e) {
+ BrokerConfiguratorBuilder
+ .wrapAndRethrowException(e, "Shutdown of the queue " + queueName, this.apiClient);
+ }
+ }
+
+ public void disableIngressOnQueue(String msgVpnName, String queueName) {
+ try {
+ final ConfigMsgVpnQueuesResponse response = this.queueApi
+ .getMsgVpnQueues(msgVpnName, 10, null, null, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ final List queues = response.getData();
+ ConfigMsgVpnQueue q = queues.stream().filter(queue -> {
+ if (queueName.equals(queue.getQueueName())) {
+ return true;
+ }
+ return false;
+ }).findFirst().orElseThrow(() -> {
+ return new SempClientException(
+ String.format("Can't shutdown the queue %s", msgVpnName));
+ });
+ q.setIngressEnabled(false);
+ this.queueApi.updateMsgVpnQueue(msgVpnName, queueName, q, null, null);
+ } else {
+ throw new SempClientException(
+ String.format("Can't shutdown the queue %s", queueName));
+ }
+ } catch (ApiException e) {
+ BrokerConfiguratorBuilder
+ .wrapAndRethrowException(e, "Shutdown of the queue " + queueName, this.apiClient);
+ }
+ }
+
+ public void reenableIngressOnQueue(String msgVpnName, String queueName) {
+ try {
+ final ConfigMsgVpnQueuesResponse response = this.queueApi
+ .getMsgVpnQueues(msgVpnName, 10, null, null, null, null);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ final List queues = response.getData();
+ ConfigMsgVpnQueue q = queues.stream().filter(queue -> {
+ if (queueName.equals(queue.getQueueName())) {
+ return true;
+ }
+ return false;
+ }).findFirst().orElseThrow(() -> {
+ return new SempClientException(
+ String.format("Can't shutdown the queue %s", msgVpnName));
+ });
+ q.setIngressEnabled(true);
+ this.queueApi.updateMsgVpnQueue(msgVpnName, queueName, q, null, null);
+ } else {
+ throw new SempClientException(
+ String.format("Can't shutdown the queue %s", queueName));
+ }
+ } catch (ApiException e) {
+ BrokerConfiguratorBuilder
+ .wrapAndRethrowException(e, "Shutdown of the queue " + queueName, this.apiClient);
+ }
+ }
+
+ public void deleteQueue(String msgVpnName, String queueName) {
+ try {
+ final ConfigSempMetaOnlyResponse response = this.queueApi
+ .deleteMsgVpnQueue(msgVpnName, queueName);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ logger.debug("Queue Deleted!");
+ } else {
+ throw new SempClientException(
+ String.format("Can't shutdown the queue %s", queueName));
+ }
+ } catch (ApiException e) {
+ if (e.getResponseBody().contains("NOT_FOUND")) {
+ return;
+ }
+ BrokerConfiguratorBuilder
+ .wrapAndRethrowException(e, "Delete queue failed: " + queueName, this.apiClient);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/it/util/semp/monitor/BrokerMonitorBuilder.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/it/util/semp/monitor/BrokerMonitorBuilder.java
new file mode 100644
index 00000000..71e8dfb7
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/it/util/semp/monitor/BrokerMonitorBuilder.java
@@ -0,0 +1,374 @@
+package com.solace.it.util.semp.monitor;
+
+import com.solace.it.util.semp.SempClientException;
+import com.solace.it.util.semp.SempClientException.AuthenticationException;
+import com.solace.it.util.semp.SempClientException.AuthorizationException;
+import com.solace.it.util.semp.SempClientException.MissingResourceException;
+import com.solace.test.integration.semp.v2.SempV2Api;
+import com.solace.test.integration.semp.v2.monitor.ApiClient;
+import com.solace.test.integration.semp.v2.monitor.ApiException;
+import com.solace.test.integration.semp.v2.monitor.api.ClientProfileApi;
+import com.solace.test.integration.semp.v2.monitor.api.MsgVpnApi;
+import com.solace.test.integration.semp.v2.monitor.api.QueueApi;
+import com.solace.test.integration.semp.v2.monitor.auth.HttpBasicAuth;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorMsgVpnClient;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorMsgVpnClientConnection;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorMsgVpnClientConnectionsResponse;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorMsgVpnClientProfile;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorMsgVpnClientProfilesResponse;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorMsgVpnClientResponse;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorMsgVpnClientSubscription;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorMsgVpnClientSubscriptionsResponse;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorMsgVpnClientsResponse;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorMsgVpnQueueSubscription;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorMsgVpnQueueSubscriptionsResponse;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+
+/**
+ * Builder for entity that can perform administrator level monitoring tasks on a messaging broker
+ */
+public class BrokerMonitorBuilder {
+
+ static final Logger logger = LoggerFactory.getLogger(BrokerMonitorBuilder.class);
+ private final ApiClient theClient;
+
+ public static BrokerMonitorBuilder create(SempV2Api sempV2Api) {
+ return new BrokerMonitorBuilder(sempV2Api);
+ }
+
+ private BrokerMonitorBuilder(SempV2Api sempV2Api) {
+ this.theClient = sempV2Api.monitor().getApiClient();
+ }
+
+ public BrokerMonitorBuilder withDebugLog() {
+ this.theClient.setDebugging(true);
+ return this;
+ }
+
+ public BrokerMonitorBuilder withBasicAuth(String userName, String password) {
+ this.theClient.setUsername(userName);
+ this.theClient.setPassword(password);
+ return this;
+ }
+
+ public BrokerMonitor build() {
+ return new BrokerMonitor(this.theClient);
+ }
+
+ /**
+ * Entity that can perform administrator level monitoring tasks on a messaging broker
+ */
+ public static class BrokerMonitor {
+
+ private final ApiClient theClient;
+
+ private BrokerMonitor(final ApiClient theClient) {
+ this.theClient = theClient;
+ }
+
+ public MessageVpnClients vpnClients() {
+ return new MessageVpnClients(this.theClient);
+ }
+
+ public QueueClients queueClients() {
+ return new QueueClients(this.theClient);
+ }
+
+ public MsgVpnClientProfiles clientProfiles() {
+ return new MsgVpnClientProfiles(this.theClient);
+ }
+ }
+
+ public static class QueueClients {
+
+ private final QueueApi queueApi;
+
+ public QueueClients(ApiClient theClient) {
+ this.queueApi = new QueueApi(theClient);
+ }
+
+ public List querySubscriptionsByOriginalClientUsername(String msgVpnName,
+ String queueName) throws SempClientException {
+ List subscriptions = new LinkedList<>();
+ final MonitorMsgVpnQueueSubscriptionsResponse response;
+ try {
+ response = queueApi.getMsgVpnQueueSubscriptions(msgVpnName, queueName, 10, null,
+ Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+ final List subscriptionsList = response.getData();
+ if (subscriptionsList != null) {
+ subscriptionsList.forEach(s -> {
+ if (s != null) {
+ subscriptions.add(s.getSubscriptionTopic());
+ }
+ });
+ }
+ } catch (ApiException e) {
+ return wrapAndRethrowException(e, "Query queue subscriptions");
+ }
+
+ return subscriptions;
+ }
+
+ private T wrapAndRethrowException(ApiException e, String operation)
+ throws SempClientException {
+ final String userName = ((HttpBasicAuth) queueApi.getApiClient()
+ .getAuthentication("basicAuth"))
+ .getUsername();
+ if (HttpStatus.NOT_FOUND.value() == e.getCode()) {
+ throw new MissingResourceException(
+ String.format("Can't find resource for %s ", userName), e);
+ } else if (HttpStatus.UNAUTHORIZED.value() == e.getCode()) {
+ throw new AuthenticationException(
+ String.format("Invalid credentials provided for user %s to perform %s", userName,
+ operation), e);
+ } else if (HttpStatus.FORBIDDEN.value() == e.getCode()) {
+ throw new AuthorizationException(
+ String.format("User %s not authorized to perform %s", userName, operation), e);
+ } else {
+ throw new SempClientException(String.format("%s failed", operation), e);
+ }
+ }
+ }
+
+ public static class MsgVpnClientProfiles {
+
+ private final ClientProfileApi clientProfileApi;
+
+ private MsgVpnClientProfiles(final ApiClient theClient) {
+ this.clientProfileApi = new ClientProfileApi(theClient);
+ }
+
+ public MonitorMsgVpnClientProfile queryClientProfile(String msgVpnName, String clientName)
+ throws SempClientException {
+ try {
+ MonitorMsgVpnClientProfilesResponse response = this.clientProfileApi
+ .getMsgVpnClientProfiles(msgVpnName, 10, null, Collections.EMPTY_LIST,
+ Collections.EMPTY_LIST);
+ if (HttpStatus.OK.value() == response.getMeta().getResponseCode()) {
+ final List clProfiles = response.getData();
+ final Optional cp = clProfiles.stream()
+ .filter(clientProfile -> {
+ if (clientProfile != null && clientName.equals(
+ clientProfile.getClientProfileName())) {
+ return true;
+ } else {
+ return false;
+ }
+ }).findFirst();
+
+ try {
+ Thread.sleep(6000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ if (cp.isPresent()) {
+ return cp.get();
+ } else {
+ throw new MissingResourceException("Can't find resource");
+ }
+ } else {
+ throw new MissingResourceException("Can't find resource");
+ }
+ } catch (ApiException e) {
+ return wrapAndRethrowException(e, "Query client connections");
+ }
+ }
+
+ private T wrapAndRethrowException(ApiException e, String operation)
+ throws SempClientException {
+ final String userName = ((HttpBasicAuth) clientProfileApi.getApiClient()
+ .getAuthentication("basicAuth"))
+ .getUsername();
+
+ if (HttpStatus.NOT_FOUND.value() == e.getCode()) {
+ throw new MissingResourceException(
+ String.format("Can't find resource for %s ", userName), e);
+ } else if (HttpStatus.UNAUTHORIZED.value() == e.getCode()) {
+ throw new AuthenticationException(
+ String.format("Invalid credentials provided for user %s to perform %s", userName,
+ operation), e);
+ } else if (HttpStatus.FORBIDDEN.value() == e.getCode()) {
+ throw new AuthorizationException(
+ String.format("User %s not authorized to perform %s", userName, operation), e);
+ } else {
+ throw new SempClientException(String.format("%s failed", operation), e);
+ }
+ }
+ }
+
+ public static class MessageVpnClients {
+
+ private final MsgVpnApi vpnApi;
+
+ private MessageVpnClients(ApiClient apiClient) {
+ this.vpnApi = new MsgVpnApi(apiClient);
+ }
+
+ public Collection queryVpnClientConnections(String msgVpnName,
+ String clientName) throws SempClientException {
+ try {
+ final MonitorMsgVpnClientConnectionsResponse response = this.vpnApi
+ .getMsgVpnClientConnections(msgVpnName,
+ clientName, 100, null, null,
+ null);
+ return response.getData();
+ } catch (ApiException e) {
+ return wrapAndRethrowException(e, "Query client connections");
+ }
+ }
+
+ public List querySubscriptionsByOriginalClientUsername(String msgVpnName,
+ String originalClientUsername) throws SempClientException {
+ final List subscriptions = new LinkedList<>();
+ try {
+ final MonitorMsgVpnClientsResponse response = this.vpnApi
+ .getMsgVpnClients(msgVpnName, 10, null, Collections.EMPTY_LIST,
+ Collections.EMPTY_LIST);
+
+ final List clients = response.getData();
+
+ final Optional theClient = clients.stream().filter(client -> {
+ if (client != null && originalClientUsername
+ .equals(client.getOriginalClientUsername())) {
+ return true;
+ } else {
+ return false;
+ }
+ }).findFirst();
+
+ theClient.ifPresent(cl -> {
+ try {
+ final MonitorMsgVpnClientSubscriptionsResponse r = this.vpnApi
+ .getMsgVpnClientSubscriptions(msgVpnName, cl.getClientName(), 10,
+ null, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+ final List allSubscriptions = r.getData();
+ allSubscriptions.forEach(s -> {
+ subscriptions.add(s.getSubscriptionTopic());
+ });
+ } catch (ApiException e) {
+ logger.warn("can't find any subscription", e);
+ }
+ });
+ } catch (Exception e) {
+ logger.warn("can't find any subscription", e);
+ //
+ }
+ return subscriptions;
+ }
+
+ public MonitorMsgVpnClient queryVpnClientByClientName(String msgVpnName, String clientName)
+ throws SempClientException {
+ try {
+ final MonitorMsgVpnClientResponse response = this.vpnApi
+ .getMsgVpnClient(msgVpnName, clientName, null);
+ return response.getData();
+ } catch (ApiException e) {
+ return wrapAndRethrowException(e, "Query vpn client");
+ }
+ }
+
+ public List queryVpnClientsByClientName(String msgVpnName,
+ String clientName) throws SempClientException {
+ final Collection all = queryVpnClients(msgVpnName);
+ List result = new LinkedList<>();
+ for (MonitorMsgVpnClient c : all) {
+ if (c != null && clientName.equals(c.getClientName())) {
+ result.add(c);
+ }
+ }
+
+ if (!result.isEmpty()) {
+ return result;
+ }
+ throw new MissingResourceException(
+ String.format("Can't find client with client name %s", clientName));
+ }
+
+ public List queryVpnClientsByUserName(String msgVpnName,
+ String clientUsername)
+ throws SempClientException {
+ final Collection all = queryVpnClients(msgVpnName);
+ List result = new LinkedList<>();
+ for (MonitorMsgVpnClient c : all) {
+ if (c != null && clientUsername.equals(c.getClientUsername())) {
+ result.add(c);
+ }
+ }
+
+ if (!result.isEmpty()) {
+ return result;
+ }
+ throw new MissingResourceException(
+ String.format("Can't find client with user name %s", clientUsername));
+ }
+
+ public List queryVpnClientsByClientUser(String msgVpnName,
+ String clientUser) throws SempClientException {
+ final Collection all = queryVpnClients(msgVpnName);
+ List result = new LinkedList<>();
+ for (MonitorMsgVpnClient c : all) {
+ if (c != null && clientUser.equals(c.getUser())) {
+ result.add(c);
+ }
+ }
+
+ if (!result.isEmpty()) {
+ return result;
+ }
+ throw new MissingResourceException(
+ String.format("Can't find client with user %s", clientUser));
+ }
+
+ public Collection queryVpnClients(String msgVpnName)
+ throws SempClientException {
+ try {
+ final MonitorMsgVpnClientsResponse response =
+ this.vpnApi.getMsgVpnClients(msgVpnName, 10000, null, null, null);
+ final Collection cl = response.getData();
+ whenNotFound(cl);
+ return cl;
+ } catch (ApiException e) {
+ return wrapAndRethrowException(e, "Query vpn client");
+ }
+ }
+
+ private T wrapAndRethrowException(ApiException e, String operation)
+ throws SempClientException {
+ // TBD if not found == no vpns or == bad request
+ final String userName = ((HttpBasicAuth) vpnApi.getApiClient()
+ .getAuthentication("basicAuth"))
+ .getUsername();
+
+ if (HttpStatus.NOT_FOUND.value() == e.getCode()) {
+ throw new MissingResourceException(
+ String.format("Can't find resource for %s ", userName), e);
+ } else if (HttpStatus.UNAUTHORIZED.value() == e.getCode()) {
+ throw new AuthenticationException(
+ String.format("Invalid credentials provided for user %s to perform %s", userName,
+ operation), e);
+ } else if (HttpStatus.FORBIDDEN.value() == e.getCode()) {
+ throw new AuthorizationException(
+ String.format("User %s not authorized to perform %s", userName, operation), e);
+ } else {
+ throw new SempClientException(String.format("%s failed", operation), e);
+ }
+ }
+
+ private void whenNotFound(Collection> collection) throws SempClientException {
+ if (collection == null || collection.isEmpty()) {
+ final String userName = ((HttpBasicAuth) vpnApi.getApiClient()
+ .getAuthentication("basicAuth"))
+ .getUsername();
+ throw new MissingResourceException("Can't find resource");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderBasicIT.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderBasicIT.java
index 7d80b1e2..e7673295 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderBasicIT.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderBasicIT.java
@@ -976,7 +976,7 @@ public void testConsumerReconnect(
// Minimize message pre-fetch since we're not testing JCSMP, and this influences the test counters
sempV2Api.config().updateMsgVpnQueue(vpnName, queue0, new ConfigMsgVpnQueue()
- .maxDeliveredUnackedMsgsPerFlow((long) consumerProperties.getExtension().getBatchMaxSize()), null);
+ .maxDeliveredUnackedMsgsPerFlow((long) consumerProperties.getExtension().getBatchMaxSize()), null, null);
retryAssert(() -> assertThat(sempV2Api.monitor()
.getMsgVpnQueue(vpnName, queue0, null)
.getData()
@@ -1023,11 +1023,11 @@ public void testConsumerReconnect(
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
logger.info(String.format("Disabling egress to queue %s", queue0));
- sempV2Api.config().updateMsgVpnQueue(vpnName, queue0, new ConfigMsgVpnQueue().egressEnabled(false), null);
+ sempV2Api.config().updateMsgVpnQueue(vpnName, queue0, new ConfigMsgVpnQueue().egressEnabled(false), null, null);
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
logger.info(String.format("Enabling egress to queue %s", queue0));
- sempV2Api.config().updateMsgVpnQueue(vpnName, queue0, new ConfigMsgVpnQueue().egressEnabled(true), null);
+ sempV2Api.config().updateMsgVpnQueue(vpnName, queue0, new ConfigMsgVpnQueue().egressEnabled(true), null, null);
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
logger.info("Stopping producer");
@@ -1103,7 +1103,7 @@ public void testConsumerRebind(
// Minimize message pre-fetch since we're not testing JCSMP, and this influences the test counters
sempV2Api.config().updateMsgVpnQueue(vpnName, queue0, new ConfigMsgVpnQueue()
- .maxDeliveredUnackedMsgsPerFlow((long) consumerProperties.getExtension().getBatchMaxSize()), null);
+ .maxDeliveredUnackedMsgsPerFlow((long) consumerProperties.getExtension().getBatchMaxSize()), null, null);
retryAssert(() -> assertThat(sempV2Api.monitor()
.getMsgVpnQueue(vpnName, queue0, null)
.getData()
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderHealthIT.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderHealthIT.java
index b705c380..9dabb65b 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderHealthIT.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderHealthIT.java
@@ -177,7 +177,7 @@ public void testConsumerFlowHealthUnhealthy(
switch (healthStatus) {
case "DOWN" -> sempV2Api.config().deleteMsgVpnQueue(vpnName, queueName);
case "RECONNECTING" -> sempV2Api.config()
- .updateMsgVpnQueue(vpnName, queueName, new ConfigMsgVpnQueue().egressEnabled(false), null);
+ .updateMsgVpnQueue(vpnName, queueName, new ConfigMsgVpnQueue().egressEnabled(false), null, null);
default -> throw new IllegalArgumentException("No test for health status: " + healthStatus);
}
@@ -189,7 +189,7 @@ public void testConsumerFlowHealthUnhealthy(
if (healthStatus.equals("RECONNECTING")) {
sempV2Api.config()
- .updateMsgVpnQueue(vpnName, queueName, new ConfigMsgVpnQueue().egressEnabled(true), null);
+ .updateMsgVpnQueue(vpnName, queueName, new ConfigMsgVpnQueue().egressEnabled(true), null, null);
retryAssert(2, TimeUnit.MINUTES,
() -> assertThat(bindingsHealthContributor)
.asInstanceOf(InstanceOfAssertFactories.type(BindingsHealthContributor.class))
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderMeterIT.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderMeterIT.java
index cf1dd5eb..fe8778b2 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderMeterIT.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderMeterIT.java
@@ -214,7 +214,7 @@ public void testProducerMeters(@Values(booleans = {false, true}) boolean batched
(String) jcsmpSession.getProperty(JCSMPProperties.VPN_NAME),
queue.getName(),
new ConfigMsgVpnQueueSubscription().subscriptionTopic(destination0),
- null);
+ null, null);
SolaceTestBinder binder = context.getBinder();
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderProvisioningLifecycleIT.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderProvisioningLifecycleIT.java
index 80ce8852..b43c681d 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderProvisioningLifecycleIT.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/SolaceBinderProvisioningLifecycleIT.java
@@ -1096,7 +1096,7 @@ public void testFailConsumerWithConcurrencyGreaterThanMax(JCSMPSession jcsmpSess
logger.info(String.format("Pre-provisioning queue %s with maxBindCount=%s", queue0, maxBindCount));
jcsmpSession.provision(queue, new EndpointProperties(), JCSMPSession.WAIT_FOR_CONFIRM);
sempV2Api.config()
- .updateMsgVpnQueue(vpnName, queue0, new ConfigMsgVpnQueue().maxBindCount(maxBindCount), null);
+ .updateMsgVpnQueue(vpnName, queue0, new ConfigMsgVpnQueue().maxBindCount(maxBindCount), null, null);
try {
Binding consumerBinding = binder.bindConsumer(
@@ -2369,7 +2369,7 @@ void operatorProvisionTopicEndpoint(SempV2Api sempV2Api, String vpnName, String
config.setEgressEnabled(true);
config.setMsgVpnName(vpnName);
// assume TE is created, when not created test will catch it
- sempV2Api.config().createMsgVpnTopicEndpoint(vpnName, config, null);
+ sempV2Api.config().createMsgVpnTopicEndpoint(vpnName, config, null, null);
}
}
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/config/SolaceBinderConfigIT.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/config/SolaceBinderConfigIT.java
index f02f9199..35c1f7cd 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/config/SolaceBinderConfigIT.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/config/SolaceBinderConfigIT.java
@@ -44,7 +44,7 @@ void setUp(JCSMPProperties jcsmpProperties, ApplicationContext applicationContex
jcsmpProperties.setProperty(JCSMPProperties.CLIENT_NAME, clientName);
binderConfiguration = new SolaceMessageChannelBinderConfiguration(jcsmpProperties,
- new SolaceExtendedBindingProperties(), null);
+ new SolaceExtendedBindingProperties(), null, null);
AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();
beanFactory.autowireBean(binderConfiguration);
binderConfiguration = (SolaceMessageChannelBinderConfiguration) beanFactory.initializeBean(binderConfiguration,
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/health/handlers/SolaceSessionEventHandlerTest.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/health/handlers/SolaceSessionEventHandlerTest.java
index a6dc9394..a829d0b0 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/health/handlers/SolaceSessionEventHandlerTest.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/health/handlers/SolaceSessionEventHandlerTest.java
@@ -1,6 +1,7 @@
package com.solace.spring.cloud.stream.binder.health.handlers;
import com.solace.spring.cloud.stream.binder.health.indicators.SessionHealthIndicator;
+import com.solacesystems.jcsmp.JCSMPProperties;
import com.solacesystems.jcsmp.SessionEvent;
import com.solacesystems.jcsmp.SessionEventArgs;
import org.junit.jupiter.api.Test;
@@ -15,7 +16,7 @@
public class SolaceSessionEventHandlerTest {
@Test
public void testConnected(@Mock SessionHealthIndicator healthIndicator) {
- SolaceSessionEventHandler sessionEventHandler = new SolaceSessionEventHandler(healthIndicator);
+ SolaceSessionEventHandler sessionEventHandler = new SolaceSessionEventHandler(new JCSMPProperties(), null, healthIndicator);
sessionEventHandler.setSessionHealthUp();
Mockito.verify(healthIndicator, Mockito.times(1)).up();
Mockito.verifyNoMoreInteractions(healthIndicator);
@@ -28,7 +29,7 @@ public void testHandleEvent(SessionEvent event,
@Mock SessionHealthIndicator healthIndicator) {
Mockito.when(eventArgs.getEvent()).thenReturn(event);
- SolaceSessionEventHandler sessionEventHandler = new SolaceSessionEventHandler(healthIndicator);
+ SolaceSessionEventHandler sessionEventHandler = new SolaceSessionEventHandler(new JCSMPProperties(), null, healthIndicator);
sessionEventHandler.handleEvent(eventArgs);
switch (event) {
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/MultiBinderIT.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/multibinder/MultiBinderIT.java
similarity index 93%
rename from solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/MultiBinderIT.java
rename to solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/multibinder/MultiBinderIT.java
index 29ad7329..4e04c141 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/MultiBinderIT.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/multibinder/MultiBinderIT.java
@@ -1,5 +1,9 @@
-package com.solace.spring.cloud.stream.binder.springBootTests;
+package com.solace.spring.cloud.stream.binder.springBootTests.multibinder;
+import static org.awaitility.Awaitility.await;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.solace.test.integration.testcontainer.PubSubPlusContainer;
import com.solacesystems.jcsmp.JCSMPException;
import com.solacesystems.jcsmp.JCSMPFactory;
@@ -19,10 +23,6 @@
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.web.servlet.MockMvc;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
/**
* Note: not using the PubSubPlusExtension as it appears to trigger after Spring Boot Application is started.
@@ -91,11 +91,14 @@ public void checkSolaceMetricsAreExposed(@Autowired MockMvc mvc) throws Exceptio
producer.send(JCSMPFactory.onlyInstance().createBytesXMLMessage(),
JCSMPFactory.onlyInstance().createQueue(QUEUE_NAME_PREFIX + QUEUE_NAME_1));
- mvc.perform(get("/actuator/metrics"))
- .andExpectAll(
+ await().until(() -> {
+ mvc.perform(get("/actuator/metrics"))
+ .andExpectAll(
jsonPath("names", Matchers.hasItem("solace.message.size.payload")),
- jsonPath("names", Matchers.hasItem("solace.message.size.total"))
- );
+ jsonPath("names", Matchers.hasItem("solace.message.size.total")));
+ return true;
+ }
+ );
}
}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/SpringCloudStreamApp.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/multibinder/SpringCloudStreamApp.java
similarity index 52%
rename from solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/SpringCloudStreamApp.java
rename to solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/multibinder/SpringCloudStreamApp.java
index 673e0773..f9dedf6d 100644
--- a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/SpringCloudStreamApp.java
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/multibinder/SpringCloudStreamApp.java
@@ -1,4 +1,4 @@
-package com.solace.spring.cloud.stream.binder.springBootTests;
+package com.solace.spring.cloud.stream.binder.springBootTests.multibinder;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -6,6 +6,9 @@
import org.springframework.messaging.Message;
import java.util.function.Consumer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@SpringBootApplication
public class SpringCloudStreamApp {
@@ -24,4 +27,12 @@ public Consumer> otherConsume() {
return (msg -> System.out.println(msg.getPayload()));
}
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http.authorizeHttpRequests(requests -> requests
+ .requestMatchers(new AntPathRequestMatcher("/actuator/*")).permitAll()
+ .anyRequest().authenticated());
+
+ return http.build();
+ }
}
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/oauth2/MessagingServiceFreeTierBrokerTestContainerWithTlsAndOAuthSetup.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/oauth2/MessagingServiceFreeTierBrokerTestContainerWithTlsAndOAuthSetup.java
new file mode 100644
index 00000000..82d91931
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/oauth2/MessagingServiceFreeTierBrokerTestContainerWithTlsAndOAuthSetup.java
@@ -0,0 +1,117 @@
+package com.solace.spring.cloud.stream.binder.springBootTests.oauth2;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import java.io.File;
+import java.time.Duration;
+import java.util.function.Consumer;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.ComposeContainer;
+import org.testcontainers.containers.ContainerState;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+public interface MessagingServiceFreeTierBrokerTestContainerWithTlsAndOAuthSetup {
+
+ String FULL_DOCKER_COMPOSE_FILE_PATH = "src/test/resources/oauth2/free-tier-broker-with-tls-and-oauth-docker-compose.yml";
+ String PUBSUB_BROKER_SERVICE_NAME = "solbroker";
+ String NGINX_RPROXY_SERVICE_NAME = "solaceoauth";
+ String KEYCLOAK_OAUTH_SERVICE_NAME = "keycloak";
+
+ Logger LOGGER = LoggerFactory.getLogger(
+ MessagingServiceFreeTierBrokerTestContainerWithTlsAndOAuthSetup.class);
+
+ ComposeContainer COMPOSE_CONTAINER = new ComposeContainer(
+ new File(FULL_DOCKER_COMPOSE_FILE_PATH)).withLocalCompose(true).withPull(true)
+ .withExposedService(PUBSUB_BROKER_SERVICE_NAME, 8080)
+ .withExposedService(PUBSUB_BROKER_SERVICE_NAME, 55443)
+ .withExposedService(PUBSUB_BROKER_SERVICE_NAME, 55555)
+
+ .withExposedService(NGINX_RPROXY_SERVICE_NAME, 10443)
+ .withExposedService(NGINX_RPROXY_SERVICE_NAME, 1080)
+
+ .withExposedService(KEYCLOAK_OAUTH_SERVICE_NAME, 8080)
+
+ .waitingFor(PUBSUB_BROKER_SERVICE_NAME,
+ Wait.forHttp("/").forPort(8080).withStartupTimeout(Duration.ofSeconds(120)))
+ .waitingFor(NGINX_RPROXY_SERVICE_NAME,
+ Wait.forHttp("/").forPort(10443).allowInsecure().usingTls()
+ .withStartupTimeout(Duration.ofSeconds(120))).waitingFor(KEYCLOAK_OAUTH_SERVICE_NAME,
+ Wait.forHttp("/").forPort(8080).allowInsecure()
+ .withStartupTimeout(Duration.ofSeconds(120)));
+
+ @BeforeAll
+ static void startContainer() {
+ System.setProperty("javax.net.ssl.trustStore",
+ new File("src/test/resources/oauth2/certs/client/client-truststore.p12").getAbsolutePath());
+ System.setProperty("javax.net.ssl.trustStorePassword", "changeMe123");
+ System.setProperty("javax.net.ssl.trustStoreType", "PKCS12");
+ //System.setProperty("javax.net.debug", "all");
+
+ COMPOSE_CONTAINER.start();
+ }
+
+ @BeforeAll
+ static void checkContainer() {
+ String solaceBroker = COMPOSE_CONTAINER.getServiceHost(PUBSUB_BROKER_SERVICE_NAME, 8080);
+ assertNotNull(solaceBroker, "solace broker host expected to be not null");
+
+ String nginxProxy = COMPOSE_CONTAINER.getServiceHost(NGINX_RPROXY_SERVICE_NAME, 10443);
+ assertNotNull(nginxProxy, "nginx proxy host expected to be not null");
+
+ String keycloak = COMPOSE_CONTAINER.getServiceHost(KEYCLOAK_OAUTH_SERVICE_NAME, 8080);
+ assertNotNull(keycloak, "keycloak host expected to be not null");
+ }
+
+ @AfterAll
+ static void afterAll() {
+ final SolaceBroker broker = SolaceBroker.getInstance();
+ broker.backupFinalBrokerLogs(); //Backup container logs before it's destroyed
+ COMPOSE_CONTAINER.stop(); //Destroy the container
+ }
+
+ class SolaceBroker {
+
+ private static final class LazyHolder {
+
+ static final SolaceBroker INSTANCE = new SolaceBroker();
+ }
+
+
+ public static SolaceBroker getInstance() {
+ return LazyHolder.INSTANCE;
+ }
+
+ private final ComposeContainer container;
+
+ private SolaceBroker(ComposeContainer container) {
+ this.container = container;
+ }
+
+ public SolaceBroker() {
+ this(COMPOSE_CONTAINER);
+ }
+
+ /**
+ * bucks up final log form a broker
+ */
+ void backupFinalBrokerLogs() {
+ final Consumer copyToBrokerJob = containerState -> {
+ if (containerState.isRunning()) {
+ try {
+ containerState.copyFileFromContainer("/usr/sw/jail/logs/debug.log",
+ "oauth_test_final_debug.log");
+ containerState.copyFileFromContainer("/usr/sw/jail/logs/event.log",
+ "oauth_test_final_event.log");
+ } catch (Exception e) {
+ LOGGER.error("Failed to backup final log from a broker", e);
+ }
+ }
+ };
+ // run actual job on a container
+ container.getContainerByServiceName(PUBSUB_BROKER_SERVICE_NAME + "_1")
+ .ifPresent(copyToBrokerJob);
+ }
+ }
+}
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/oauth2/MultiBinderOAuth2IT.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/oauth2/MultiBinderOAuth2IT.java
new file mode 100644
index 00000000..acf6b98f
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/oauth2/MultiBinderOAuth2IT.java
@@ -0,0 +1,212 @@
+package com.solace.spring.cloud.stream.binder.springBootTests.oauth2;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import com.solace.it.util.semp.config.BrokerConfiguratorBuilder;
+import com.solace.it.util.semp.config.BrokerConfiguratorBuilder.BrokerConfigurator;
+import com.solace.it.util.semp.monitor.BrokerMonitorBuilder;
+import com.solace.it.util.semp.monitor.BrokerMonitorBuilder.BrokerMonitor;
+import com.solace.test.integration.semp.v2.SempV2Api;
+import com.solace.test.integration.semp.v2.action.ApiException;
+import com.solace.test.integration.semp.v2.action.model.ActionMsgVpnClientDisconnect;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnAuthenticationOauthProfile;
+import com.solace.test.integration.semp.v2.config.model.ConfigMsgVpnAuthenticationOauthProfile.OauthRoleEnum;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorMsgVpnClient;
+import java.io.File;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.assertj.core.util.Files;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.RepeatedTest;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.springframework.test.web.servlet.MockMvc;
+
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+@AutoConfigureMockMvc
+@ActiveProfiles("multibinderOAuth2")
+@DirtiesContext //Ensures all listeners are stopped
+class MultiBinderOAuth2IT implements
+ MessagingServiceFreeTierBrokerTestContainerWithTlsAndOAuthSetup {
+
+ private static final Logger logger = LoggerFactory.getLogger(MultiBinderOAuth2IT.class);
+ private static BrokerConfigurator solaceConfigUtil;
+ private static BrokerMonitor solaceMonitorUtil;
+ private static SempV2Api sempV2Api;
+
+ final static String OAUTH_PROFILE_NAME = "SolaceOauthResourceServer";
+ final static String MSG_VPN_OAUTH_1 = "OAUTH_1";
+ final static String MSG_VPN_OAUTH_2 = "OAUTH_2";
+
+ final static String REALM_1 = "solace-oauth-resource-server1";
+ final static String REALM_2 = "solace-oauth-resource-server2";
+
+ @DynamicPropertySource
+ static void registerDynamicProperties(DynamicPropertyRegistry registry) {
+ String solaceHost = COMPOSE_CONTAINER.getServiceHost(PUBSUB_BROKER_SERVICE_NAME, 55443);
+ int solaceSecureSMFPort = COMPOSE_CONTAINER.getServicePort(PUBSUB_BROKER_SERVICE_NAME, 55443);
+ COMPOSE_CONTAINER.getServicePort(PUBSUB_BROKER_SERVICE_NAME, 55443);
+ registry.add("spring.cloud.stream.binders.solace1.environment.solace.java.host",
+ () -> String.format("tcps://%s:%s", solaceHost, solaceSecureSMFPort));
+ registry.add("spring.cloud.stream.binders.solace2.environment.solace.java.host",
+ () -> String.format("tcps://%s:%s", solaceHost, solaceSecureSMFPort));
+
+ String nginxHost = COMPOSE_CONTAINER.getServiceHost(NGINX_RPROXY_SERVICE_NAME, 10443);
+ int nginxSecurePort = COMPOSE_CONTAINER.getServicePort(NGINX_RPROXY_SERVICE_NAME, 10443);
+ registry.add("spring.security.oauth2.client.provider.solace1-auth-server.token-uri",
+ () -> String.format("https://%s:%s/auth/realms/%s/protocol/openid-connect/token", nginxHost,
+ nginxSecurePort, REALM_1));
+ registry.add("spring.security.oauth2.client.provider.solace2-auth-server.token-uri",
+ () -> String.format("https://%s:%s/auth/realms/%s/protocol/openid-connect/token", nginxHost,
+ nginxSecurePort, REALM_2));
+ }
+
+ @BeforeAll
+ static void setUp() {
+ try {
+ String solaceHost = COMPOSE_CONTAINER.getServiceHost(PUBSUB_BROKER_SERVICE_NAME, 8080);
+ int solaceSempPort = COMPOSE_CONTAINER.getServicePort(PUBSUB_BROKER_SERVICE_NAME, 8080);
+ String sempUrl = String.format("http://%s:%s", solaceHost, solaceSempPort);
+ sempV2Api = new SempV2Api(sempUrl, "admin", "admin");
+ solaceConfigUtil = BrokerConfiguratorBuilder.create(sempV2Api).build();
+ solaceMonitorUtil = BrokerMonitorBuilder.create(sempV2Api).build();
+
+ solaceConfigUtil.vpns().copyVpn("default", MSG_VPN_OAUTH_1);
+ solaceConfigUtil.vpns().updateClientProfile(MSG_VPN_OAUTH_1, "default");
+ solaceConfigUtil.vpns().enableClientUsername(MSG_VPN_OAUTH_1, "default");
+
+ solaceConfigUtil.vpns().copyVpn("default", MSG_VPN_OAUTH_2);
+ solaceConfigUtil.vpns().updateClientProfile(MSG_VPN_OAUTH_2, "default");
+ solaceConfigUtil.vpns().enableClientUsername(MSG_VPN_OAUTH_2, "default");
+
+ logger.debug("Prepare to upload CA cert to the broker");
+ final URL resource = MultiBinderOAuth2IT.class.getClassLoader()
+ .getResource("oauth2/certs/rootCA/rootCA.pem");
+ if (resource != null) {
+ final File caFile = new File(resource.toURI());
+ final String ca = Files.contentOf(caFile, StandardCharsets.US_ASCII);
+ solaceConfigUtil.certAuthorities().setupCertAuthority("myCA", ca);
+ logger.debug("CA cert is uploaded to the broker");
+ } else {
+ logger.error("CA cert file can't be uploaded");
+ fail("Root certificate file can't be found");
+ }
+
+ //Setup Solace PubSub+ for OAuth2
+ setupOAuth(MSG_VPN_OAUTH_1, REALM_1);
+ setupOAuth(MSG_VPN_OAUTH_2, REALM_2);
+ } catch (URISyntaxException e) {
+ fail(e);
+ }
+ }
+
+ private static void setupOAuth(String msgVpnName, String realmName) {
+ solaceConfigUtil.vpns().enableOAuthAuth(msgVpnName);
+ solaceConfigUtil.vpns().createOAuthProfile(msgVpnName, oAuthProfileResourceServer(realmName));
+ }
+
+ private static ConfigMsgVpnAuthenticationOauthProfile oAuthProfileResourceServer(
+ String realmName) {
+ final String AUTHORIZATION_GROUP_CLAIM_NAME = "";
+ final String ENDPOINT_JWKS =
+ "https://solaceoauth:10443/auth/realms/" + realmName + "/protocol/openid-connect/certs";
+ final String ENDPOINT_USERINFO =
+ "https://solaceoauth:10443/auth/realms/" + realmName + "/protocol/openid-connect/userinfo";
+ final String REALM2_ISSUER_IDENTIFIER = "https://solaceoauth:10443/auth/realms/" + realmName;
+
+ return new ConfigMsgVpnAuthenticationOauthProfile().enabled(true)
+ .oauthProfileName(OAUTH_PROFILE_NAME)
+ .authorizationGroupsClaimName(AUTHORIZATION_GROUP_CLAIM_NAME)
+ .issuer(REALM2_ISSUER_IDENTIFIER).endpointJwks(ENDPOINT_JWKS)
+ .endpointUserinfo(ENDPOINT_USERINFO).resourceServerParseAccessTokenEnabled(true)
+ .resourceServerRequiredAudience("").resourceServerRequiredIssuer("")
+ .resourceServerRequiredScope("").resourceServerValidateAudienceEnabled(false)
+ .resourceServerValidateIssuerEnabled(false).resourceServerValidateScopeEnabled(false)
+ .resourceServerValidateTypeEnabled(false).oauthRole(OauthRoleEnum.RESOURCE_SERVER);
+ }
+
+ @Test
+ void checkHealthOfMultipleSolaceBinders(@Autowired MockMvc mvc) throws Exception {
+ mvc.perform(get("/actuator/health"))
+ .andExpectAll(status().isOk(), jsonPath("components.binders.components.solace1").exists(),
+ jsonPath("components.binders.components.solace1.status").value("UP"),
+ jsonPath("components.binders.components.solace2").exists(),
+ jsonPath("components.binders.components.solace2.status").value("UP"));
+ }
+
+ @Test
+ void checkHealthOfMultipleSolaceBindersWhenForceReconnect(@Autowired MockMvc mvc) {
+ try {
+ CountDownLatch forcedReconnect = new CountDownLatch(1);
+ int numberOfReconnects = 5;
+ AtomicBoolean failed = new AtomicBoolean(false);
+ Thread t = new Thread(() -> {
+ try {
+ for (int i = 0; i < numberOfReconnects; i++) {
+ try {
+ String msgVPnName = i % 2 == 0 ? MSG_VPN_OAUTH_1 : MSG_VPN_OAUTH_2;
+ MonitorMsgVpnClient msgVpnClient = solaceMonitorUtil.vpnClients()
+ .queryVpnClients(msgVPnName)
+ .stream()
+ .filter(client -> client.getClientUsername().startsWith("default"))
+ .findFirst().orElse(null);
+
+ if (msgVpnClient == null) {
+ throw new RuntimeException("Client not found");
+ }
+
+ logger.info("Forcing Session Reconnect for client: {}", msgVpnClient.getClientName());
+ sempV2Api.action()
+ .doMsgVpnClientDisconnect(msgVPnName, msgVpnClient.getClientName(),
+ new ActionMsgVpnClientDisconnect());
+ } catch (ApiException e) {
+ throw new RuntimeException(e);
+ } finally {
+ Thread.sleep(10_000); //Introduced delay between force reconnects
+ }
+ }
+ } catch (Exception e) {
+ logger.error("Failed to force reconnect", e);
+ failed.set(true);
+ } finally {
+ forcedReconnect.countDown();
+ }
+ });
+ t.start();
+
+ logger.info("Wait for force session reconnect, to refresh token");
+ boolean success = forcedReconnect.await(3, TimeUnit.MINUTES);
+ if (!success) {
+ fail("Timed out waiting for token refresh");
+ }
+
+ assertFalse(failed.get(), "Failed to force reconnect");
+
+ //Thread.sleep(10000); //Wait for reconnect to complete
+
+ mvc.perform(get("/actuator/health"))
+ .andExpectAll(status().isOk(), jsonPath("components.binders.components.solace1").exists(),
+ jsonPath("components.binders.components.solace1.status").value("UP"),
+ jsonPath("components.binders.components.solace2").exists(),
+ jsonPath("components.binders.components.solace2.status").value("UP"));
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/oauth2/SpringCloudStreamOAuth2App.java b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/oauth2/SpringCloudStreamOAuth2App.java
new file mode 100644
index 00000000..41a21be2
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/java/com/solace/spring/cloud/stream/binder/springBootTests/oauth2/SpringCloudStreamOAuth2App.java
@@ -0,0 +1,39 @@
+package com.solace.spring.cloud.stream.binder.springBootTests.oauth2;
+
+import java.util.function.Consumer;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.messaging.Message;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+
+@SpringBootApplication
+@EnableWebSecurity
+public class SpringCloudStreamOAuth2App {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringCloudStreamOAuth2App.class, args);
+ }
+
+ @Bean
+ public Consumer> consume() {
+ return (msg -> System.out.println(msg.getPayload()));
+ }
+
+ @Bean
+ public Consumer> otherConsume() {
+ return (msg -> System.out.println(msg.getPayload()));
+ }
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http.authorizeHttpRequests(requests -> requests
+ .requestMatchers(new AntPathRequestMatcher("/actuator/*")).permitAll()
+ .anyRequest().authenticated());
+
+ return http.build();
+ }
+}
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/application-multibinderOAuth2.yml b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/application-multibinderOAuth2.yml
new file mode 100644
index 00000000..650f23fa
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/application-multibinderOAuth2.yml
@@ -0,0 +1,89 @@
+spring:
+ security:
+ oauth2:
+ client:
+ registration:
+ solace1-oauth2-client:
+ provider: solace1-auth-server
+ client-id: solclient_oauth
+ client-secret: j6gWnw13iqzJfFZzlqzaQabQgXza4oHl
+ authorization-grant-type: client_credentials
+ scope: openid
+ solace2-oauth2-client:
+ provider: solace2-auth-server
+ client-id: solclient_oauth
+ client-secret: j6gWnw13iqzJfFZzlqzaQabQgXza4oHl
+ authorization-grant-type: client_credentials
+ scope: openid
+ provider:
+ solace1-auth-server:
+ token-uri: https://localhost:10443/auth/realms/solace-oauth-resource-server1/protocol/openid-connect/token
+ solace2-auth-server:
+ token-uri: https://localhost:10443/auth/realms/solace-oauth-resource-server2/protocol/openid-connect/token
+
+ cloud:
+ function:
+ definition: "consume;otherConsume"
+ stream:
+ binders:
+ solace1:
+ type: solace
+ environment:
+ solace:
+ java:
+ host: placeholder
+ msgVpn: OAUTH_1
+ connectRetries: 10
+ reconnectRetries: 10
+ reconnectRetryWaitInMillis: 2000
+ oauth2ClientRegistrationId: solace1-oauth2-client
+ apiProperties:
+ SSL_VALIDATE_CERTIFICATE: false ## Because using self-signed certificate
+ AUTHENTICATION_SCHEME: AUTHENTICATION_SCHEME_OAUTH2
+
+ solace2:
+ type: solace
+ environment:
+ solace:
+ java:
+ host: placeholder
+ msgVpn: OAUTH_2
+ connectRetries: 10
+ reconnectRetries: 10
+ reconnectRetryWaitInMillis: 2000
+ oauth2ClientRegistrationId: solace2-oauth2-client
+ apiProperties:
+ SSL_VALIDATE_CERTIFICATE: false ## Because using self-signed certificate
+ AUTHENTICATION_SCHEME: AUTHENTICATION_SCHEME_OAUTH2
+
+ bindings:
+ consume-in-0:
+ destination: MultiBinder/Queue/1
+ group: myConsumerGroup
+ binder: solace1
+ otherConsume-in-0:
+ destination: MultiBinder/Queue/2
+ group: myConsumerGroup
+ binder: solace2
+ default-binder: solace1
+
+# solace:
+# default:
+# consumer:
+# add-destination-as-subscription-to-queue: false
+# provision-durable-queue: false
+# producer:
+# provision-durable-queue: false
+
+management:
+ endpoint:
+ health:
+ show-components: always
+ show-details: always
+ endpoints:
+ web: # Actuator web endpoint configuration. For more info: https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints
+ exposure:
+ include: 'health,metrics'
+ health:
+ binders:
+ enabled: true
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/README.txt b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/README.txt
new file mode 100644
index 00000000..d1e517c7
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/README.txt
@@ -0,0 +1,96 @@
+Note: where a password is required, the password is "changeMe123"
+
+Create a Certificate Authority (CA) and use it to sign certificates for a Solace PubSub+ broker, a Solace PubSub+ client, and a Keycloak server.
+
+The directory contains the following files:
+- solbroker_san.conf: Solace PubSub+ broker certificate signing request (CSR) configuration file, mainly for Subject Alternative Name (SAN) extension.
+- keycloak_san.conf: Keycloak server certificate signing request (CSR) configuration file, mainly for Subject Alternative Name (SAN) extension.
+
+
+Create sub-directories:
+=======================
+mkdir rootCA
+mkdir broker
+mkdir client
+mkdir keycloak
+
+Root CA Key/Certificate:
+========================
+1. Generate a 4096-bit RSA private key for the Root CA:
+
+openssl genrsa -out ./rootCA/rootCA.key 4096
+
+2. Create a self-signed Root CA certificate valid for 20 years:
+openssl req -x509 -new -nodes -key ./rootCA/rootCA.key -sha256 -days 7300 -out ./rootCA/rootCA.crt \
+ -subj "/C=CA/ST=Ontario/L=Kanata/O=Solace Systems/CN=Root CA"
+
+3. Create a pem file with the key and certificate:
+cat ./rootCA/rootCA.key ./rootCA/rootCA.crt > ./rootCA/rootCA.pem
+
+4. verify the certificate:
+openssl x509 -in ./rootCA/rootCA.crt -text -noout
+
+Solace Broker Key/Certificate:
+==============================
+1. Generate a 2048-bit RSA private key for the server:
+openssl genrsa -out ./broker/solbroker.key 2048
+
+2. Create a CSR (Certificate Signing Request) for the server:
+openssl req -new -key ./broker/solbroker.key -out ./broker/solbroker.csr -config ./solbroker_san.conf
+
+3. Sign the server CSR with your Root CA to generate the server certificate valid for 20 years:
+openssl x509 -req -in ./broker/solbroker.csr -CA ./rootCA/rootCA.crt -CAkey ./rootCA/rootCA.key -CAcreateserial \
+ -out ./broker/solbroker.crt -days 7300 -sha256 -extensions req_ext -extfile ./solbroker_san.conf
+
+4. Create a pem file with the key and certificate:
+cat ./broker/solbroker.key ./broker/solbroker.crt > ./broker/solbroker.pem
+
+5. verify the certificate:
+openssl x509 -in ./broker/solbroker.crt -text -noout
+
+Solace Client Key/Certificate:
+==============================
+1. Generate a 2048-bit RSA private key for the client:
+openssl genrsa -out ./client/client.key 2048
+
+2. Create a CSR (Certificate Signing Request) for the client:
+openssl req -new -key ./client/client.key -out ./client/client.csr \
+ -subj "/C=CA/ST=Ontario/L=Kanata/O=Solace Systems/CN=solclient"
+
+3. Sign the client CSR with your Root CA to generate the client certificate valid for 20 years:
+openssl x509 -req -in ./client/client.csr -CA ./rootCA/rootCA.crt -CAkey ./rootCA/rootCA.key -CAcreateserial \
+ -out ./client/client.crt -days 7300 -sha256
+
+4. Create a pem file with the key and certificate:
+cat ./client/client.key ./client/client.crt > ./client/client.pem
+
+5. verify the certificate:
+openssl x509 -in ./client/client.crt -text -noout
+
+6. Create and verify a client truststore containing the Root CA certificate:
+openssl x509 -outform der -in ./rootCA/rootCA.pem -out ./rootCA/rootCA.der
+keytool -import -trustcacerts -alias root_ca -file ./rootCA/rootCA.der -keystore ./client/client-truststore.p12 -storepass changeMe123 -noprompt
+keytool -v -list -keystore ./client/client-truststore.p12 -storepass changeMe123
+
+7. Create a client keystore containing the client key and certificate:
+openssl pkcs12 -export -in ./client/client.pem -inkey ./client/client.key -name client -out ./client/client.p12 -passout pass:changeMe123
+keytool -importkeystore -srckeystore ./client/client.p12 -srcstoretype PKCS12 -destkeystore ./client/client-keystore.jks -deststoretype JKS -srcstorepass changeMe123 -deststorepass changeMe123
+
+
+Keycloak Server Key/Certificate:
+================================
+1. Generate a 2048-bit RSA private key for the keycloak:
+openssl genrsa -out ./keycloak/keycloak.key 2048
+
+2. Create a CSR (Certificate Signing Request) for the keycloak:
+openssl req -new -key ./keycloak/keycloak.key -out ./keycloak/keycloak.csr -config ./keycloak_san.conf
+
+3. Sign the keycloak CSR with your Root CA to generate the keycloak certificate valid for 20 years:
+openssl x509 -req -in ./keycloak/keycloak.csr -CA ./rootCA/rootCA.crt -CAkey ./rootCA/rootCA.key -CAcreateserial \
+ -out ./keycloak/keycloak.crt -days 7300 -sha256 -extensions req_ext -extfile ./keycloak_san.conf
+
+4. Create a pem file with the key and certificate:
+cat ./keycloak/keycloak.key ./keycloak/keycloak.crt > ./keycloak/keycloak.pem
+
+5. verify the certificate:
+openssl x509 -in ./keycloak/keycloak.crt -text -noout
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/broker/solbroker.crt b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/broker/solbroker.crt
new file mode 100644
index 00000000..70b2af51
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/broker/solbroker.crt
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIEzDCCArSgAwIBAgIUbAVQlzPN4AFLWLI4lE+Rzw1cG+cwDQYJKoZIhvcNAQEL
+BQAwWzELMAkGA1UEBhMCQ0ExEDAOBgNVBAgMB09udGFyaW8xDzANBgNVBAcMBkth
+bmF0YTEXMBUGA1UECgwOU29sYWNlIFN5c3RlbXMxEDAOBgNVBAMMB1Jvb3QgQ0Ew
+HhcNMjQwNzEwMTUzMDQ3WhcNNDQwNzA1MTUzMDQ3WjB2MQswCQYDVQQGEwJDQTEQ
+MA4GA1UECAwHT250YXJpbzEPMA0GA1UEBwwGS2FuYXRhMRcwFQYDVQQKDA5Tb2xh
+Y2UgU3lzdGVtczEXMBUGA1UECwwOU29sYWNlIFN5c3RlbXMxEjAQBgNVBAMMCXNv
+bGJyb2tlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ1ElE74dz7g
+eSeGgnH6KjcyE1p6Dc3y/6mlr6ST7IWtFAfbWRdwALDEZWUYXyDkpvEQtxZDrgkF
+Ss8sbL1dskAj3xtdU1wh6ibqgH5hpzlrUhgCIssDVgMOppRxZXnjYEN85nW3r2r5
+bbgVST/bJ+YEHX66cb1TK0ISqcugzySd5bQSwW9G0vKMvQv9VWoVRWtVpPm/s5Y3
+2FAd65rTJWBJhPowzsqKMNfx9JYEScFGfq6YOPjgm1SVoAG9nJXaUjt7C9S/fS+i
+eIafRYTOhQUIjXw6IgL29CTmoKRajbxjsjpi0C/zPg/1amB4OEceQatNXwc0YZgM
+j6dxPTsqKisCAwEAAaNtMGswaQYDVR0RBGIwYIIJc29sYnJva2Vyggpzb2xicm9r
+ZXIxggtzb2xicm9rZXJfMYIJbG9jYWxob3N0ggpzb2xicm9rZXIyggtzb2xicm9r
+ZXJfMocEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAgEA
+TYl/0ObS+eP9sb/IKLOXXaWS6K+1HQaqwRW0HJ0QBgwjfL9WhWupp1eujd+QFIGq
+eESZZvhxcF7lQIoaAQe/gn7wAL4ZIPJMlUdLiTBNacKCNqH9z/Meu+UHMN2mxJmD
+JlNVbmuWcpbE82nGylv+iZc8DWofnoyoyBRhJLPVqeyv3YUuIchkUxRjl7XbpQ9U
+4nG4+9pACEM4pAZZHG8aXcca8PjwlcdJjfYt0n4700NlCqpREe/w/GJZFjk+Pf87
+O1t9Xycrux0k4Q8eLiO0qNNeJWEaKEdoUGkmO5/mu3ghq2/wRB9Vmmq+z/UaZW+j
+l62lIowEO1eRtoYthvNh4eZ0kWlfE5m0F2kor69QsiB0Qgfr7ucMamAzNLD+JEgU
++scfnmW48y0eGqipKt9A02Bo6d2X36oG/IbTAYPuPJiDwWej77mgBfHDQhyV/+YI
+ayB/9bur4CW3wov6+a+YO/Q2rhU3ONs4EsQNGL8lo4opG9yCop9PR/BrcFcHvu+b
+61I6u1ZLGk2e5KUxKk7XEAkG/L1Y8JEY5D1j2f9HUa0rQ2NO69Ry0qMvMeN0PKQt
+XDs0INdK0VGyuD4BxHlwHutKXBVpsZyoxUWsR21UzczxuLqncHGJ7+/MbJBLElwa
+/5J9RRDdKubX3mv5tVV8fpBn6bYYYThZsDX7qMU1vgQ=
+-----END CERTIFICATE-----
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/broker/solbroker.csr b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/broker/solbroker.csr
new file mode 100644
index 00000000..3969f7cc
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/broker/solbroker.csr
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIDNzCCAh8CAQAwdjELMAkGA1UEBhMCQ0ExEDAOBgNVBAgMB09udGFyaW8xDzAN
+BgNVBAcMBkthbmF0YTEXMBUGA1UECgwOU29sYWNlIFN5c3RlbXMxFzAVBgNVBAsM
+DlNvbGFjZSBTeXN0ZW1zMRIwEAYDVQQDDAlzb2xicm9rZXIwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCdRJRO+Hc+4HknhoJx+io3MhNaeg3N8v+ppa+k
+k+yFrRQH21kXcACwxGVlGF8g5KbxELcWQ64JBUrPLGy9XbJAI98bXVNcIeom6oB+
+Yac5a1IYAiLLA1YDDqaUcWV542BDfOZ1t69q+W24FUk/2yfmBB1+unG9UytCEqnL
+oM8kneW0EsFvRtLyjL0L/VVqFUVrVaT5v7OWN9hQHeua0yVgSYT6MM7KijDX8fSW
+BEnBRn6umDj44JtUlaABvZyV2lI7ewvUv30voniGn0WEzoUFCI18OiIC9vQk5qCk
+Wo28Y7I6YtAv8z4P9WpgeDhHHkGrTV8HNGGYDI+ncT07KiorAgMBAAGgfDB6Bgkq
+hkiG9w0BCQ4xbTBrMGkGA1UdEQRiMGCCCXNvbGJyb2tlcoIKc29sYnJva2VyMYIL
+c29sYnJva2VyXzGCCWxvY2FsaG9zdIIKc29sYnJva2VyMoILc29sYnJva2VyXzKH
+BH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEBACYEqj5E
+A/4Jl45GrqYmVAc2f+3upB4XY6f67A5RR8q4fogtpU6WDZbxleJhlJceT4DaS4zx
+lk5sglzRCPEloskaosZ/fWXJsI/OcAGilcdPkpgPnf24iZCFIfLfhf8Vq4CM8m9M
+2pbTyghLPcORkIQ2rATRp/2gHHBWblfXbPw0FCxONyLolMGDMMF6/3z3yjT4pYGd
+/wAIgku9dB6og+yZeJ2JozLbml69eXbiASAWVSnkGLNag7u+NXkWiXFRQNQ4jAsi
+qYZ0Nl70F5QQ78ko4Nj+96Jo1ET1TON7C2fbl4ueHuHV7pxiGCRxe511OFSFQva6
+ubRf1fqacCMvD7w=
+-----END CERTIFICATE REQUEST-----
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/broker/solbroker.key b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/broker/solbroker.key
new file mode 100644
index 00000000..c2632aba
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/broker/solbroker.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAnUSUTvh3PuB5J4aCcfoqNzITWnoNzfL/qaWvpJPsha0UB9tZ
+F3AAsMRlZRhfIOSm8RC3FkOuCQVKzyxsvV2yQCPfG11TXCHqJuqAfmGnOWtSGAIi
+ywNWAw6mlHFleeNgQ3zmdbevavltuBVJP9sn5gQdfrpxvVMrQhKpy6DPJJ3ltBLB
+b0bS8oy9C/1VahVFa1Wk+b+zljfYUB3rmtMlYEmE+jDOyoow1/H0lgRJwUZ+rpg4
++OCbVJWgAb2cldpSO3sL1L99L6J4hp9FhM6FBQiNfDoiAvb0JOagpFqNvGOyOmLQ
+L/M+D/VqYHg4Rx5Bq01fBzRhmAyPp3E9OyoqKwIDAQABAoIBAHsUYuVy+xAQaYEP
+eiNtX4CXBiJ3Bzq5BHFmpBGvWxo7HEQR3KXFGCU/bwMxkbGSgTyEkmUwTpHsvGFr
+KScCnzAnYsJtxYGDYVdXi3xdPJxpa3Qyp7wuPjBiVOgz3vEHjB0FMO/L89NKph29
+Ovhosc8IRXUawU0kO+SX6p7cmYDTf/EdvCh1M6N1XlsvcOMb82v4arb5XyQ5IKdl
+qpm3Q23B1IPiwv4ErzeLHRbZ0XbMdQrSR85oZVAyFdQUjKZs/Bq0rZbrd9yxclBi
+k+4PJS3HpDjDwAmIeRVRIlsZdSOLLUOywieDLY8+inCwLXs6A5kRVbbKYWxewehG
+47n35EECgYEAzojA0NyA7ihtJHvez/CcbAfSW+/uXEhnEPv++ynnUAmz5unDacnx
+yICpFqZCMG8Ob/lrXoFvEMrmyFETBWiF9BlGJY17mtTZkKb83Osz1AGvdeKQQ/zB
+5UOLXsQKN+1HAI4VdyI4UutwzIfwcNu6mYqVMFZfBfHZ8iBCR5oR/ZECgYEAwu8o
+Tnowfu9uzDMNx8uQRnzCXvkL+jWqqUdfJXiRhsBSgQlfkw62TxByj3wRKIqUjj9E
+DzhsXJXsqetzfmVQH12RgVkJ5GkDY060Z8c9GYtx1rPJqtkIu6M+m/yVxZ4Tost6
+K3jvXveJXqu0wlJJMvpzupMBYSpvMo0KBj7rPfsCgYAY484o3YoEKYcNsIfnk12m
+f0LQpZeaM3eISnYuGpyvvpuZpm5QX2/t8+NswViUsa2RvQM9fme+JFWvqmWab0BF
+bI5RlD1jKWeW0SkEDqxOTm2wzT8JknpjgMJZB1Mb7lJyNK1NkCgthgYv/+nwD+rq
++hKEosQM2VqknVKfgmfMoQKBgDWKGjfzt34lpPjQzOgjMO0rNvd+z5tZQhZcU/Wm
+t9Ga4Q4v1OA/GjN9APoHyW6pIUQwfDDx/lEvnGDPGlmM2gTDXkN4gQ8LCLMt2r7m
+KhHqCso9dxZFpfBjVb7iEQDF+f6shFGMVbJvqnsmDe+RSimGQGLuHWLilMf9lNNC
+VLohAoGAKnfkD7NQgeez9fGrWjFN/8jj7jI3yvVjihpLQBEMMbMLma/V8Btm2/7x
+lgWiGMBcfYDqCPbe48xGWiHOKkW3tot5wsQCLZzwTPLYqFgjMDOFfCXmtmlOTMYu
++UdceSxN1JU/wyfXSf7CB1kkwcpOQ1awrccJ75DoHQW6seFjTFk=
+-----END RSA PRIVATE KEY-----
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/broker/solbroker.pem b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/broker/solbroker.pem
new file mode 100644
index 00000000..21e12a1b
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/broker/solbroker.pem
@@ -0,0 +1,55 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAnUSUTvh3PuB5J4aCcfoqNzITWnoNzfL/qaWvpJPsha0UB9tZ
+F3AAsMRlZRhfIOSm8RC3FkOuCQVKzyxsvV2yQCPfG11TXCHqJuqAfmGnOWtSGAIi
+ywNWAw6mlHFleeNgQ3zmdbevavltuBVJP9sn5gQdfrpxvVMrQhKpy6DPJJ3ltBLB
+b0bS8oy9C/1VahVFa1Wk+b+zljfYUB3rmtMlYEmE+jDOyoow1/H0lgRJwUZ+rpg4
++OCbVJWgAb2cldpSO3sL1L99L6J4hp9FhM6FBQiNfDoiAvb0JOagpFqNvGOyOmLQ
+L/M+D/VqYHg4Rx5Bq01fBzRhmAyPp3E9OyoqKwIDAQABAoIBAHsUYuVy+xAQaYEP
+eiNtX4CXBiJ3Bzq5BHFmpBGvWxo7HEQR3KXFGCU/bwMxkbGSgTyEkmUwTpHsvGFr
+KScCnzAnYsJtxYGDYVdXi3xdPJxpa3Qyp7wuPjBiVOgz3vEHjB0FMO/L89NKph29
+Ovhosc8IRXUawU0kO+SX6p7cmYDTf/EdvCh1M6N1XlsvcOMb82v4arb5XyQ5IKdl
+qpm3Q23B1IPiwv4ErzeLHRbZ0XbMdQrSR85oZVAyFdQUjKZs/Bq0rZbrd9yxclBi
+k+4PJS3HpDjDwAmIeRVRIlsZdSOLLUOywieDLY8+inCwLXs6A5kRVbbKYWxewehG
+47n35EECgYEAzojA0NyA7ihtJHvez/CcbAfSW+/uXEhnEPv++ynnUAmz5unDacnx
+yICpFqZCMG8Ob/lrXoFvEMrmyFETBWiF9BlGJY17mtTZkKb83Osz1AGvdeKQQ/zB
+5UOLXsQKN+1HAI4VdyI4UutwzIfwcNu6mYqVMFZfBfHZ8iBCR5oR/ZECgYEAwu8o
+Tnowfu9uzDMNx8uQRnzCXvkL+jWqqUdfJXiRhsBSgQlfkw62TxByj3wRKIqUjj9E
+DzhsXJXsqetzfmVQH12RgVkJ5GkDY060Z8c9GYtx1rPJqtkIu6M+m/yVxZ4Tost6
+K3jvXveJXqu0wlJJMvpzupMBYSpvMo0KBj7rPfsCgYAY484o3YoEKYcNsIfnk12m
+f0LQpZeaM3eISnYuGpyvvpuZpm5QX2/t8+NswViUsa2RvQM9fme+JFWvqmWab0BF
+bI5RlD1jKWeW0SkEDqxOTm2wzT8JknpjgMJZB1Mb7lJyNK1NkCgthgYv/+nwD+rq
++hKEosQM2VqknVKfgmfMoQKBgDWKGjfzt34lpPjQzOgjMO0rNvd+z5tZQhZcU/Wm
+t9Ga4Q4v1OA/GjN9APoHyW6pIUQwfDDx/lEvnGDPGlmM2gTDXkN4gQ8LCLMt2r7m
+KhHqCso9dxZFpfBjVb7iEQDF+f6shFGMVbJvqnsmDe+RSimGQGLuHWLilMf9lNNC
+VLohAoGAKnfkD7NQgeez9fGrWjFN/8jj7jI3yvVjihpLQBEMMbMLma/V8Btm2/7x
+lgWiGMBcfYDqCPbe48xGWiHOKkW3tot5wsQCLZzwTPLYqFgjMDOFfCXmtmlOTMYu
++UdceSxN1JU/wyfXSf7CB1kkwcpOQ1awrccJ75DoHQW6seFjTFk=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIEzDCCArSgAwIBAgIUbAVQlzPN4AFLWLI4lE+Rzw1cG+cwDQYJKoZIhvcNAQEL
+BQAwWzELMAkGA1UEBhMCQ0ExEDAOBgNVBAgMB09udGFyaW8xDzANBgNVBAcMBkth
+bmF0YTEXMBUGA1UECgwOU29sYWNlIFN5c3RlbXMxEDAOBgNVBAMMB1Jvb3QgQ0Ew
+HhcNMjQwNzEwMTUzMDQ3WhcNNDQwNzA1MTUzMDQ3WjB2MQswCQYDVQQGEwJDQTEQ
+MA4GA1UECAwHT250YXJpbzEPMA0GA1UEBwwGS2FuYXRhMRcwFQYDVQQKDA5Tb2xh
+Y2UgU3lzdGVtczEXMBUGA1UECwwOU29sYWNlIFN5c3RlbXMxEjAQBgNVBAMMCXNv
+bGJyb2tlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ1ElE74dz7g
+eSeGgnH6KjcyE1p6Dc3y/6mlr6ST7IWtFAfbWRdwALDEZWUYXyDkpvEQtxZDrgkF
+Ss8sbL1dskAj3xtdU1wh6ibqgH5hpzlrUhgCIssDVgMOppRxZXnjYEN85nW3r2r5
+bbgVST/bJ+YEHX66cb1TK0ISqcugzySd5bQSwW9G0vKMvQv9VWoVRWtVpPm/s5Y3
+2FAd65rTJWBJhPowzsqKMNfx9JYEScFGfq6YOPjgm1SVoAG9nJXaUjt7C9S/fS+i
+eIafRYTOhQUIjXw6IgL29CTmoKRajbxjsjpi0C/zPg/1amB4OEceQatNXwc0YZgM
+j6dxPTsqKisCAwEAAaNtMGswaQYDVR0RBGIwYIIJc29sYnJva2Vyggpzb2xicm9r
+ZXIxggtzb2xicm9rZXJfMYIJbG9jYWxob3N0ggpzb2xicm9rZXIyggtzb2xicm9r
+ZXJfMocEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAgEA
+TYl/0ObS+eP9sb/IKLOXXaWS6K+1HQaqwRW0HJ0QBgwjfL9WhWupp1eujd+QFIGq
+eESZZvhxcF7lQIoaAQe/gn7wAL4ZIPJMlUdLiTBNacKCNqH9z/Meu+UHMN2mxJmD
+JlNVbmuWcpbE82nGylv+iZc8DWofnoyoyBRhJLPVqeyv3YUuIchkUxRjl7XbpQ9U
+4nG4+9pACEM4pAZZHG8aXcca8PjwlcdJjfYt0n4700NlCqpREe/w/GJZFjk+Pf87
+O1t9Xycrux0k4Q8eLiO0qNNeJWEaKEdoUGkmO5/mu3ghq2/wRB9Vmmq+z/UaZW+j
+l62lIowEO1eRtoYthvNh4eZ0kWlfE5m0F2kor69QsiB0Qgfr7ucMamAzNLD+JEgU
++scfnmW48y0eGqipKt9A02Bo6d2X36oG/IbTAYPuPJiDwWej77mgBfHDQhyV/+YI
+ayB/9bur4CW3wov6+a+YO/Q2rhU3ONs4EsQNGL8lo4opG9yCop9PR/BrcFcHvu+b
+61I6u1ZLGk2e5KUxKk7XEAkG/L1Y8JEY5D1j2f9HUa0rQ2NO69Ry0qMvMeN0PKQt
+XDs0INdK0VGyuD4BxHlwHutKXBVpsZyoxUWsR21UzczxuLqncHGJ7+/MbJBLElwa
+/5J9RRDdKubX3mv5tVV8fpBn6bYYYThZsDX7qMU1vgQ=
+-----END CERTIFICATE-----
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client-keystore.jks b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client-keystore.jks
new file mode 100644
index 00000000..68b57de0
Binary files /dev/null and b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client-keystore.jks differ
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client-truststore.p12 b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client-truststore.p12
new file mode 100644
index 00000000..e915ff1e
Binary files /dev/null and b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client-truststore.p12 differ
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client.crt b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client.crt
new file mode 100644
index 00000000..12c61d81
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client.crt
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEPzCCAicCFGwFUJczzeABS1iyOJRPkc8NXBvoMA0GCSqGSIb3DQEBCwUAMFsx
+CzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMQ8wDQYDVQQHDAZLYW5hdGEx
+FzAVBgNVBAoMDlNvbGFjZSBTeXN0ZW1zMRAwDgYDVQQDDAdSb290IENBMB4XDTI0
+MDcxMDE1MzUyNFoXDTQ0MDcwNTE1MzUyNFowXTELMAkGA1UEBhMCQ0ExEDAOBgNV
+BAgMB09udGFyaW8xDzANBgNVBAcMBkthbmF0YTEXMBUGA1UECgwOU29sYWNlIFN5
+c3RlbXMxEjAQBgNVBAMMCXNvbGNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAMPxo1xlJD9Yi+fkR9MK7lNHsQ6VLHTad6ktUG+at0rclQfYG2x/
+G1lp812UaceQexOgEPTc7g4+cL8k9zDfJZhjF0hN5jZPrf5r1vP2v9xwdRRebVeZ
+juHsjPOjeu+1IbSWOELLUNqqdgt4zxUbEnMjX693n7jivBPWMtqJiVmXLgB/bdA4
+qJQ5DxtTF0jZLpxlVOPKKk/G6HMuW6EevI4XyP2MA1ayUXpTulcj39gCfMN3Bxcp
+epIYSV5gzeqV03fuUoh7GrtnBATIQxFe3guJEcoBqT+DUCieH0hRmizA435UK+lp
+yzDZWxLjLSh7msnTaIfgKmuNmrJcCk6KulMCAwEAATANBgkqhkiG9w0BAQsFAAOC
+AgEAiMRofj8jnppDr+IOCdZTqSUemV/+5+IIYJkMpibPnM/WPQjn95GgxLhfNtAq
+gaIOlN/IPEMJCZzCVSX/Fd+5YI10NBX7wBoFVrf0izSp40OAzynqlGrFMPdWQwAJ
+9kyqSuZa7R8phZzRAH6IrdsQcrxr7qGc5yBCEXnLTbd5rCEXYh6Yvq5/CspYvuCJ
+9kieXujn1XDt/bApFxkKJpOzkXZQxRDBKK8oYxH6u/0uN03M0yXnswiUVBjXEGZE
+MpBud1qIOfDOnZeS2yxJRKzX9Q0tXg9CHT0CnQTTC9f31FfDYW/ro81pqnLuzlm7
+RIPb7M6ovgJgFUmvh8lSqz0wDugNfTZZX6QCa/x3kUlCtmxwbLsC4MYmzgy3wtYw
+Bt6B8t9KzWCdLRbk+akGOX3nDn8NUpZhqgXyVUY10IxSIShueZ+IIwvdbKEXdIsz
+QNyaU76zeD175gPVOb9upuV7R3gJXcbLRQ6gCNoGkZrnTXOjI4wpGFmWHXJOJTDS
+nngQ9n1QiGBqYsEun+PRIod90ucwjTYuG88HSqTS8zsYQSf1qvQCG79Piq2osdMW
+jxPzdK/bZDNBObSGl/gHkXy9YmZ2MFyQoEaVeRjPUYRcPquALMFYRB9HDBWlYDsb
+GTgiYKVIs3FK90XnX7TzXyzH0GyzOCkC/47/dplWkP3WL+8=
+-----END CERTIFICATE-----
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client.csr b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client.csr
new file mode 100644
index 00000000..cb3ef9ff
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client.csr
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICojCCAYoCAQAwXTELMAkGA1UEBhMCQ0ExEDAOBgNVBAgMB09udGFyaW8xDzAN
+BgNVBAcMBkthbmF0YTEXMBUGA1UECgwOU29sYWNlIFN5c3RlbXMxEjAQBgNVBAMM
+CXNvbGNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMPxo1xl
+JD9Yi+fkR9MK7lNHsQ6VLHTad6ktUG+at0rclQfYG2x/G1lp812UaceQexOgEPTc
+7g4+cL8k9zDfJZhjF0hN5jZPrf5r1vP2v9xwdRRebVeZjuHsjPOjeu+1IbSWOELL
+UNqqdgt4zxUbEnMjX693n7jivBPWMtqJiVmXLgB/bdA4qJQ5DxtTF0jZLpxlVOPK
+Kk/G6HMuW6EevI4XyP2MA1ayUXpTulcj39gCfMN3BxcpepIYSV5gzeqV03fuUoh7
+GrtnBATIQxFe3guJEcoBqT+DUCieH0hRmizA435UK+lpyzDZWxLjLSh7msnTaIfg
+KmuNmrJcCk6KulMCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQAWOCvnysa8pBPh
+H4liajvowUgOBGz8fS87kiy57s9n4IZXYNU/PeNmMrm8RJLseP5UiYRvVaI0Wk8h
+2zJmb0g73brW153TPxU9aNDTTcSkMQmS3NMYvt/okxcEWkBoQ6zbgNE0DvCPnCkz
+svPASwYrGY0k3gLCWK8+/znrCHuqOAeQKYpdpYiLwJwPc7/iFfWxzdUp2XWV2141
+yRhJPwqlMK5UI8RwpkdkjIljuuaX5zeMtNIHlKtvcxC6B4ZYLii5mpVoOvNS/Wp/
+ZEpoFY6mn70z9uD+gyRyv8Yxwk6IMZnHiJVtcn93+z3O3Ini5hqRTgG9eCiIwFQl
+FgEVqEdJ
+-----END CERTIFICATE REQUEST-----
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client.key b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client.key
new file mode 100644
index 00000000..6847c05b
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAw/GjXGUkP1iL5+RH0wruU0exDpUsdNp3qS1Qb5q3StyVB9gb
+bH8bWWnzXZRpx5B7E6AQ9NzuDj5wvyT3MN8lmGMXSE3mNk+t/mvW8/a/3HB1FF5t
+V5mO4eyM86N677UhtJY4QstQ2qp2C3jPFRsScyNfr3efuOK8E9Yy2omJWZcuAH9t
+0DiolDkPG1MXSNkunGVU48oqT8bocy5boR68jhfI/YwDVrJRelO6VyPf2AJ8w3cH
+Fyl6khhJXmDN6pXTd+5SiHsau2cEBMhDEV7eC4kRygGpP4NQKJ4fSFGaLMDjflQr
+6WnLMNlbEuMtKHuaydNoh+Aqa42aslwKToq6UwIDAQABAoIBAHJQFLggoYb7R5Pf
+0C9FX0jiuF8DlE4P7mOadiTGJEzeZ2uOHmGrve7qKvrbTOMKXWNTrNDN22wf7XL2
+Q+gVJz/B/6FFIRtqXN3jWCI4QDKAwS1C8ZN7mKohcRHqvBwAlkteoDAHoYIQlJGY
+x2dOxfK6Hmal6V7ZmFQSUNTCDIlg7Mguz8WaI0C1Wen+jJHUjiIkPz8xKF/gvddl
+18iqHVGfZFSI1rTvfGeVO1vRwlwHoVV9HeXYEw9pAe0yxDLKKvY2brf8J8+qSWl2
+l7Unr89Ti8Yrm0EWQs/xFZpvQRkl7NG7WLIMJBCc4w3wOEvjpyklw7CfJaWGu6lM
+sRlk4gECgYEA+LQhF0SaL6Vs36VVAMrTqj8EWyr4V7AB+xAr0aoGawKnSnnlOj4x
+BSer4sulyQvL81rQnbekgkpWl05ZCcDp52+ATak+yBq3BvnX5SNjovcKQTuWhqLk
+EMHK8SuaMKcp+jUTRdaohsmy+zUtNzkkVEzNMdR93i7+yS1ao6lMHtMCgYEAybFC
+vTMhZITXmpvEHQ0qi0Ucgp/J6VQkPAhQMzoUHSIZbL6eXhaFmeQdD/ta9nIoOc3K
+enDEenW3ABrz84fPNaRVnS5U7H5448ZxW1HskwbeDx88PjTbUkS+tT8ITF3PiwA3
+d+vOw8ngg8Msmve19Sh6ssSPLvIIfxwYpTxIxoECgYEAoSnxS6e8FuYnQGJeTC4j
+re46P24AEqrPDcfz7WE12YCVshB9uBl3ILUNkOGRJFBNsPyHtby8kWXk6RXvYv+t
+U7mQtkLXmUqekpmzCxy8w209KvqXV9YU3rsGbPRpbd/VtvtP6vDosrfgESPrkh6o
+aSx/yCvACQwBNZL7apUZ69sCgYBAuUCohIr3veWOeOQTSpFXhgMjK/HYjabfGO/b
+sIyZ2MJ98iHSIboX62skIM5M/c9I1XBfoGZ8wd/LCds1UGS/WxAaU67vAZr7xUfF
+PWIEwJRsF+L2N3IWUXc9pI+eKhCbE6O5ORPuIo+I2Q4sYMekd6wASDGGqCbv221R
+QSo9gQKBgCRhdvN6Jp0IFrpHtopSaSYUCek0rotWlmXMuX/n4k+3RLdX4/xNn5gV
+gR8nvVvfzLz9wmsbERsKmnMILBIWZPD//2O9PXubcMp4Shifl0mKh9zzPvriiF/H
+7E9UU53p9WScYAPW+BonpKnopoGCXxM2smTwE2bdxzPs3jDWM8lJ
+-----END RSA PRIVATE KEY-----
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client.p12 b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client.p12
new file mode 100644
index 00000000..52ca2b62
Binary files /dev/null and b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client.p12 differ
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client.pem b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client.pem
new file mode 100644
index 00000000..7d3d8915
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/client/client.pem
@@ -0,0 +1,52 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAw/GjXGUkP1iL5+RH0wruU0exDpUsdNp3qS1Qb5q3StyVB9gb
+bH8bWWnzXZRpx5B7E6AQ9NzuDj5wvyT3MN8lmGMXSE3mNk+t/mvW8/a/3HB1FF5t
+V5mO4eyM86N677UhtJY4QstQ2qp2C3jPFRsScyNfr3efuOK8E9Yy2omJWZcuAH9t
+0DiolDkPG1MXSNkunGVU48oqT8bocy5boR68jhfI/YwDVrJRelO6VyPf2AJ8w3cH
+Fyl6khhJXmDN6pXTd+5SiHsau2cEBMhDEV7eC4kRygGpP4NQKJ4fSFGaLMDjflQr
+6WnLMNlbEuMtKHuaydNoh+Aqa42aslwKToq6UwIDAQABAoIBAHJQFLggoYb7R5Pf
+0C9FX0jiuF8DlE4P7mOadiTGJEzeZ2uOHmGrve7qKvrbTOMKXWNTrNDN22wf7XL2
+Q+gVJz/B/6FFIRtqXN3jWCI4QDKAwS1C8ZN7mKohcRHqvBwAlkteoDAHoYIQlJGY
+x2dOxfK6Hmal6V7ZmFQSUNTCDIlg7Mguz8WaI0C1Wen+jJHUjiIkPz8xKF/gvddl
+18iqHVGfZFSI1rTvfGeVO1vRwlwHoVV9HeXYEw9pAe0yxDLKKvY2brf8J8+qSWl2
+l7Unr89Ti8Yrm0EWQs/xFZpvQRkl7NG7WLIMJBCc4w3wOEvjpyklw7CfJaWGu6lM
+sRlk4gECgYEA+LQhF0SaL6Vs36VVAMrTqj8EWyr4V7AB+xAr0aoGawKnSnnlOj4x
+BSer4sulyQvL81rQnbekgkpWl05ZCcDp52+ATak+yBq3BvnX5SNjovcKQTuWhqLk
+EMHK8SuaMKcp+jUTRdaohsmy+zUtNzkkVEzNMdR93i7+yS1ao6lMHtMCgYEAybFC
+vTMhZITXmpvEHQ0qi0Ucgp/J6VQkPAhQMzoUHSIZbL6eXhaFmeQdD/ta9nIoOc3K
+enDEenW3ABrz84fPNaRVnS5U7H5448ZxW1HskwbeDx88PjTbUkS+tT8ITF3PiwA3
+d+vOw8ngg8Msmve19Sh6ssSPLvIIfxwYpTxIxoECgYEAoSnxS6e8FuYnQGJeTC4j
+re46P24AEqrPDcfz7WE12YCVshB9uBl3ILUNkOGRJFBNsPyHtby8kWXk6RXvYv+t
+U7mQtkLXmUqekpmzCxy8w209KvqXV9YU3rsGbPRpbd/VtvtP6vDosrfgESPrkh6o
+aSx/yCvACQwBNZL7apUZ69sCgYBAuUCohIr3veWOeOQTSpFXhgMjK/HYjabfGO/b
+sIyZ2MJ98iHSIboX62skIM5M/c9I1XBfoGZ8wd/LCds1UGS/WxAaU67vAZr7xUfF
+PWIEwJRsF+L2N3IWUXc9pI+eKhCbE6O5ORPuIo+I2Q4sYMekd6wASDGGqCbv221R
+QSo9gQKBgCRhdvN6Jp0IFrpHtopSaSYUCek0rotWlmXMuX/n4k+3RLdX4/xNn5gV
+gR8nvVvfzLz9wmsbERsKmnMILBIWZPD//2O9PXubcMp4Shifl0mKh9zzPvriiF/H
+7E9UU53p9WScYAPW+BonpKnopoGCXxM2smTwE2bdxzPs3jDWM8lJ
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIEPzCCAicCFGwFUJczzeABS1iyOJRPkc8NXBvoMA0GCSqGSIb3DQEBCwUAMFsx
+CzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMQ8wDQYDVQQHDAZLYW5hdGEx
+FzAVBgNVBAoMDlNvbGFjZSBTeXN0ZW1zMRAwDgYDVQQDDAdSb290IENBMB4XDTI0
+MDcxMDE1MzUyNFoXDTQ0MDcwNTE1MzUyNFowXTELMAkGA1UEBhMCQ0ExEDAOBgNV
+BAgMB09udGFyaW8xDzANBgNVBAcMBkthbmF0YTEXMBUGA1UECgwOU29sYWNlIFN5
+c3RlbXMxEjAQBgNVBAMMCXNvbGNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAMPxo1xlJD9Yi+fkR9MK7lNHsQ6VLHTad6ktUG+at0rclQfYG2x/
+G1lp812UaceQexOgEPTc7g4+cL8k9zDfJZhjF0hN5jZPrf5r1vP2v9xwdRRebVeZ
+juHsjPOjeu+1IbSWOELLUNqqdgt4zxUbEnMjX693n7jivBPWMtqJiVmXLgB/bdA4
+qJQ5DxtTF0jZLpxlVOPKKk/G6HMuW6EevI4XyP2MA1ayUXpTulcj39gCfMN3Bxcp
+epIYSV5gzeqV03fuUoh7GrtnBATIQxFe3guJEcoBqT+DUCieH0hRmizA435UK+lp
+yzDZWxLjLSh7msnTaIfgKmuNmrJcCk6KulMCAwEAATANBgkqhkiG9w0BAQsFAAOC
+AgEAiMRofj8jnppDr+IOCdZTqSUemV/+5+IIYJkMpibPnM/WPQjn95GgxLhfNtAq
+gaIOlN/IPEMJCZzCVSX/Fd+5YI10NBX7wBoFVrf0izSp40OAzynqlGrFMPdWQwAJ
+9kyqSuZa7R8phZzRAH6IrdsQcrxr7qGc5yBCEXnLTbd5rCEXYh6Yvq5/CspYvuCJ
+9kieXujn1XDt/bApFxkKJpOzkXZQxRDBKK8oYxH6u/0uN03M0yXnswiUVBjXEGZE
+MpBud1qIOfDOnZeS2yxJRKzX9Q0tXg9CHT0CnQTTC9f31FfDYW/ro81pqnLuzlm7
+RIPb7M6ovgJgFUmvh8lSqz0wDugNfTZZX6QCa/x3kUlCtmxwbLsC4MYmzgy3wtYw
+Bt6B8t9KzWCdLRbk+akGOX3nDn8NUpZhqgXyVUY10IxSIShueZ+IIwvdbKEXdIsz
+QNyaU76zeD175gPVOb9upuV7R3gJXcbLRQ6gCNoGkZrnTXOjI4wpGFmWHXJOJTDS
+nngQ9n1QiGBqYsEun+PRIod90ucwjTYuG88HSqTS8zsYQSf1qvQCG79Piq2osdMW
+jxPzdK/bZDNBObSGl/gHkXy9YmZ2MFyQoEaVeRjPUYRcPquALMFYRB9HDBWlYDsb
+GTgiYKVIs3FK90XnX7TzXyzH0GyzOCkC/47/dplWkP3WL+8=
+-----END CERTIFICATE-----
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/keycloak/keycloak.crt b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/keycloak/keycloak.crt
new file mode 100644
index 00000000..062f94b0
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/keycloak/keycloak.crt
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFHzCCAwegAwIBAgIUbAVQlzPN4AFLWLI4lE+Rzw1cG+kwDQYJKoZIhvcNAQEL
+BQAwWzELMAkGA1UEBhMCQ0ExEDAOBgNVBAgMB09udGFyaW8xDzANBgNVBAcMBkth
+bmF0YTEXMBUGA1UECgwOU29sYWNlIFN5c3RlbXMxEDAOBgNVBAMMB1Jvb3QgQ0Ew
+HhcNMjQwNzEwMTU0MzMyWhcNNDQwNzA1MTU0MzMyWjB2MQswCQYDVQQGEwJDQTEQ
+MA4GA1UECAwHT250YXJpbzEPMA0GA1UEBwwGS2FuYXRhMRcwFQYDVQQKDA5Tb2xh
+Y2UgU3lzdGVtczEXMBUGA1UECwwOU29sYWNlIFN5c3RlbXMxEjAQBgNVBAMMCXNv
+bGJyb2tlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKuVDvCSYll7
+7eWyEmcDDunp+z/4KziZatEZYGNten2SRzfvhtP+uLJCbexjeJ8NAYtSILE1q1tD
+lVAIItU6I2gUxKbJ8iUgX+VB92/35k314dKwvR7/pMivEj7joRdzE/qVszOJDZYu
+K9n4JHzpzpQ4fc9JCDOHlDlGjQMNan5HHjlzWloUbTeDlaEK6lGLimenYkTiU/Jt
+K9HS9Ep00E8IGNoTTuP7eUOnr+DxHSvdFJCarxe8t/TALkPgRyL/y14JOs9zG3e9
+AAblGWXXpexeLnKAUApJK/WX+bscWx9gdsbnAL0RSEO4hPX7BdAx5n3nwhbwWUYz
+HCWT72HQs0sCAwEAAaOBvzCBvDCBuQYDVR0RBIGxMIGugglzb2xicm9rZXKCCnNv
+bGJyb2tlcjGCC3NvbGJyb2tlcl8xgglsb2NhbGhvc3SCC3NvbGFjZU9BdXRoggtz
+b2xhY2VvYXV0aIIMc29sYWNlb2F1dGgyghUqLnNvbGFjZV9pbnRlcm5hbF9uZXSC
+ECouc29sYWNlX21zZ19uZXSCFCouc29sYWNlX21zZ19uZXR3b3JrhwR/AAABhxAA
+AAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4ICAQBJ8jD5hSKXPri4NmOx
+S3Lh6bMbkq8JcIQesR7sgPFENy3UX+WuAY6Fjm9edXBdG+9OuqfBXoSHEA8jL+oU
+gos1Lz/6Sy3kKGIe8E44zgas0CYH3veXJJeLMBaE8RKdKEaiEUT+Ka7OXhhOWDGj
+n8QKo5um+ZUzsBzBF5B0zw/5kK02aDE9N/eqvAAm1fZqDC8txZD7i0dBa0rrQp8Q
+yYZGQmobis7ban//MOxPnL/lpoWVkwn3HrGlUzhhoG8xwokI0pXGMCga4K3iWvyc
+jTbICt8AyEyhwQqOpugzVQxRc7Qs3sTlkrN2bTy6mmS5WrYIYwCKfq9yeH9OR41Q
+KVsHpcDrSCtVlwYe8TF/d+HkvFJudKj5VgXPatI3tRAFe1BxXWA0fNHn/Hla9QiC
+sYyb3T9dyxW1XVTV9sLNcib8Q8Cew/JqchaemPNG+gek+/tc7lOQfdRNdoKcflPc
+ko+2OvPZAoI5tg9kOLYn71KG3sHMJgU/42yZhbwOtPEGJ+AXH+TtIE1Twoum8C2X
+qe5uMOuE7MziRD2Zsg6t0Wi7RgLMfz+nKNDWJ1YXHX5tBj+p6CxyKZ+6MUmnj1Cl
+toLbj/FgNtJaey9bi1zVK2oy6r3oHn7sdI5NdMPFuRTYacX/Foq5KAeDo/MzN9pa
+mnTgszi5F12OP3KbMvw6PyR8tQ==
+-----END CERTIFICATE-----
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/keycloak/keycloak.csr b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/keycloak/keycloak.csr
new file mode 100644
index 00000000..60032ebb
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/keycloak/keycloak.csr
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIDjDCCAnQCAQAwdjELMAkGA1UEBhMCQ0ExEDAOBgNVBAgMB09udGFyaW8xDzAN
+BgNVBAcMBkthbmF0YTEXMBUGA1UECgwOU29sYWNlIFN5c3RlbXMxFzAVBgNVBAsM
+DlNvbGFjZSBTeXN0ZW1zMRIwEAYDVQQDDAlzb2xicm9rZXIwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCrlQ7wkmJZe+3lshJnAw7p6fs/+Cs4mWrRGWBj
+bXp9kkc374bT/riyQm3sY3ifDQGLUiCxNatbQ5VQCCLVOiNoFMSmyfIlIF/lQfdv
+9+ZN9eHSsL0e/6TIrxI+46EXcxP6lbMziQ2WLivZ+CR86c6UOH3PSQgzh5Q5Ro0D
+DWp+Rx45c1paFG03g5WhCupRi4pnp2JE4lPybSvR0vRKdNBPCBjaE07j+3lDp6/g
+8R0r3RSQmq8XvLf0wC5D4Eci/8teCTrPcxt3vQAG5Rll16XsXi5ygFAKSSv1l/m7
+HFsfYHbG5wC9EUhDuIT1+wXQMeZ958IW8FlGMxwlk+9h0LNLAgMBAAGggdAwgc0G
+CSqGSIb3DQEJDjGBvzCBvDCBuQYDVR0RBIGxMIGugglzb2xicm9rZXKCCnNvbGJy
+b2tlcjGCC3NvbGJyb2tlcl8xgglsb2NhbGhvc3SCC3NvbGFjZU9BdXRoggtzb2xh
+Y2VvYXV0aIIMc29sYWNlb2F1dGgyghUqLnNvbGFjZV9pbnRlcm5hbF9uZXSCECou
+c29sYWNlX21zZ19uZXSCFCouc29sYWNlX21zZ19uZXR3b3JrhwR/AAABhxAAAAAA
+AAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQBzBBKrR+MCMi2lv4m5wFuK
+vj8/idCcCQeVLhMFCgFu770FMAw2NLvYi0SB56S3J1FUrEkT8XPMfts8S8msrCot
+UZGMJMCc5RHeI+A308+xz069Y0i4wo4ajK3CkBRXzoMFejlgzF0oMafOMx2Lp9Tl
+k+s6peHvKdXa0KQIvn28DwO2sfRP4fqMy4borJRLYLOJEKGMCjo1ClsJNA4pZvFv
+mSJCO4DlUfEn04+H18EOKNLs8ChQXsxKqKAk+1VKvtrvRByPhhfU1Jg0Smmipvcg
+Z0uQseLKKZ2MYw6tCuvC1C+LbBtDd43GG1ayCg2G8OtNfKIHoCS0oVWsvUnENgUK
+-----END CERTIFICATE REQUEST-----
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/keycloak/keycloak.key b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/keycloak/keycloak.key
new file mode 100644
index 00000000..6d9102dd
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/keycloak/keycloak.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEoQIBAAKCAQEAq5UO8JJiWXvt5bISZwMO6en7P/grOJlq0RlgY216fZJHN++G
+0/64skJt7GN4nw0Bi1IgsTWrW0OVUAgi1TojaBTEpsnyJSBf5UH3b/fmTfXh0rC9
+Hv+kyK8SPuOhF3MT+pWzM4kNli4r2fgkfOnOlDh9z0kIM4eUOUaNAw1qfkceOXNa
+WhRtN4OVoQrqUYuKZ6diROJT8m0r0dL0SnTQTwgY2hNO4/t5Q6ev4PEdK90UkJqv
+F7y39MAuQ+BHIv/LXgk6z3Mbd70ABuUZZdel7F4ucoBQCkkr9Zf5uxxbH2B2xucA
+vRFIQ7iE9fsF0DHmfefCFvBZRjMcJZPvYdCzSwIDAQABAoIBAAm/RwD9n96rfqE8
+03TMpK0/IInKxFHLzVihk2syjfHSPH99+O/UGZPu2CXEpNaMO5k5iifm/5wIo9PP
+EoOAcQB5pY5ADKR1SV1RuQfAUnH9VN3OMoAvT6Ii5+twrPcTD4B9vpdf4si0SMNy
+KEh8U8LxzpvW70NWIWJ7koko2vLfaYVuvDSMZLXll9B9iz6ROYp1qNh0aYrscRC/
+uC2/ziAJzbMEhsPN59AF8upfnyBxEOjxhFJ9LzbsxNoZyRCCMuDu35TUB57q1TZm
+rp8RdrqZ2qlknkZMbf5E06xyHBt4QgyzltivxbugShZB9iUe74Mjqyp4LTkAoy8J
+U/P9cTkCgYEA4Tg+0mOvMpVE0x5nrV3z5uydNnYOWdDk3W8Q3JVKs5b7QQDFWtEs
+stP6jZCNnQkrRnhwY2mlGnWUUJ0CEtZp5eWXHdHsrFL150FOBYBofVdFTZFKdHRk
+CXn7zSXLULmrqBRptI0K7DQzAJ9y2agh3iATka22xuHUscm7qrKuqh0CgYEAwwgz
+hkruZBO/+6VE2LBy1/ew5btZyaF/x85Q/AiBPfEO/ilT4cKV3eOOZtjpnsyF15U2
+p4ubluNbvUYlJ7IPTZEUWwiw94B5hksQXzUfi96zb9GDcaEfoYGsZ+fqEPVkqgzM
+Z5rE8Db9QDnsd9uGdzSPMziEnLGqqNEd/lGolocCf1HRHQFRNVQq5dXMNd3FQ9Wg
+H3ypZo06VeobbwSzN3AGaUA0B332f0Z3u42x9cAWlKIFHs7+kfwKutaOMzKksdPS
+lBNBL7lqaeqYzr8w5sSh74s+PM4RekX3CoJ8OGAbE0D8KWpt0on8bIrNYeuwKJ2J
+CZLiiIO3ho0PvB1GzC0CgYEAg//U/5tPZaSIV4Uv54jk8Y7Ox23aA0Gu/kiBP1Ny
+Rb4Va6gFAdN1I0yUYL+Gvtel7pcq+pLep20R9jS3iPpWqST8JfDn9Vua5G2Bky6d
+P0lnINMop4tpoSHm0hyAqyGrE/y9i5GQoRRWq1WI2kZV5/BGy2ABQRxuaPu/1RTn
+iZkCgYB92CW0tnV1PDr4fRbFMQ/KnAHCaQHBjnfyx1pYAn5Km+iC7OiwT9/JFTAx
+fic17YAogeOOWnroiH7uUtJRLbo1kyCS1RDIFW0LsreXeGkQ4147cbyQxHORu7s0
+SU9voGpGV26zX0Ik0VbefsonMDZrQhoxign568DMLIMGfp/Sqg==
+-----END RSA PRIVATE KEY-----
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/keycloak/keycloak.pem b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/keycloak/keycloak.pem
new file mode 100644
index 00000000..d6020f39
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/keycloak/keycloak.pem
@@ -0,0 +1,57 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEoQIBAAKCAQEAq5UO8JJiWXvt5bISZwMO6en7P/grOJlq0RlgY216fZJHN++G
+0/64skJt7GN4nw0Bi1IgsTWrW0OVUAgi1TojaBTEpsnyJSBf5UH3b/fmTfXh0rC9
+Hv+kyK8SPuOhF3MT+pWzM4kNli4r2fgkfOnOlDh9z0kIM4eUOUaNAw1qfkceOXNa
+WhRtN4OVoQrqUYuKZ6diROJT8m0r0dL0SnTQTwgY2hNO4/t5Q6ev4PEdK90UkJqv
+F7y39MAuQ+BHIv/LXgk6z3Mbd70ABuUZZdel7F4ucoBQCkkr9Zf5uxxbH2B2xucA
+vRFIQ7iE9fsF0DHmfefCFvBZRjMcJZPvYdCzSwIDAQABAoIBAAm/RwD9n96rfqE8
+03TMpK0/IInKxFHLzVihk2syjfHSPH99+O/UGZPu2CXEpNaMO5k5iifm/5wIo9PP
+EoOAcQB5pY5ADKR1SV1RuQfAUnH9VN3OMoAvT6Ii5+twrPcTD4B9vpdf4si0SMNy
+KEh8U8LxzpvW70NWIWJ7koko2vLfaYVuvDSMZLXll9B9iz6ROYp1qNh0aYrscRC/
+uC2/ziAJzbMEhsPN59AF8upfnyBxEOjxhFJ9LzbsxNoZyRCCMuDu35TUB57q1TZm
+rp8RdrqZ2qlknkZMbf5E06xyHBt4QgyzltivxbugShZB9iUe74Mjqyp4LTkAoy8J
+U/P9cTkCgYEA4Tg+0mOvMpVE0x5nrV3z5uydNnYOWdDk3W8Q3JVKs5b7QQDFWtEs
+stP6jZCNnQkrRnhwY2mlGnWUUJ0CEtZp5eWXHdHsrFL150FOBYBofVdFTZFKdHRk
+CXn7zSXLULmrqBRptI0K7DQzAJ9y2agh3iATka22xuHUscm7qrKuqh0CgYEAwwgz
+hkruZBO/+6VE2LBy1/ew5btZyaF/x85Q/AiBPfEO/ilT4cKV3eOOZtjpnsyF15U2
+p4ubluNbvUYlJ7IPTZEUWwiw94B5hksQXzUfi96zb9GDcaEfoYGsZ+fqEPVkqgzM
+Z5rE8Db9QDnsd9uGdzSPMziEnLGqqNEd/lGolocCf1HRHQFRNVQq5dXMNd3FQ9Wg
+H3ypZo06VeobbwSzN3AGaUA0B332f0Z3u42x9cAWlKIFHs7+kfwKutaOMzKksdPS
+lBNBL7lqaeqYzr8w5sSh74s+PM4RekX3CoJ8OGAbE0D8KWpt0on8bIrNYeuwKJ2J
+CZLiiIO3ho0PvB1GzC0CgYEAg//U/5tPZaSIV4Uv54jk8Y7Ox23aA0Gu/kiBP1Ny
+Rb4Va6gFAdN1I0yUYL+Gvtel7pcq+pLep20R9jS3iPpWqST8JfDn9Vua5G2Bky6d
+P0lnINMop4tpoSHm0hyAqyGrE/y9i5GQoRRWq1WI2kZV5/BGy2ABQRxuaPu/1RTn
+iZkCgYB92CW0tnV1PDr4fRbFMQ/KnAHCaQHBjnfyx1pYAn5Km+iC7OiwT9/JFTAx
+fic17YAogeOOWnroiH7uUtJRLbo1kyCS1RDIFW0LsreXeGkQ4147cbyQxHORu7s0
+SU9voGpGV26zX0Ik0VbefsonMDZrQhoxign568DMLIMGfp/Sqg==
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIFHzCCAwegAwIBAgIUbAVQlzPN4AFLWLI4lE+Rzw1cG+kwDQYJKoZIhvcNAQEL
+BQAwWzELMAkGA1UEBhMCQ0ExEDAOBgNVBAgMB09udGFyaW8xDzANBgNVBAcMBkth
+bmF0YTEXMBUGA1UECgwOU29sYWNlIFN5c3RlbXMxEDAOBgNVBAMMB1Jvb3QgQ0Ew
+HhcNMjQwNzEwMTU0MzMyWhcNNDQwNzA1MTU0MzMyWjB2MQswCQYDVQQGEwJDQTEQ
+MA4GA1UECAwHT250YXJpbzEPMA0GA1UEBwwGS2FuYXRhMRcwFQYDVQQKDA5Tb2xh
+Y2UgU3lzdGVtczEXMBUGA1UECwwOU29sYWNlIFN5c3RlbXMxEjAQBgNVBAMMCXNv
+bGJyb2tlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKuVDvCSYll7
+7eWyEmcDDunp+z/4KziZatEZYGNten2SRzfvhtP+uLJCbexjeJ8NAYtSILE1q1tD
+lVAIItU6I2gUxKbJ8iUgX+VB92/35k314dKwvR7/pMivEj7joRdzE/qVszOJDZYu
+K9n4JHzpzpQ4fc9JCDOHlDlGjQMNan5HHjlzWloUbTeDlaEK6lGLimenYkTiU/Jt
+K9HS9Ep00E8IGNoTTuP7eUOnr+DxHSvdFJCarxe8t/TALkPgRyL/y14JOs9zG3e9
+AAblGWXXpexeLnKAUApJK/WX+bscWx9gdsbnAL0RSEO4hPX7BdAx5n3nwhbwWUYz
+HCWT72HQs0sCAwEAAaOBvzCBvDCBuQYDVR0RBIGxMIGugglzb2xicm9rZXKCCnNv
+bGJyb2tlcjGCC3NvbGJyb2tlcl8xgglsb2NhbGhvc3SCC3NvbGFjZU9BdXRoggtz
+b2xhY2VvYXV0aIIMc29sYWNlb2F1dGgyghUqLnNvbGFjZV9pbnRlcm5hbF9uZXSC
+ECouc29sYWNlX21zZ19uZXSCFCouc29sYWNlX21zZ19uZXR3b3JrhwR/AAABhxAA
+AAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4ICAQBJ8jD5hSKXPri4NmOx
+S3Lh6bMbkq8JcIQesR7sgPFENy3UX+WuAY6Fjm9edXBdG+9OuqfBXoSHEA8jL+oU
+gos1Lz/6Sy3kKGIe8E44zgas0CYH3veXJJeLMBaE8RKdKEaiEUT+Ka7OXhhOWDGj
+n8QKo5um+ZUzsBzBF5B0zw/5kK02aDE9N/eqvAAm1fZqDC8txZD7i0dBa0rrQp8Q
+yYZGQmobis7ban//MOxPnL/lpoWVkwn3HrGlUzhhoG8xwokI0pXGMCga4K3iWvyc
+jTbICt8AyEyhwQqOpugzVQxRc7Qs3sTlkrN2bTy6mmS5WrYIYwCKfq9yeH9OR41Q
+KVsHpcDrSCtVlwYe8TF/d+HkvFJudKj5VgXPatI3tRAFe1BxXWA0fNHn/Hla9QiC
+sYyb3T9dyxW1XVTV9sLNcib8Q8Cew/JqchaemPNG+gek+/tc7lOQfdRNdoKcflPc
+ko+2OvPZAoI5tg9kOLYn71KG3sHMJgU/42yZhbwOtPEGJ+AXH+TtIE1Twoum8C2X
+qe5uMOuE7MziRD2Zsg6t0Wi7RgLMfz+nKNDWJ1YXHX5tBj+p6CxyKZ+6MUmnj1Cl
+toLbj/FgNtJaey9bi1zVK2oy6r3oHn7sdI5NdMPFuRTYacX/Foq5KAeDo/MzN9pa
+mnTgszi5F12OP3KbMvw6PyR8tQ==
+-----END CERTIFICATE-----
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/keycloak_san.conf b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/keycloak_san.conf
new file mode 100644
index 00000000..be980a73
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/keycloak_san.conf
@@ -0,0 +1,30 @@
+[ req ]
+req_extensions = req_ext
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+C = CA
+ST = Ontario
+L = Kanata
+O = Solace Systems
+OU = Solace Systems
+CN = solbroker
+
+[ req_ext ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = solbroker
+DNS.2 = solbroker1
+DNS.3 = solbroker_1
+DNS.4 = localhost
+DNS.5 = solaceOAuth
+DNS.6 = solaceoauth
+DNS.7 = solaceoauth2
+DNS.8 = *.solace_internal_net
+DNS.9 = *.solace_msg_net
+DNS.10 = *.solace_msg_network
+IP.1 = 127.0.0.1
+IP.2 = 0:0:0:0:0:0:0:1
+# ... (add more SAN entries)
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/rootCA/rootCA.crt b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/rootCA/rootCA.crt
new file mode 100644
index 00000000..43fd900d
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/rootCA/rootCA.crt
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFlzCCA3+gAwIBAgIUU1biQS/fFJ8bcYNdXYXeaJ7htJIwDQYJKoZIhvcNAQEL
+BQAwWzELMAkGA1UEBhMCQ0ExEDAOBgNVBAgMB09udGFyaW8xDzANBgNVBAcMBkth
+bmF0YTEXMBUGA1UECgwOU29sYWNlIFN5c3RlbXMxEDAOBgNVBAMMB1Jvb3QgQ0Ew
+HhcNMjQwNzEwMTQ0NzM2WhcNNDQwNzA1MTQ0NzM2WjBbMQswCQYDVQQGEwJDQTEQ
+MA4GA1UECAwHT250YXJpbzEPMA0GA1UEBwwGS2FuYXRhMRcwFQYDVQQKDA5Tb2xh
+Y2UgU3lzdGVtczEQMA4GA1UEAwwHUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAJXSAsGmhmUYSnPoNNDeV09mh5v2ZnMGJuP7+y2cNzaoLdPM
+LDV1As/gfnF6n7hGWXj+ZFxOmpMwBEBNnfQVHtzgYWdIW9p34/fdznJr3PIKeC+K
+39s3PacyIIriX8WxD6T+WAO7RjzrbNLq+2XB3sWU3BR2Y0mFCP/u0iEJ2QratWjr
+A+Ers85jEGYlAsk5hL9D2jErVzluuW3Th+Rl/ov4ev1NE6vu/tm+k7I4PdTSA0PV
+3T8FrzS/KQK7aBnU982hhB1lHWSWyJl3IOhPE5VzghBhDb0nytn0Y8kTh7+qmSC2
+8LdKwPt16pyvcamCBInxJnQFNQitlvTfTPLDGs0YTD47gBR5CTWLlr2u8grlxviY
+T0xALNtf8RqcbMpizAt6+9RJsFKFOip2xKbVuia5KH96TYqMFmPSFs72jJBr2bC1
+/JpyokdUh6gquZfmN18igdu8UB58sK03wYdVort+6nLBax8JNYBvj2+hy8r3HVsi
+5xdaZofj3vPyFBC7GHrn7X4cUiff0RSncJC8Fr0LRb3Pi5sXNmN4wFAlJIC/Wd67
+QWouvoU1zUe9n8IB0yXGJcJ9u7CMZDxgAj9EAh4xkR0q7gxT5XSBpm4ZhOX35GwO
+50/cZ3zmNsQ/6d/illGwnP7bc6G8cCWKErDquLm5dI2P9OEUdOPFuIwkmLZFAgMB
+AAGjUzBRMB0GA1UdDgQWBBQWSokRHFfinJUJY8moOtt3Mt2K/TAfBgNVHSMEGDAW
+gBQWSokRHFfinJUJY8moOtt3Mt2K/TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
+DQEBCwUAA4ICAQCSJvzYL7u/Nv0Eq9xBvotNN7Dl1KrgqbSzz0Q5jV68vIZiL9n0
+HdP9oe1mQZeYr1DrTnysnlMjU1s/uoMorNu2cd1LBvUKSrjhmeAGg4g2JYBUoeJf
+C5kpFWaB3i5IksY8D0PlIsX3jufv35c4aP4NlUzcESu6cr+lO4K/GNV4cS5ACiaF
+giPhJvWjW9n/yxQXTki/294RRsEqTxQzcHVC5ZSBRdiMti2BJoV/CgFxiZhKaFPy
+dHOX8iHiI55mvLx+UisFc+YsJSMjuL7EjSJJQQVWIa+H7g23jenqbar0shstzfhE
+NoPBaB65o9c6qKCruajlcjZBxKuGlgP4wmIZMkFWwktHK5JJorLv9A29EpZp1fFs
+VE6kSNQxebbIvgZCqNE2bOTMfGspKVxDENOXDNTNgsf/lArtQk1+PMwjW1zwFGGn
+tiH3LbJtLgeeX7HnB80lsohD5d+6/v/xxrB6aWRlpQGT+271trZdR/pixh2dhsPg
+92D0oyR6CQTwuE1CUIthIonN1m+PtMKnpodoVsRkMwMOshcMWzmWVNNNIy5BuEgK
+Lhh30MdiiwlpLV5gkcnygd4hkMV2VO9j5sFllo5ttnkQ6OZKmNoAqcYWIHCW2sh8
+rDYFzwes5qhK/K+qYLbBroHli14/p6AZRAlkcS8Vc5ew40naHn1Dd7EKRA==
+-----END CERTIFICATE-----
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/rootCA/rootCA.der b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/rootCA/rootCA.der
new file mode 100644
index 00000000..ca4a7fe1
Binary files /dev/null and b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/rootCA/rootCA.der differ
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/rootCA/rootCA.key b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/rootCA/rootCA.key
new file mode 100644
index 00000000..58cb5363
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/rootCA/rootCA.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAldICwaaGZRhKc+g00N5XT2aHm/ZmcwYm4/v7LZw3Nqgt08ws
+NXUCz+B+cXqfuEZZeP5kXE6akzAEQE2d9BUe3OBhZ0hb2nfj993Ocmvc8gp4L4rf
+2zc9pzIgiuJfxbEPpP5YA7tGPOts0ur7ZcHexZTcFHZjSYUI/+7SIQnZCtq1aOsD
+4SuzzmMQZiUCyTmEv0PaMStXOW65bdOH5GX+i/h6/U0Tq+7+2b6Tsjg91NIDQ9Xd
+PwWvNL8pArtoGdT3zaGEHWUdZJbImXcg6E8TlXOCEGENvSfK2fRjyROHv6qZILbw
+t0rA+3XqnK9xqYIEifEmdAU1CK2W9N9M8sMazRhMPjuAFHkJNYuWva7yCuXG+JhP
+TEAs21/xGpxsymLMC3r71EmwUoU6KnbEptW6Jrkof3pNiowWY9IWzvaMkGvZsLX8
+mnKiR1SHqCq5l+Y3XyKB27xQHnywrTfBh1Wiu37qcsFrHwk1gG+Pb6HLyvcdWyLn
+F1pmh+Pe8/IUELsYeuftfhxSJ9/RFKdwkLwWvQtFvc+Lmxc2Y3jAUCUkgL9Z3rtB
+ai6+hTXNR72fwgHTJcYlwn27sIxkPGACP0QCHjGRHSruDFPldIGmbhmE5ffkbA7n
+T9xnfOY2xD/p3+KWUbCc/ttzobxwJYoSsOq4ubl0jY/04RR048W4jCSYtkUCAwEA
+AQKCAgAI53Vgevwrz/jE0L0q2LwJrQdMPqWyGmB/Vj+EY29ooTAwEUdjWfPz1NzO
+88HAWvYAWeYvEkDflI/8HmDP2918tR003TkQT+XNmnIlnMGB5Rtlf/Rz++F/KVyD
+xJZ6kl5iqPckKaIwBrHuCycr0gziY0l9MdgOy4hQZao5anNq0LrNZIJThJxoHL0h
+xPtYaEG6eFbkazYA5NLCczr1WRZ6zSbKHSWZJ1ggKtJuWidamay2AGTo1PanxOC0
+F91FA8JCh2HpuVO44blEXa8n/2Mjk7zcKlh9sHq+32Z60d1Uh9gX+KdvzVKQ3141
+N7wineaVKC7n4FNZk6+QGCFjoDLcfFeoo7PKykELzSgbNuIgCJw+Hgt0fvnipSWF
+SXhNh9lCjXeEOKV3Joy5cuQLRDg6oKXw+004iwKlYjU2xjvtoVGRmAuiBXAmSRoC
+W/VtkFpThmi3yKg5we0WRrEpN6oJaugWimF24YSuj2jwDYeno3vRpWHytmc/+PeU
+QF5i6fF5qSSgWpc2eb1ipbG+tNIKEC9tJaBJWFoFHs92hiVU6RrIYVp2aTA2RK67
+OASRSeXeYgNgYqnBEk7JaVNT+35WlO/hJS0PO6wYIbyFtkDMrGqpeSv705qz/eVq
+1w8R1C5nDfFSSgThBHsYtm89Rr6oUAFlBlj+AX32csPkTA/vAQKCAQEAx7UQD7cE
+ye75k0qHus8nn7iF0kwGmB7xG1IJrMH4C/9E5AA5TwPhLmEsN9lUCr4QI8Whi+VG
+e0im8CxfDDIk5YMcVNxHaAviHGHGw/Ve56Qlgb9aI6zI8ODKg1UJwAUmLUv69ZRJ
+zbESG8uxX/1dd+pMYNuKDsuVJ2t9bHrWiOBrvaw2/R+5Mkk4BoVYy59q3u7XozBQ
+4Po99JYQcdWaBKI5prxQkgNZ5NusDLmVIxA6FRclj2WWxCG3/AJY6h/oryqUvj9R
+LFz7N0TcWjWpoDElGAGQ24QVkiEXl+No1KHfV8c2t8q3rDqjEwGHLfSIp+eLWUui
+oUDP7/ysMPnbBQKCAQEAwA0XvCfOBXntN7qKBFkd2i7eaaGRm0dRdGSKyqAK6yfY
+ONwT5yx9KzbZVV1phDtTdAatvRmaWQX5B92fNUyxPjl4E8CwkFPEosz9Vje7vIdF
+aQI/QTkHdFu8VfKkTuDhbrO7yoY8jcS0hAo8azuFR8NQ7huVKMX5OoqmTr3iKxhX
+ByOJjk0cyYnwicH7gCiGJatEv55U4hUj8zPSn0V/tYuTuEB/kznKtPpKVu9sIg3D
+aOgSYSY0VMoxTnV2TRyiREKCkkOSbCD4Jsffxym2HgmEGZIDHzfz8PGsoANAfGyC
+4xisxnNHsr17h2+QRRXAxCVKyoq5Y+Q5pRMnZebSQQKCAQAjKYMZcTz7nQL+Zwn8
+30p3udJ+E3q5wADtsYUYkNJuslRb3Jo6ilFUjDFv5+j/NzW6RqrJ6eV+AW27LeTS
+TeXnLy2G134PGMCIBMMtb391Q5aDAMELNPnwR3QAqbFcyMtPAGjAYoqYF8w7bqLd
+ZsvVOECYcS8eqcOqPCfKONqbIQB3VeIcsUA3VWLy6vmWaIw1klIPXotvAUB1VxKw
+KE7E8Bc8fz2kZ8ilHfRuDSLwGIRRgFDRra5c/B7b1UH4fwPGC7ZCxP0y1XA56/rs
+OzSRivWgA26Q5/GwV/lCefzUK2gamW3N8Hhkb7KUvxkhA0QoZAFKCKIqyDGUbKWY
+vfVNAoIBAAsmd3NQKFD/FDvBE9ROzEHnqLgfTlHioSMN11UOV7Pxe0dJ18n7NkU1
+CQdAxiiMPTsmTB4Hh4OVqjC/uEei7UN8mLEk5dtrUaZWGntP/xFiFTCUldGWmw0x
+akzfKpT9z3ja7JNEme1tN0HXSky7hvB0sZUxesaEQAUbGa7GrYPtBNiAQrNFXN+C
+p7mHzq9RKwCy4enyKmF58r1jC959bX2/3dK7w+xrVY6OXZSQkAmmHOtRVgfX7P/j
+QVuZzEWL3QvzhJszWyP2AhJWVnK8xDsYOFg3twCwAfTCQ1CC/9J5hlvjCdz3wnjp
+MWvamVi5e5inxaDezwaysHoaE5aCAUECggEBAJjTpus8tJ0LEDAwO2J9N20NSp9i
+IMw1rpgkLrYr7/6zAChxlyCb3jndHzP+XLkbZeDmUzXlz10lwswYUaN89rFz8DBf
+JzmzTI8AeLMlNlP/tptj4U53ex9OgjHjy84YclqfdajuQYynM5S1a5MBGbFk0Kt9
+iV/N6OMtFtLQAgxtcMy6Wy/t5IgTWXfJx/XGtT/wS5a8IMojNcH/R/QyZM34IuLL
+FE6pCrdfQnBofTtp6shR2iDjdGFRm5N+KO8psxcQBX3cqabNAgNEPp4zbdW5F55y
+9xG5Pn18UReo6aCwAlPANxqj3u9wjShtgEDH+YP4kJ/nUzDw9LjQWdBaF70=
+-----END RSA PRIVATE KEY-----
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/rootCA/rootCA.pem b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/rootCA/rootCA.pem
new file mode 100644
index 00000000..c43bc76d
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/rootCA/rootCA.pem
@@ -0,0 +1,83 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAldICwaaGZRhKc+g00N5XT2aHm/ZmcwYm4/v7LZw3Nqgt08ws
+NXUCz+B+cXqfuEZZeP5kXE6akzAEQE2d9BUe3OBhZ0hb2nfj993Ocmvc8gp4L4rf
+2zc9pzIgiuJfxbEPpP5YA7tGPOts0ur7ZcHexZTcFHZjSYUI/+7SIQnZCtq1aOsD
+4SuzzmMQZiUCyTmEv0PaMStXOW65bdOH5GX+i/h6/U0Tq+7+2b6Tsjg91NIDQ9Xd
+PwWvNL8pArtoGdT3zaGEHWUdZJbImXcg6E8TlXOCEGENvSfK2fRjyROHv6qZILbw
+t0rA+3XqnK9xqYIEifEmdAU1CK2W9N9M8sMazRhMPjuAFHkJNYuWva7yCuXG+JhP
+TEAs21/xGpxsymLMC3r71EmwUoU6KnbEptW6Jrkof3pNiowWY9IWzvaMkGvZsLX8
+mnKiR1SHqCq5l+Y3XyKB27xQHnywrTfBh1Wiu37qcsFrHwk1gG+Pb6HLyvcdWyLn
+F1pmh+Pe8/IUELsYeuftfhxSJ9/RFKdwkLwWvQtFvc+Lmxc2Y3jAUCUkgL9Z3rtB
+ai6+hTXNR72fwgHTJcYlwn27sIxkPGACP0QCHjGRHSruDFPldIGmbhmE5ffkbA7n
+T9xnfOY2xD/p3+KWUbCc/ttzobxwJYoSsOq4ubl0jY/04RR048W4jCSYtkUCAwEA
+AQKCAgAI53Vgevwrz/jE0L0q2LwJrQdMPqWyGmB/Vj+EY29ooTAwEUdjWfPz1NzO
+88HAWvYAWeYvEkDflI/8HmDP2918tR003TkQT+XNmnIlnMGB5Rtlf/Rz++F/KVyD
+xJZ6kl5iqPckKaIwBrHuCycr0gziY0l9MdgOy4hQZao5anNq0LrNZIJThJxoHL0h
+xPtYaEG6eFbkazYA5NLCczr1WRZ6zSbKHSWZJ1ggKtJuWidamay2AGTo1PanxOC0
+F91FA8JCh2HpuVO44blEXa8n/2Mjk7zcKlh9sHq+32Z60d1Uh9gX+KdvzVKQ3141
+N7wineaVKC7n4FNZk6+QGCFjoDLcfFeoo7PKykELzSgbNuIgCJw+Hgt0fvnipSWF
+SXhNh9lCjXeEOKV3Joy5cuQLRDg6oKXw+004iwKlYjU2xjvtoVGRmAuiBXAmSRoC
+W/VtkFpThmi3yKg5we0WRrEpN6oJaugWimF24YSuj2jwDYeno3vRpWHytmc/+PeU
+QF5i6fF5qSSgWpc2eb1ipbG+tNIKEC9tJaBJWFoFHs92hiVU6RrIYVp2aTA2RK67
+OASRSeXeYgNgYqnBEk7JaVNT+35WlO/hJS0PO6wYIbyFtkDMrGqpeSv705qz/eVq
+1w8R1C5nDfFSSgThBHsYtm89Rr6oUAFlBlj+AX32csPkTA/vAQKCAQEAx7UQD7cE
+ye75k0qHus8nn7iF0kwGmB7xG1IJrMH4C/9E5AA5TwPhLmEsN9lUCr4QI8Whi+VG
+e0im8CxfDDIk5YMcVNxHaAviHGHGw/Ve56Qlgb9aI6zI8ODKg1UJwAUmLUv69ZRJ
+zbESG8uxX/1dd+pMYNuKDsuVJ2t9bHrWiOBrvaw2/R+5Mkk4BoVYy59q3u7XozBQ
+4Po99JYQcdWaBKI5prxQkgNZ5NusDLmVIxA6FRclj2WWxCG3/AJY6h/oryqUvj9R
+LFz7N0TcWjWpoDElGAGQ24QVkiEXl+No1KHfV8c2t8q3rDqjEwGHLfSIp+eLWUui
+oUDP7/ysMPnbBQKCAQEAwA0XvCfOBXntN7qKBFkd2i7eaaGRm0dRdGSKyqAK6yfY
+ONwT5yx9KzbZVV1phDtTdAatvRmaWQX5B92fNUyxPjl4E8CwkFPEosz9Vje7vIdF
+aQI/QTkHdFu8VfKkTuDhbrO7yoY8jcS0hAo8azuFR8NQ7huVKMX5OoqmTr3iKxhX
+ByOJjk0cyYnwicH7gCiGJatEv55U4hUj8zPSn0V/tYuTuEB/kznKtPpKVu9sIg3D
+aOgSYSY0VMoxTnV2TRyiREKCkkOSbCD4Jsffxym2HgmEGZIDHzfz8PGsoANAfGyC
+4xisxnNHsr17h2+QRRXAxCVKyoq5Y+Q5pRMnZebSQQKCAQAjKYMZcTz7nQL+Zwn8
+30p3udJ+E3q5wADtsYUYkNJuslRb3Jo6ilFUjDFv5+j/NzW6RqrJ6eV+AW27LeTS
+TeXnLy2G134PGMCIBMMtb391Q5aDAMELNPnwR3QAqbFcyMtPAGjAYoqYF8w7bqLd
+ZsvVOECYcS8eqcOqPCfKONqbIQB3VeIcsUA3VWLy6vmWaIw1klIPXotvAUB1VxKw
+KE7E8Bc8fz2kZ8ilHfRuDSLwGIRRgFDRra5c/B7b1UH4fwPGC7ZCxP0y1XA56/rs
+OzSRivWgA26Q5/GwV/lCefzUK2gamW3N8Hhkb7KUvxkhA0QoZAFKCKIqyDGUbKWY
+vfVNAoIBAAsmd3NQKFD/FDvBE9ROzEHnqLgfTlHioSMN11UOV7Pxe0dJ18n7NkU1
+CQdAxiiMPTsmTB4Hh4OVqjC/uEei7UN8mLEk5dtrUaZWGntP/xFiFTCUldGWmw0x
+akzfKpT9z3ja7JNEme1tN0HXSky7hvB0sZUxesaEQAUbGa7GrYPtBNiAQrNFXN+C
+p7mHzq9RKwCy4enyKmF58r1jC959bX2/3dK7w+xrVY6OXZSQkAmmHOtRVgfX7P/j
+QVuZzEWL3QvzhJszWyP2AhJWVnK8xDsYOFg3twCwAfTCQ1CC/9J5hlvjCdz3wnjp
+MWvamVi5e5inxaDezwaysHoaE5aCAUECggEBAJjTpus8tJ0LEDAwO2J9N20NSp9i
+IMw1rpgkLrYr7/6zAChxlyCb3jndHzP+XLkbZeDmUzXlz10lwswYUaN89rFz8DBf
+JzmzTI8AeLMlNlP/tptj4U53ex9OgjHjy84YclqfdajuQYynM5S1a5MBGbFk0Kt9
+iV/N6OMtFtLQAgxtcMy6Wy/t5IgTWXfJx/XGtT/wS5a8IMojNcH/R/QyZM34IuLL
+FE6pCrdfQnBofTtp6shR2iDjdGFRm5N+KO8psxcQBX3cqabNAgNEPp4zbdW5F55y
+9xG5Pn18UReo6aCwAlPANxqj3u9wjShtgEDH+YP4kJ/nUzDw9LjQWdBaF70=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIFlzCCA3+gAwIBAgIUU1biQS/fFJ8bcYNdXYXeaJ7htJIwDQYJKoZIhvcNAQEL
+BQAwWzELMAkGA1UEBhMCQ0ExEDAOBgNVBAgMB09udGFyaW8xDzANBgNVBAcMBkth
+bmF0YTEXMBUGA1UECgwOU29sYWNlIFN5c3RlbXMxEDAOBgNVBAMMB1Jvb3QgQ0Ew
+HhcNMjQwNzEwMTQ0NzM2WhcNNDQwNzA1MTQ0NzM2WjBbMQswCQYDVQQGEwJDQTEQ
+MA4GA1UECAwHT250YXJpbzEPMA0GA1UEBwwGS2FuYXRhMRcwFQYDVQQKDA5Tb2xh
+Y2UgU3lzdGVtczEQMA4GA1UEAwwHUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAJXSAsGmhmUYSnPoNNDeV09mh5v2ZnMGJuP7+y2cNzaoLdPM
+LDV1As/gfnF6n7hGWXj+ZFxOmpMwBEBNnfQVHtzgYWdIW9p34/fdznJr3PIKeC+K
+39s3PacyIIriX8WxD6T+WAO7RjzrbNLq+2XB3sWU3BR2Y0mFCP/u0iEJ2QratWjr
+A+Ers85jEGYlAsk5hL9D2jErVzluuW3Th+Rl/ov4ev1NE6vu/tm+k7I4PdTSA0PV
+3T8FrzS/KQK7aBnU982hhB1lHWSWyJl3IOhPE5VzghBhDb0nytn0Y8kTh7+qmSC2
+8LdKwPt16pyvcamCBInxJnQFNQitlvTfTPLDGs0YTD47gBR5CTWLlr2u8grlxviY
+T0xALNtf8RqcbMpizAt6+9RJsFKFOip2xKbVuia5KH96TYqMFmPSFs72jJBr2bC1
+/JpyokdUh6gquZfmN18igdu8UB58sK03wYdVort+6nLBax8JNYBvj2+hy8r3HVsi
+5xdaZofj3vPyFBC7GHrn7X4cUiff0RSncJC8Fr0LRb3Pi5sXNmN4wFAlJIC/Wd67
+QWouvoU1zUe9n8IB0yXGJcJ9u7CMZDxgAj9EAh4xkR0q7gxT5XSBpm4ZhOX35GwO
+50/cZ3zmNsQ/6d/illGwnP7bc6G8cCWKErDquLm5dI2P9OEUdOPFuIwkmLZFAgMB
+AAGjUzBRMB0GA1UdDgQWBBQWSokRHFfinJUJY8moOtt3Mt2K/TAfBgNVHSMEGDAW
+gBQWSokRHFfinJUJY8moOtt3Mt2K/TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
+DQEBCwUAA4ICAQCSJvzYL7u/Nv0Eq9xBvotNN7Dl1KrgqbSzz0Q5jV68vIZiL9n0
+HdP9oe1mQZeYr1DrTnysnlMjU1s/uoMorNu2cd1LBvUKSrjhmeAGg4g2JYBUoeJf
+C5kpFWaB3i5IksY8D0PlIsX3jufv35c4aP4NlUzcESu6cr+lO4K/GNV4cS5ACiaF
+giPhJvWjW9n/yxQXTki/294RRsEqTxQzcHVC5ZSBRdiMti2BJoV/CgFxiZhKaFPy
+dHOX8iHiI55mvLx+UisFc+YsJSMjuL7EjSJJQQVWIa+H7g23jenqbar0shstzfhE
+NoPBaB65o9c6qKCruajlcjZBxKuGlgP4wmIZMkFWwktHK5JJorLv9A29EpZp1fFs
+VE6kSNQxebbIvgZCqNE2bOTMfGspKVxDENOXDNTNgsf/lArtQk1+PMwjW1zwFGGn
+tiH3LbJtLgeeX7HnB80lsohD5d+6/v/xxrB6aWRlpQGT+271trZdR/pixh2dhsPg
+92D0oyR6CQTwuE1CUIthIonN1m+PtMKnpodoVsRkMwMOshcMWzmWVNNNIy5BuEgK
+Lhh30MdiiwlpLV5gkcnygd4hkMV2VO9j5sFllo5ttnkQ6OZKmNoAqcYWIHCW2sh8
+rDYFzwes5qhK/K+qYLbBroHli14/p6AZRAlkcS8Vc5ew40naHn1Dd7EKRA==
+-----END CERTIFICATE-----
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/rootCA/rootCA.srl b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/rootCA/rootCA.srl
new file mode 100644
index 00000000..3ddaad86
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/rootCA/rootCA.srl
@@ -0,0 +1 @@
+6C05509733CDE0014B58B238944F91CF0D5C1BE9
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/solbroker_san.conf b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/solbroker_san.conf
new file mode 100644
index 00000000..37bf92fb
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/certs/solbroker_san.conf
@@ -0,0 +1,26 @@
+[ req ]
+req_extensions = req_ext
+distinguished_name = req_distinguished_name
+prompt = no
+
+[ req_distinguished_name ]
+C = CA
+ST = Ontario
+L = Kanata
+O = Solace Systems
+OU = Solace Systems
+CN = solbroker
+
+[ req_ext ]
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = solbroker
+DNS.2 = solbroker1
+DNS.3 = solbroker_1
+DNS.4 = localhost
+DNS.5 = solbroker2
+DNS.6 = solbroker_2
+IP.1 = 127.0.0.1
+IP.2 = 0:0:0:0:0:0:0:1
+# ... (add more SAN entries)
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/free-tier-broker-with-tls-and-oauth-docker-compose.yml b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/free-tier-broker-with-tls-and-oauth-docker-compose.yml
new file mode 100644
index 00000000..49a564a7
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/free-tier-broker-with-tls-and-oauth-docker-compose.yml
@@ -0,0 +1,52 @@
+version: '3.5'
+
+networks:
+ solace_msg_net:
+ external: false
+
+services:
+ solbroker:
+ image: solace/solace-pubsub-standard:10.4.0.23
+ hostname: solbroker
+ networks:
+ - solace_msg_net
+ env_file:
+ - ./solace_tls.env
+ shm_size: 2g
+ ulimits:
+ memlock: -1
+ nofile:
+ soft: 2448
+ hard: 42192
+ secrets:
+ - server.pem
+
+ solaceoauth: # A nginx reverse proxy for enabling SSL access to Keycloak
+ image: nginx:1.21.6
+ hostname: solaceoauth
+ volumes:
+ - ./oauth/nginx.conf:/etc/nginx/nginx.conf
+ - ./oauth/www:/data/www
+ - ./certs/keycloak:/etc/sslcerts/
+ networks:
+ - solace_msg_net
+
+ keycloak:
+ image: quay.io/keycloak/keycloak:16.1.1
+ hostname: keycloak
+ networks:
+ - solace_msg_net
+ volumes:
+ - ./oauth/keycloak/:/tmp/keycloak/
+ environment:
+ - DB_VENDOR=h2
+ - KEYCLOAK_USER=admin
+ - KEYCLOAK_PASSWORD=mysecret1!
+ - PROXY_ADDRESS_FORWARDING=true #important for reverse proxy
+ - "KEYCLOAK_IMPORT=/tmp/keycloak/solace-oauth-resource-server1-realm-export.json,/tmp/keycloak/solace-oauth-resource-server2-realm-export.json"
+ command:
+ - "-Dkeycloak.migration.strategy=IGNORE_EXISTING"
+
+secrets:
+ server.pem:
+ file: "certs/broker/solbroker.pem" ## The server certificate for the Solace PubSub+ broker
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/oauth/README b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/oauth/README
new file mode 100644
index 00000000..ae851755
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/oauth/README
@@ -0,0 +1,60 @@
+folder structure
+----------------
+ - ./keycloak: contains the realm export json files.
+ - ./certs/keycloak: contains the OAuth server private key and signed certificate
+ - ./www: contains the index.html file to be used for the nginx server health check
+ - ./nginx.conf: contains nginx reverse proxy configuration
+
+
+sslcerts
+--------
+ - keycloak.key
+ - keycloak.crt
+ - keycloak.pem
+
+
+nginx.conf
+----------
+ - SSL access to Keycloak can be enable either via a reverse proxy server or by configuration file changes directly on the Keycloak server container.
+ - SSL via external reverse proxy server like nginx is quite simile and a recommended way.
+ - Any traffic that comes to URL starting with https://localhost/auth is routed to the Keycloak container
+ - Any traffic that comes to http (port 80) will be redirected to https (port 443) as per the configuration
+ - nginx.conf file has additional comments where necessary
+
+
+keycloak
+---------
+The keycloak is configured with one realm, to be used as authorization server for Solace PubSub+ broker configured as resource server.
+
+ - solace-oauth-resource-server-role-realm-export.json
+ As the name suggests this realm is for resource server role mode.
+
+ - Keycloak request for fetching the well-known URLs (like userInfoUrl, tokenUrl, discoveryUrl etc) for given realm
+ curl --location --request GET 'https://localhost:10443/auth/realms/solace-oauth-client-role/.well-known/openid-configuration'
+ curl --location --request GET 'https://localhost:10443/auth/realms/solace-oauth-resource-server-role/.well-known/openid-configuration'
+
+ - Keycloak request for fetching the Access and Id token
+ ```
+ curl --location --request POST 'https://localhost:10443/auth/realms/solace-oauth-resource-server-role/protocol/openid-connect/token' \
+ --header 'Content-Type: application/x-www-form-urlencoded' \
+ --data-urlencode 'username=admin' \
+ --data-urlencode 'password=mysecret!' \
+ --data-urlencode 'client_id=solclient_oauth' \
+ --data-urlencode 'grant_type=password' \
+ --data-urlencode 'scope=openid audience-for-solace-oauth autz-group-for-solace-oauth subject-for-solace-oauth' \
+ ```
+
+ The scope param contains the optional scope names. Depending on the required claims in the token we can add/remove scopes.
+
+High level steps in setting up the realm:
+ 1. Create a realm
+ 2. Optional, Change the realm access token timeout to 1 minutes
+ 3. Required, Create a Client with type = "confidential" and "service accounts enabled"
+ 4. Optional, Client > Scopes > remove all the existing scopes. So that unwanted claims are not included in token
+ 5. Required, Realm > Scopes > Create three custom scopes and corresponding mapper with hard coded value
+ - audience-for-solace-oauth - hardcoded claim value "solclient_oauth" (same as client name) for the "aud" claim in token
+ - autz-group-for-solace-oauth - hardcoded claim value "solclient_oauth_auth_group" for the "groups" claim in token
+ - subject-for-solace-oauth - hardcoded claim value "default" for the "sub" claim in token
+ 6. Required, Client > Scopes > Add the custom claims in the optional claims section
+ 7. Required, Realm > Users > Add a user named "admin" with password "mysecret!"
+ 8. Export a realm
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/oauth/keycloak/solace-oauth-resource-server1-realm-export.json b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/oauth/keycloak/solace-oauth-resource-server1-realm-export.json
new file mode 100644
index 00000000..d80d00ca
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/oauth/keycloak/solace-oauth-resource-server1-realm-export.json
@@ -0,0 +1,2276 @@
+{
+ "id": "solace-oauth-resource-server1",
+ "realm": "solace-oauth-resource-server1",
+ "notBefore": 0,
+ "defaultSignatureAlgorithm": "RS256",
+ "revokeRefreshToken": false,
+ "refreshTokenMaxReuse": 0,
+ "accessTokenLifespan": 7200,
+ "accessTokenLifespanForImplicitFlow": 900,
+ "ssoSessionIdleTimeout": 1800,
+ "ssoSessionMaxLifespan": 36000,
+ "ssoSessionIdleTimeoutRememberMe": 0,
+ "ssoSessionMaxLifespanRememberMe": 0,
+ "offlineSessionIdleTimeout": 2592000,
+ "offlineSessionMaxLifespanEnabled": false,
+ "offlineSessionMaxLifespan": 5184000,
+ "clientSessionIdleTimeout": 0,
+ "clientSessionMaxLifespan": 0,
+ "clientOfflineSessionIdleTimeout": 0,
+ "clientOfflineSessionMaxLifespan": 0,
+ "accessCodeLifespan": 60,
+ "accessCodeLifespanUserAction": 300,
+ "accessCodeLifespanLogin": 1800,
+ "actionTokenGeneratedByAdminLifespan": 43200,
+ "actionTokenGeneratedByUserLifespan": 300,
+ "oauth2DeviceCodeLifespan": 600,
+ "oauth2DevicePollingInterval": 5,
+ "enabled": true,
+ "sslRequired": "external",
+ "registrationAllowed": false,
+ "registrationEmailAsUsername": false,
+ "rememberMe": false,
+ "verifyEmail": false,
+ "loginWithEmailAllowed": true,
+ "duplicateEmailsAllowed": false,
+ "resetPasswordAllowed": false,
+ "editUsernameAllowed": false,
+ "bruteForceProtected": false,
+ "permanentLockout": false,
+ "maxFailureWaitSeconds": 900,
+ "minimumQuickLoginWaitSeconds": 60,
+ "waitIncrementSeconds": 60,
+ "quickLoginCheckMilliSeconds": 1000,
+ "maxDeltaTimeSeconds": 43200,
+ "failureFactor": 30,
+ "roles": {
+ "realm": [
+ {
+ "id": "bdda6bc5-75eb-449f-8521-a2faec50a08d",
+ "name": "default-roles-solace-oauth-resource-server1",
+ "description": "${role_default-roles}",
+ "composite": true,
+ "composites": {
+ "realm": [
+ "offline_access",
+ "uma_authorization"
+ ],
+ "client": {
+ "account": [
+ "manage-account",
+ "view-profile"
+ ]
+ }
+ },
+ "clientRole": false,
+ "containerId": "solace-oauth-resource-server1",
+ "attributes": {}
+ },
+ {
+ "id": "2e5b3f5c-6bb7-4f35-94c3-a277948e2f06",
+ "name": "uma_authorization",
+ "description": "${role_uma_authorization}",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "solace-oauth-resource-server1",
+ "attributes": {}
+ },
+ {
+ "id": "844b9405-65bf-4b12-8cd6-e88860d2afe1",
+ "name": "offline_access",
+ "description": "${role_offline-access}",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "solace-oauth-resource-server1",
+ "attributes": {}
+ }
+ ],
+ "client": {
+ "realm-management": [
+ {
+ "id": "85865318-e7e8-4511-ae1b-a09ff49bf283",
+ "name": "manage-identity-providers",
+ "description": "${role_manage-identity-providers}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "b6fb2701-b3dd-44cb-bc86-cd47c7c225ef",
+ "name": "manage-authorization",
+ "description": "${role_manage-authorization}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "925a03f4-8a0c-4959-9f37-72964654ea03",
+ "name": "manage-users",
+ "description": "${role_manage-users}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "40402bdd-018c-4119-8cf6-72aa2cf5fb71",
+ "name": "view-identity-providers",
+ "description": "${role_view-identity-providers}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "48ea6621-9819-4090-80fe-c0e44305210d",
+ "name": "query-groups",
+ "description": "${role_query-groups}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "9abaa11c-4db2-4e51-ad84-ea1393333da5",
+ "name": "view-authorization",
+ "description": "${role_view-authorization}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "5cd096c9-7032-45d6-8899-34a072abe109",
+ "name": "query-clients",
+ "description": "${role_query-clients}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "2c609f41-70e2-4dc7-baa5-9275d4c08ae9",
+ "name": "view-clients",
+ "description": "${role_view-clients}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "query-clients"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "d51078b9-0585-4f61-8ce9-3c73452b105b",
+ "name": "manage-realm",
+ "description": "${role_manage-realm}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "553070f0-aca8-4448-9f25-fcbff5cd2dcc",
+ "name": "impersonation",
+ "description": "${role_impersonation}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "1702c190-2ceb-4c2f-8356-ba39ebe09a64",
+ "name": "realm-admin",
+ "description": "${role_realm-admin}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "manage-identity-providers",
+ "manage-authorization",
+ "view-identity-providers",
+ "manage-users",
+ "query-groups",
+ "view-authorization",
+ "query-clients",
+ "view-clients",
+ "manage-realm",
+ "impersonation",
+ "manage-clients",
+ "create-client",
+ "view-events",
+ "manage-events",
+ "view-realm",
+ "view-users",
+ "query-users",
+ "query-realms"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "312152f8-e339-456a-b020-20c17e78c2ab",
+ "name": "manage-clients",
+ "description": "${role_manage-clients}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "7c5bd1cf-c5c0-4b62-aeb1-041b2290f1b1",
+ "name": "create-client",
+ "description": "${role_create-client}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "4e1340fa-e318-44bc-b430-85310342d3dc",
+ "name": "view-events",
+ "description": "${role_view-events}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "51bec016-19b0-456c-a0eb-12cd194b1188",
+ "name": "manage-events",
+ "description": "${role_manage-events}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "52bb2127-ec4b-4cb7-9518-a995d35da31d",
+ "name": "view-realm",
+ "description": "${role_view-realm}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "3caaba7c-0079-430c-9bba-419d35a69294",
+ "name": "view-users",
+ "description": "${role_view-users}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "query-groups",
+ "query-users"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "ccfa8727-d590-48e3-af71-05672392d99a",
+ "name": "query-users",
+ "description": "${role_query-users}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ },
+ {
+ "id": "d7c921d0-bb1e-422d-8f65-0c4dbafbf8c3",
+ "name": "query-realms",
+ "description": "${role_query-realms}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "attributes": {}
+ }
+ ],
+ "security-admin-console": [],
+ "admin-cli": [],
+ "solclient_oauth": [],
+ "account-console": [],
+ "broker": [
+ {
+ "id": "1e228e30-99a1-4edb-b6be-0c4134dabe91",
+ "name": "read-token",
+ "description": "${role_read-token}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "e7d16a15-00b6-4123-93ee-ef946e307c31",
+ "attributes": {}
+ }
+ ],
+ "account": [
+ {
+ "id": "2b80753e-b5d9-4691-9917-cd583c1254e7",
+ "name": "manage-consent",
+ "description": "${role_manage-consent}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "account": [
+ "view-consent"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "c2042faf-a667-466d-bbb4-aecb63aa991f",
+ "attributes": {}
+ },
+ {
+ "id": "2266aa22-2f3d-4a32-8378-fd18c3eeac6b",
+ "name": "view-applications",
+ "description": "${role_view-applications}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "c2042faf-a667-466d-bbb4-aecb63aa991f",
+ "attributes": {}
+ },
+ {
+ "id": "29aa6bbf-a1ab-4600-925e-ae7d558841bd",
+ "name": "manage-account",
+ "description": "${role_manage-account}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "account": [
+ "manage-account-links"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "c2042faf-a667-466d-bbb4-aecb63aa991f",
+ "attributes": {}
+ },
+ {
+ "id": "ae53ffdb-0e03-469b-b4d6-c3d6f1ae089f",
+ "name": "view-profile",
+ "description": "${role_view-profile}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "c2042faf-a667-466d-bbb4-aecb63aa991f",
+ "attributes": {}
+ },
+ {
+ "id": "6fe55a48-5732-41f5-a658-c3f82dd2afcc",
+ "name": "delete-account",
+ "description": "${role_delete-account}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "c2042faf-a667-466d-bbb4-aecb63aa991f",
+ "attributes": {}
+ },
+ {
+ "id": "de4f98ac-c1d1-4bed-bacf-e34d068ba7de",
+ "name": "view-consent",
+ "description": "${role_view-consent}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "c2042faf-a667-466d-bbb4-aecb63aa991f",
+ "attributes": {}
+ },
+ {
+ "id": "083c1f91-5bb7-4c82-b6c0-3e72c5ae080a",
+ "name": "manage-account-links",
+ "description": "${role_manage-account-links}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "c2042faf-a667-466d-bbb4-aecb63aa991f",
+ "attributes": {}
+ }
+ ]
+ }
+ },
+ "groups": [],
+ "defaultRole": {
+ "id": "bdda6bc5-75eb-449f-8521-a2faec50a08d",
+ "name": "default-roles-solace-oauth-resource-server1",
+ "description": "${role_default-roles}",
+ "composite": true,
+ "clientRole": false,
+ "containerId": "solace-oauth-resource-server1"
+ },
+ "requiredCredentials": [
+ "password"
+ ],
+ "otpPolicyType": "totp",
+ "otpPolicyAlgorithm": "HmacSHA1",
+ "otpPolicyInitialCounter": 0,
+ "otpPolicyDigits": 6,
+ "otpPolicyLookAheadWindow": 1,
+ "otpPolicyPeriod": 30,
+ "otpSupportedApplications": [
+ "FreeOTP",
+ "Google Authenticator"
+ ],
+ "webAuthnPolicyRpEntityName": "keycloak",
+ "webAuthnPolicySignatureAlgorithms": [
+ "ES256"
+ ],
+ "webAuthnPolicyRpId": "",
+ "webAuthnPolicyAttestationConveyancePreference": "not specified",
+ "webAuthnPolicyAuthenticatorAttachment": "not specified",
+ "webAuthnPolicyRequireResidentKey": "not specified",
+ "webAuthnPolicyUserVerificationRequirement": "not specified",
+ "webAuthnPolicyCreateTimeout": 0,
+ "webAuthnPolicyAvoidSameAuthenticatorRegister": false,
+ "webAuthnPolicyAcceptableAaguids": [],
+ "webAuthnPolicyPasswordlessRpEntityName": "keycloak",
+ "webAuthnPolicyPasswordlessSignatureAlgorithms": [
+ "ES256"
+ ],
+ "webAuthnPolicyPasswordlessRpId": "",
+ "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
+ "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
+ "webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
+ "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
+ "webAuthnPolicyPasswordlessCreateTimeout": 0,
+ "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
+ "webAuthnPolicyPasswordlessAcceptableAaguids": [],
+ "users": [
+ {
+ "username": "service-account-solclient_oauth",
+ "enabled": true,
+ "totp": false,
+ "emailVerified": false,
+ "serviceAccountClientId": "solclient_oauth",
+ "disableableCredentialTypes": [],
+ "requiredActions": [],
+ "realmRoles": [
+ "default-roles-solace-oauth-resource-server1"
+ ],
+ "notBefore": 0,
+ "groups": []
+ }
+ ],
+ "scopeMappings": [
+ {
+ "clientScope": "offline_access",
+ "roles": [
+ "offline_access"
+ ]
+ }
+ ],
+ "clientScopeMappings": {
+ "account": [
+ {
+ "client": "account-console",
+ "roles": [
+ "manage-account"
+ ]
+ }
+ ]
+ },
+ "clients": [
+ {
+ "id": "c2042faf-a667-466d-bbb4-aecb63aa991f",
+ "clientId": "account",
+ "name": "${client_account}",
+ "rootUrl": "${authBaseUrl}",
+ "baseUrl": "/realms/solace-oauth-resource-server1/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/realms/solace-oauth-resource-server1/account/*"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "6849a52c-b27c-4727-851f-1211bf2292d0",
+ "clientId": "account-console",
+ "name": "${client_account-console}",
+ "rootUrl": "${authBaseUrl}",
+ "baseUrl": "/realms/solace-oauth-resource-server1/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/realms/solace-oauth-resource-server1/account/*"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "pkce.code.challenge.method": "S256"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "protocolMappers": [
+ {
+ "id": "42bbbdaf-414a-4ca3-840a-cdf329f15fef",
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {}
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "c3e2dd5a-93df-4acd-b0ed-f91f06423882",
+ "clientId": "admin-cli",
+ "name": "${client_admin-cli}",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": false,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": true,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "e7d16a15-00b6-4123-93ee-ef946e307c31",
+ "clientId": "broker",
+ "name": "${client_broker}",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": true,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "d541c266-38a9-4b28-b78d-b15d268bc612",
+ "clientId": "realm-management",
+ "name": "${client_realm-management}",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": true,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "035b3a63-deb4-43a5-b890-295e70916e13",
+ "clientId": "security-admin-console",
+ "name": "${client_security-admin-console}",
+ "rootUrl": "${authAdminUrl}",
+ "baseUrl": "/admin/solace-oauth-resource-server1/console/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/admin/solace-oauth-resource-server1/console/*"
+ ],
+ "webOrigins": [
+ "+"
+ ],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "pkce.code.challenge.method": "S256"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "protocolMappers": [
+ {
+ "id": "47daabc3-9e21-4d79-b2c8-6cf5b2e9b84f",
+ "name": "locale",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "locale",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "locale",
+ "jsonType.label": "String"
+ }
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "ca72d62e-5a3f-43e1-8cfa-8b6e29e82415",
+ "clientId": "solclient_oauth",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "j6gWnw13iqzJfFZzlqzaQabQgXza4oHl",
+ "redirectUris": [
+ "localhost:8080"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": true,
+ "serviceAccountsEnabled": true,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "id.token.as.detached.signature": "false",
+ "saml.assertion.signature": "false",
+ "access.token.lifespan": "60",
+ "saml.force.post.binding": "false",
+ "saml.multivalued.roles": "false",
+ "saml.encrypt": "false",
+ "oauth2.device.authorization.grant.enabled": "false",
+ "backchannel.logout.revoke.offline.tokens": "false",
+ "saml.server.signature": "false",
+ "saml.server.signature.keyinfo.ext": "false",
+ "use.refresh.tokens": "true",
+ "exclude.session.state.from.auth.response": "false",
+ "oidc.ciba.grant.enabled": "false",
+ "saml.artifact.binding": "false",
+ "backchannel.logout.session.required": "true",
+ "client_credentials.use_refresh_token": "false",
+ "saml_force_name_id_format": "false",
+ "require.pushed.authorization.requests": "false",
+ "saml.client.signature": "false",
+ "tls.client.certificate.bound.access.tokens": "false",
+ "saml.authnstatement": "false",
+ "display.on.consent.screen": "false",
+ "saml.onetimeuse.condition": "false"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": true,
+ "nodeReRegistrationTimeout": -1,
+ "defaultClientScopes": [],
+ "optionalClientScopes": [
+ "audience-for-solace-oauth",
+ "autz-group-for-solace-oauth",
+ "subject-for-solace-oauth"
+ ]
+ }
+ ],
+ "clientScopes": [
+ {
+ "id": "d316150c-537b-4f09-b6b8-0f6b5e6d71b4",
+ "name": "audience-for-solace-oauth",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true"
+ },
+ "protocolMappers": [
+ {
+ "id": "2041d5d8-4a5d-46ca-b081-c0bf0a6a7952",
+ "name": "audience-claim-mapper",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-mapper",
+ "consentRequired": false,
+ "config": {
+ "included.client.audience": "solclient_oauth",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "userinfo.token.claim": "true"
+ }
+ }
+ ]
+ },
+ {
+ "id": "ec8f6ef3-47d7-469f-b8cb-5401957e5b38",
+ "name": "offline_access",
+ "description": "OpenID Connect built-in scope: offline_access",
+ "protocol": "openid-connect",
+ "attributes": {
+ "consent.screen.text": "${offlineAccessScopeConsentText}",
+ "display.on.consent.screen": "true"
+ }
+ },
+ {
+ "id": "a07707ab-769c-466c-ba6d-5976560a20ae",
+ "name": "microprofile-jwt",
+ "description": "Microprofile - JWT built-in scope",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "false"
+ },
+ "protocolMappers": [
+ {
+ "id": "15477678-dc59-44e9-a031-5dd0f9eaf26e",
+ "name": "groups",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "multivalued": "true",
+ "userinfo.token.claim": "true",
+ "user.attribute": "foo",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "groups",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "3983df24-cbdf-4067-999c-f641f96e4aa8",
+ "name": "upn",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "upn",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "id": "26e7b8e5-b439-4bf3-9af2-d9bc69943402",
+ "name": "subject-for-solace-oauth",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true"
+ },
+ "protocolMappers": [
+ {
+ "id": "d16309b8-f991-4651-a72b-a974d3405aa2",
+ "name": "subject-for-solace-oauth-mapper",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-hardcoded-claim-mapper",
+ "consentRequired": false,
+ "config": {
+ "claim.value": "default",
+ "userinfo.token.claim": "true",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "sub",
+ "jsonType.label": "String",
+ "access.tokenResponse.claim": "false"
+ }
+ }
+ ]
+ },
+ {
+ "id": "b4fa6af1-f1f5-4e8e-897b-15a46a381c7c",
+ "name": "autz-group-for-solace-oauth",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true"
+ },
+ "protocolMappers": [
+ {
+ "id": "24ae8971-bfde-4b15-ab60-6d8490baf2e3",
+ "name": "authz-group-mapper",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-hardcoded-claim-mapper",
+ "consentRequired": false,
+ "config": {
+ "claim.value": "solclient_oauth_auth_group",
+ "userinfo.token.claim": "true",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "groups",
+ "jsonType.label": "String",
+ "access.tokenResponse.claim": "false"
+ }
+ }
+ ]
+ },
+ {
+ "id": "16320014-ba72-4e98-8f1a-8bfdebd8bd7b",
+ "name": "address",
+ "description": "OpenID Connect built-in scope: address",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${addressScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "d4f85fe2-588c-4c0f-a879-5333c625510d",
+ "name": "address",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-address-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute.formatted": "formatted",
+ "user.attribute.country": "country",
+ "user.attribute.postal_code": "postal_code",
+ "userinfo.token.claim": "true",
+ "user.attribute.street": "street",
+ "id.token.claim": "true",
+ "user.attribute.region": "region",
+ "access.token.claim": "true",
+ "user.attribute.locality": "locality"
+ }
+ }
+ ]
+ },
+ {
+ "id": "f3083007-6fbd-463c-9f92-b6c46a914c66",
+ "name": "profile",
+ "description": "OpenID Connect built-in scope: profile",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${profileScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "a55535ef-2342-45d5-b663-54e6bc3ff9b3",
+ "name": "website",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "website",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "website",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "2a9fcc4d-e06f-4196-a3c0-0a452fd33d64",
+ "name": "given name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "firstName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "given_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "0c5e7e16-a5b0-4b9f-8cb1-0a63a31ce5c6",
+ "name": "full name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-full-name-mapper",
+ "consentRequired": false,
+ "config": {
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "userinfo.token.claim": "true"
+ }
+ },
+ {
+ "id": "9cce90e3-1d71-4153-9d3a-f17d5a2f5296",
+ "name": "profile",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "profile",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "profile",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "8cc21024-28eb-4481-8fdd-4d7ac02874f7",
+ "name": "family name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "lastName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "family_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "f321a059-5ca4-444e-b32f-47533ced0139",
+ "name": "birthdate",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "birthdate",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "birthdate",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "d8d793e1-41ca-437f-9dfa-a3dfdb80159b",
+ "name": "gender",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "gender",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "gender",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "5b9a2378-488d-4470-bc73-e342b2db87d8",
+ "name": "updated at",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "updatedAt",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "updated_at",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "d69de674-2c5d-4347-8e0f-f019fadcfda8",
+ "name": "locale",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "locale",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "locale",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "ff32b8c6-863b-4167-80ed-5f5be5c17327",
+ "name": "username",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "preferred_username",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "3829eb41-e62f-4846-b0e4-3d35f3232ef8",
+ "name": "picture",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "picture",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "picture",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "d8d19c54-519e-4a6e-8687-e1ed2a09c6ad",
+ "name": "zoneinfo",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "zoneinfo",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "zoneinfo",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "33dc34fc-0585-4ae5-a8ce-5b74d54e88dc",
+ "name": "nickname",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "nickname",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "nickname",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "453902b9-deb7-475c-912b-159fea3b0032",
+ "name": "middle name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "middleName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "middle_name",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "id": "f1b39a65-489a-4a23-be2c-4b833effd930",
+ "name": "web-origins",
+ "description": "OpenID Connect scope for add allowed web origins to the access token",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "false",
+ "display.on.consent.screen": "false",
+ "consent.screen.text": ""
+ },
+ "protocolMappers": [
+ {
+ "id": "90b9d047-034a-4606-887c-fad91b70d054",
+ "name": "allowed web origins",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-allowed-origins-mapper",
+ "consentRequired": false,
+ "config": {}
+ }
+ ]
+ },
+ {
+ "id": "3c3c0a02-6f21-4df6-9b36-ec4ca22e7339",
+ "name": "role_list",
+ "description": "SAML role list",
+ "protocol": "saml",
+ "attributes": {
+ "consent.screen.text": "${samlRoleListScopeConsentText}",
+ "display.on.consent.screen": "true"
+ },
+ "protocolMappers": [
+ {
+ "id": "61bb315a-894b-4cf9-93dd-46674e34f032",
+ "name": "role list",
+ "protocol": "saml",
+ "protocolMapper": "saml-role-list-mapper",
+ "consentRequired": false,
+ "config": {
+ "single": "false",
+ "attribute.nameformat": "Basic",
+ "attribute.name": "Role"
+ }
+ }
+ ]
+ },
+ {
+ "id": "c3f097ba-0d14-427e-8e2a-0f1de79e1936",
+ "name": "roles",
+ "description": "OpenID Connect scope for add user roles to the access token",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "false",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${rolesScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "24011624-b743-4693-b116-3faca08c400c",
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {}
+ },
+ {
+ "id": "a2f82fd6-a037-4a28-8278-1947bd2fd9f2",
+ "name": "client roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-client-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "resource_access.${client_id}.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ },
+ {
+ "id": "171d8632-91ee-4bdf-934b-f5b120db0237",
+ "name": "realm roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "realm_access.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ }
+ ]
+ },
+ {
+ "id": "2b3ed8c0-59aa-462a-90e4-392c8caa2e14",
+ "name": "phone",
+ "description": "OpenID Connect built-in scope: phone",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${phoneScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "9f4263c8-9f0a-40dd-975b-9801fc9f7511",
+ "name": "phone number",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "phoneNumber",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "phone_number",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "f956ddd5-5ef6-405b-aa4c-b2c5fb857b80",
+ "name": "phone number verified",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "phoneNumberVerified",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "phone_number_verified",
+ "jsonType.label": "boolean"
+ }
+ }
+ ]
+ },
+ {
+ "id": "3a696d48-9448-4721-a342-f9f6b1d96a5f",
+ "name": "email",
+ "description": "OpenID Connect built-in scope: email",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${emailScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "ef56a486-ed82-4156-b7db-d2b636383274",
+ "name": "email",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "email",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "fff7f5d7-2b27-4893-b0f5-ebfa31c1745f",
+ "name": "email verified",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "emailVerified",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email_verified",
+ "jsonType.label": "boolean"
+ }
+ }
+ ]
+ }
+ ],
+ "defaultDefaultClientScopes": [
+ "email",
+ "role_list",
+ "roles",
+ "web-origins",
+ "profile"
+ ],
+ "defaultOptionalClientScopes": [
+ "address",
+ "phone",
+ "microprofile-jwt",
+ "offline_access"
+ ],
+ "browserSecurityHeaders": {
+ "contentSecurityPolicyReportOnly": "",
+ "xContentTypeOptions": "nosniff",
+ "xRobotsTag": "none",
+ "xFrameOptions": "SAMEORIGIN",
+ "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
+ "xXSSProtection": "1; mode=block",
+ "strictTransportSecurity": "max-age=31536000; includeSubDomains"
+ },
+ "smtpServer": {},
+ "eventsEnabled": false,
+ "eventsListeners": [
+ "jboss-logging"
+ ],
+ "enabledEventTypes": [],
+ "adminEventsEnabled": false,
+ "adminEventsDetailsEnabled": false,
+ "identityProviders": [],
+ "identityProviderMappers": [],
+ "components": {
+ "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
+ {
+ "id": "dc9326b6-bf59-4c8d-97a3-4eacbf65156b",
+ "name": "Trusted Hosts",
+ "providerId": "trusted-hosts",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "host-sending-registration-request-must-match": [
+ "true"
+ ],
+ "client-uris-must-match": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "9b67f7d3-d6f1-4d3f-a845-37792210f75e",
+ "name": "Max Clients Limit",
+ "providerId": "max-clients",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "max-clients": [
+ "200"
+ ]
+ }
+ },
+ {
+ "id": "3e071626-fc03-49e8-aeea-1dbc9bda92ca",
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "39167b16-a0d8-4871-a69e-e1146047333e",
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "f6093860-56d2-47c6-bec5-f00597562493",
+ "name": "Consent Required",
+ "providerId": "consent-required",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ },
+ {
+ "id": "ac7a7a58-2d71-48d4-8701-550b7f53998a",
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "oidc-full-name-mapper",
+ "saml-user-property-mapper",
+ "oidc-usermodel-attribute-mapper",
+ "saml-role-list-mapper",
+ "oidc-usermodel-property-mapper",
+ "saml-user-attribute-mapper",
+ "oidc-address-mapper",
+ "oidc-sha256-pairwise-sub-mapper"
+ ]
+ }
+ },
+ {
+ "id": "9c39686f-ab2f-4b32-8376-186bba863297",
+ "name": "Full Scope Disabled",
+ "providerId": "scope",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ },
+ {
+ "id": "5a183745-1acc-45a9-9a73-8599f5a572f0",
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "saml-user-attribute-mapper",
+ "oidc-full-name-mapper",
+ "oidc-usermodel-property-mapper",
+ "saml-user-property-mapper",
+ "oidc-usermodel-attribute-mapper",
+ "oidc-address-mapper",
+ "saml-role-list-mapper",
+ "oidc-sha256-pairwise-sub-mapper"
+ ]
+ }
+ }
+ ],
+ "org.keycloak.userprofile.UserProfileProvider": [
+ {
+ "id": "97b8087e-cb24-487c-8466-c525a067da32",
+ "providerId": "declarative-user-profile",
+ "subComponents": {},
+ "config": {}
+ }
+ ],
+ "org.keycloak.keys.KeyProvider": [
+ {
+ "id": "abb12870-75b9-4d43-9509-b25eef3aca7b",
+ "name": "rsa-enc-generated",
+ "providerId": "rsa-enc-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ],
+ "algorithm": [
+ "RSA-OAEP"
+ ]
+ }
+ },
+ {
+ "id": "4e480cfc-fc28-49e4-95a3-f022668898ce",
+ "name": "aes-generated",
+ "providerId": "aes-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ]
+ }
+ },
+ {
+ "id": "cfa31746-63ff-40e1-a03e-efdb0f0a1202",
+ "name": "hmac-generated",
+ "providerId": "hmac-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ],
+ "algorithm": [
+ "HS256"
+ ]
+ }
+ },
+ {
+ "id": "d5f12100-bcaa-4d6c-95fc-81e6e20f8824",
+ "name": "rsa-generated",
+ "providerId": "rsa-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ]
+ }
+ }
+ ]
+ },
+ "internationalizationEnabled": false,
+ "supportedLocales": [],
+ "authenticationFlows": [
+ {
+ "id": "0adddce7-f23b-4265-bb20-9981d93c0be8",
+ "alias": "Account verification options",
+ "description": "Method with which to verity the existing account",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "idp-email-verification",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "ALTERNATIVE",
+ "priority": 20,
+ "flowAlias": "Verify Existing Account by Re-authentication",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "47e7f60a-9a41-4f75-bda0-e26e5bd0f50e",
+ "alias": "Authentication Options",
+ "description": "Authentication options.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "basic-auth",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "basic-auth-otp",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-spnego",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 30,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "254b5acc-e25a-4a48-a7b0-6ba477f01fb1",
+ "alias": "Browser - Conditional OTP",
+ "description": "Flow to determine if the OTP is required for the authentication",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-otp-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "663a65ce-1a4e-4d03-9af7-68685e937817",
+ "alias": "Direct Grant - Conditional OTP",
+ "description": "Flow to determine if the OTP is required for the authentication",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "direct-grant-validate-otp",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "2720a078-f52e-45b4-8db7-ce6a69028cdc",
+ "alias": "First broker login - Conditional OTP",
+ "description": "Flow to determine if the OTP is required for the authentication",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-otp-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "9e9950c6-b9ba-48ef-93ba-e703aa96fb53",
+ "alias": "Handle Existing Account",
+ "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "idp-confirm-link",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "flowAlias": "Account verification options",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "87cdbd1d-ca15-4503-9f96-56672b3373ab",
+ "alias": "Reset - Conditional OTP",
+ "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "reset-otp",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "3cec4211-f9bd-40ac-bee9-a217c5bd4544",
+ "alias": "User creation or linking",
+ "description": "Flow for the existing/non-existing user alternatives",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticatorConfig": "create unique user config",
+ "authenticator": "idp-create-user-if-unique",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "ALTERNATIVE",
+ "priority": 20,
+ "flowAlias": "Handle Existing Account",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "e81c01c4-71f1-4fc8-b389-63492ba66264",
+ "alias": "Verify Existing Account by Re-authentication",
+ "description": "Reauthentication of existing account",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "idp-username-password-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 20,
+ "flowAlias": "First broker login - Conditional OTP",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "2738f462-fa7d-474b-8247-caa1a414d3ba",
+ "alias": "browser",
+ "description": "browser based authentication",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "auth-cookie",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-spnego",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "identity-provider-redirector",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 25,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "ALTERNATIVE",
+ "priority": 30,
+ "flowAlias": "forms",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "bdd1acf7-7393-40e1-9028-35ee54045b08",
+ "alias": "clients",
+ "description": "Base authentication for clients",
+ "providerId": "client-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "client-secret",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "client-jwt",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "client-secret-jwt",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 30,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "client-x509",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 40,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "3f28ee1e-fef9-49f4-8dd9-56002c0c2da9",
+ "alias": "direct grant",
+ "description": "OpenID Connect Resource Owner Grant",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "direct-grant-validate-username",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "direct-grant-validate-password",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 30,
+ "flowAlias": "Direct Grant - Conditional OTP",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "931260e1-f965-45a9-8afc-31cd30ed49cb",
+ "alias": "docker auth",
+ "description": "Used by Docker clients to authenticate against the IDP",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "docker-http-basic-authenticator",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "d76e28f6-aa7b-4f57-b79c-a1abde10af23",
+ "alias": "first broker login",
+ "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticatorConfig": "review profile config",
+ "authenticator": "idp-review-profile",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "flowAlias": "User creation or linking",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "22497645-eaa9-40fa-8650-4558ec6636e9",
+ "alias": "forms",
+ "description": "Username, password, otp and other auth forms.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "auth-username-password-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 20,
+ "flowAlias": "Browser - Conditional OTP",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "6030ff30-48ca-4e0d-8cf8-d142cbf159ed",
+ "alias": "http challenge",
+ "description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "no-cookie-redirect",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "flowAlias": "Authentication Options",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "8de1c97d-abc6-45ad-9705-b5b4701661e8",
+ "alias": "registration",
+ "description": "registration flow",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "registration-page-form",
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "flowAlias": "registration form",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "c56f2918-505e-463f-982f-caa67cdb5303",
+ "alias": "registration form",
+ "description": "registration form",
+ "providerId": "form-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "registration-user-creation",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "registration-profile-action",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 40,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "registration-password-action",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 50,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "registration-recaptcha-action",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 60,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "c5247bf0-faf4-4fda-bd03-c3beae3d37d2",
+ "alias": "reset credentials",
+ "description": "Reset credentials for a user if they forgot their password or something",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "reset-credentials-choose-user",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "reset-credential-email",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "reset-password",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 30,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 40,
+ "flowAlias": "Reset - Conditional OTP",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "07490c41-8a3a-49c6-98e8-7ed31a7a1fcd",
+ "alias": "saml ecp",
+ "description": "SAML ECP Profile Authentication Flow",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "http-basic-authenticator",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ }
+ ],
+ "authenticatorConfig": [
+ {
+ "id": "010507ce-ea67-4a21-8792-6eb4328649da",
+ "alias": "create unique user config",
+ "config": {
+ "require.password.update.after.registration": "false"
+ }
+ },
+ {
+ "id": "27ab5f93-8b3e-4698-9358-2f20ee64eae1",
+ "alias": "review profile config",
+ "config": {
+ "update.profile.on.first.login": "missing"
+ }
+ }
+ ],
+ "requiredActions": [
+ {
+ "alias": "CONFIGURE_TOTP",
+ "name": "Configure OTP",
+ "providerId": "CONFIGURE_TOTP",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 10,
+ "config": {}
+ },
+ {
+ "alias": "terms_and_conditions",
+ "name": "Terms and Conditions",
+ "providerId": "terms_and_conditions",
+ "enabled": false,
+ "defaultAction": false,
+ "priority": 20,
+ "config": {}
+ },
+ {
+ "alias": "UPDATE_PASSWORD",
+ "name": "Update Password",
+ "providerId": "UPDATE_PASSWORD",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 30,
+ "config": {}
+ },
+ {
+ "alias": "UPDATE_PROFILE",
+ "name": "Update Profile",
+ "providerId": "UPDATE_PROFILE",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 40,
+ "config": {}
+ },
+ {
+ "alias": "VERIFY_EMAIL",
+ "name": "Verify Email",
+ "providerId": "VERIFY_EMAIL",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 50,
+ "config": {}
+ },
+ {
+ "alias": "delete_account",
+ "name": "Delete Account",
+ "providerId": "delete_account",
+ "enabled": false,
+ "defaultAction": false,
+ "priority": 60,
+ "config": {}
+ },
+ {
+ "alias": "update_user_locale",
+ "name": "Update User Locale",
+ "providerId": "update_user_locale",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 1000,
+ "config": {}
+ }
+ ],
+ "browserFlow": "browser",
+ "registrationFlow": "registration",
+ "directGrantFlow": "direct grant",
+ "resetCredentialsFlow": "reset credentials",
+ "clientAuthenticationFlow": "clients",
+ "dockerAuthenticationFlow": "docker auth",
+ "attributes": {
+ "cibaBackchannelTokenDeliveryMode": "poll",
+ "cibaExpiresIn": "120",
+ "cibaAuthRequestedUserHint": "login_hint",
+ "oauth2DeviceCodeLifespan": "600",
+ "clientOfflineSessionMaxLifespan": "0",
+ "oauth2DevicePollingInterval": "5",
+ "clientSessionIdleTimeout": "0",
+ "parRequestUriLifespan": "60",
+ "clientSessionMaxLifespan": "0",
+ "frontendUrl": "https://solaceoauth:10443/auth/",
+ "clientOfflineSessionIdleTimeout": "0",
+ "cibaInterval": "5"
+ },
+ "keycloakVersion": "16.1.1",
+ "userManagedAccessAllowed": false,
+ "clientProfiles": {
+ "profiles": []
+ },
+ "clientPolicies": {
+ "policies": []
+ },
+ "users": [
+ {
+ "username": "admin",
+ "enabled": true,
+ "credentials": [
+ {
+ "type": "mysecret!",
+ "value": "mysecret!"
+ }
+ ],
+ "clientRoles": {
+ "realm-management": [ "realm-admin" ],
+ "account": [ "manage-account" ]
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/oauth/keycloak/solace-oauth-resource-server2-realm-export.json b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/oauth/keycloak/solace-oauth-resource-server2-realm-export.json
new file mode 100644
index 00000000..1cf7c14a
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/oauth/keycloak/solace-oauth-resource-server2-realm-export.json
@@ -0,0 +1,2136 @@
+{
+ "realm": "solace-oauth-resource-server2",
+ "notBefore": 0,
+ "defaultSignatureAlgorithm": "RS256",
+ "revokeRefreshToken": false,
+ "refreshTokenMaxReuse": 0,
+ "accessTokenLifespan": 7200,
+ "accessTokenLifespanForImplicitFlow": 900,
+ "ssoSessionIdleTimeout": 1800,
+ "ssoSessionMaxLifespan": 36000,
+ "ssoSessionIdleTimeoutRememberMe": 0,
+ "ssoSessionMaxLifespanRememberMe": 0,
+ "offlineSessionIdleTimeout": 2592000,
+ "offlineSessionMaxLifespanEnabled": false,
+ "offlineSessionMaxLifespan": 5184000,
+ "clientSessionIdleTimeout": 0,
+ "clientSessionMaxLifespan": 0,
+ "clientOfflineSessionIdleTimeout": 0,
+ "clientOfflineSessionMaxLifespan": 0,
+ "accessCodeLifespan": 60,
+ "accessCodeLifespanUserAction": 300,
+ "accessCodeLifespanLogin": 1800,
+ "actionTokenGeneratedByAdminLifespan": 43200,
+ "actionTokenGeneratedByUserLifespan": 300,
+ "oauth2DeviceCodeLifespan": 600,
+ "oauth2DevicePollingInterval": 5,
+ "enabled": true,
+ "sslRequired": "external",
+ "registrationAllowed": false,
+ "registrationEmailAsUsername": false,
+ "rememberMe": false,
+ "verifyEmail": false,
+ "loginWithEmailAllowed": true,
+ "duplicateEmailsAllowed": false,
+ "resetPasswordAllowed": false,
+ "editUsernameAllowed": false,
+ "bruteForceProtected": false,
+ "permanentLockout": false,
+ "maxFailureWaitSeconds": 900,
+ "minimumQuickLoginWaitSeconds": 60,
+ "waitIncrementSeconds": 60,
+ "quickLoginCheckMilliSeconds": 1000,
+ "maxDeltaTimeSeconds": 43200,
+ "failureFactor": 30,
+ "roles": {
+ "realm": [
+ {
+ "name": "default-roles-solace-oauth-resource-server2",
+ "description": "${role_default-roles}",
+ "composite": true,
+ "composites": {
+ "realm": [
+ "offline_access",
+ "uma_authorization"
+ ],
+ "client": {
+ "account": [
+ "manage-account",
+ "view-profile"
+ ]
+ }
+ },
+ "clientRole": false,
+ "containerId": "solace-oauth-resource-server2",
+ "attributes": {}
+ },
+ {
+ "name": "uma_authorization",
+ "description": "${role_uma_authorization}",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "solace-oauth-resource-server2",
+ "attributes": {}
+ },
+ {
+ "name": "offline_access",
+ "description": "${role_offline-access}",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "solace-oauth-resource-server2",
+ "attributes": {}
+ }
+ ],
+ "client": {
+ "realm-management": [
+ {
+ "name": "manage-identity-providers",
+ "description": "${role_manage-identity-providers}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-authorization",
+ "description": "${role_manage-authorization}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-users",
+ "description": "${role_manage-users}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-identity-providers",
+ "description": "${role_view-identity-providers}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "query-groups",
+ "description": "${role_query-groups}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-authorization",
+ "description": "${role_view-authorization}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "query-clients",
+ "description": "${role_query-clients}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-clients",
+ "description": "${role_view-clients}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "query-clients"
+ ]
+ }
+ },
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-realm",
+ "description": "${role_manage-realm}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "impersonation",
+ "description": "${role_impersonation}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "realm-admin",
+ "description": "${role_realm-admin}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "manage-identity-providers",
+ "manage-authorization",
+ "view-identity-providers",
+ "manage-users",
+ "query-groups",
+ "view-authorization",
+ "query-clients",
+ "view-clients",
+ "manage-realm",
+ "impersonation",
+ "manage-clients",
+ "create-client",
+ "view-events",
+ "manage-events",
+ "view-realm",
+ "view-users",
+ "query-users",
+ "query-realms"
+ ]
+ }
+ },
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-clients",
+ "description": "${role_manage-clients}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "create-client",
+ "description": "${role_create-client}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-events",
+ "description": "${role_view-events}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-events",
+ "description": "${role_manage-events}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-realm",
+ "description": "${role_view-realm}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-users",
+ "description": "${role_view-users}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "query-groups",
+ "query-users"
+ ]
+ }
+ },
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "query-users",
+ "description": "${role_query-users}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "query-realms",
+ "description": "${role_query-realms}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ }
+ ],
+ "security-admin-console": [],
+ "admin-cli": [],
+ "solclient_oauth": [],
+ "account-console": [],
+ "broker": [
+ {
+ "name": "read-token",
+ "description": "${role_read-token}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ }
+ ],
+ "account": [
+ {
+ "name": "manage-consent",
+ "description": "${role_manage-consent}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "account": [
+ "view-consent"
+ ]
+ }
+ },
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-applications",
+ "description": "${role_view-applications}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-account",
+ "description": "${role_manage-account}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "account": [
+ "manage-account-links"
+ ]
+ }
+ },
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-profile",
+ "description": "${role_view-profile}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "delete-account",
+ "description": "${role_delete-account}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-consent",
+ "description": "${role_view-consent}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-account-links",
+ "description": "${role_manage-account-links}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ }
+ ]
+ }
+ },
+ "groups": [],
+ "defaultRole": {
+ "name": "default-roles-solace-oauth-resource-server2",
+ "description": "${role_default-roles}",
+ "composite": true,
+ "clientRole": false,
+ "containerId": "solace-oauth-resource-server2"
+ },
+ "requiredCredentials": [
+ "password"
+ ],
+ "otpPolicyType": "totp",
+ "otpPolicyAlgorithm": "HmacSHA1",
+ "otpPolicyInitialCounter": 0,
+ "otpPolicyDigits": 6,
+ "otpPolicyLookAheadWindow": 1,
+ "otpPolicyPeriod": 30,
+ "otpSupportedApplications": [
+ "FreeOTP",
+ "Google Authenticator"
+ ],
+ "webAuthnPolicyRpEntityName": "keycloak",
+ "webAuthnPolicySignatureAlgorithms": [
+ "ES256"
+ ],
+ "webAuthnPolicyRpId": "",
+ "webAuthnPolicyAttestationConveyancePreference": "not specified",
+ "webAuthnPolicyAuthenticatorAttachment": "not specified",
+ "webAuthnPolicyRequireResidentKey": "not specified",
+ "webAuthnPolicyUserVerificationRequirement": "not specified",
+ "webAuthnPolicyCreateTimeout": 0,
+ "webAuthnPolicyAvoidSameAuthenticatorRegister": false,
+ "webAuthnPolicyAcceptableAaguids": [],
+ "webAuthnPolicyPasswordlessRpEntityName": "keycloak",
+ "webAuthnPolicyPasswordlessSignatureAlgorithms": [
+ "ES256"
+ ],
+ "webAuthnPolicyPasswordlessRpId": "",
+ "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
+ "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
+ "webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
+ "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
+ "webAuthnPolicyPasswordlessCreateTimeout": 0,
+ "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
+ "webAuthnPolicyPasswordlessAcceptableAaguids": [],
+ "users": [
+ {
+ "username": "service-account-solclient_oauth",
+ "enabled": true,
+ "totp": false,
+ "emailVerified": false,
+ "serviceAccountClientId": "solclient_oauth",
+ "disableableCredentialTypes": [],
+ "requiredActions": [],
+ "realmRoles": [
+ "default-roles-solace-oauth-resource-server2"
+ ],
+ "notBefore": 0,
+ "groups": []
+ }
+ ],
+ "scopeMappings": [
+ {
+ "clientScope": "offline_access",
+ "roles": [
+ "offline_access"
+ ]
+ }
+ ],
+ "clientScopeMappings": {
+ "account": [
+ {
+ "client": "account-console",
+ "roles": [
+ "manage-account"
+ ]
+ }
+ ]
+ },
+ "clients": [
+ {
+ "clientId": "account",
+ "name": "${client_account}",
+ "rootUrl": "${authBaseUrl}",
+ "baseUrl": "/realms/solace-oauth-resource-server2/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/realms/solace-oauth-resource-server2/account/*"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "clientId": "account-console",
+ "name": "${client_account-console}",
+ "rootUrl": "${authBaseUrl}",
+ "baseUrl": "/realms/solace-oauth-resource-server2/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/realms/solace-oauth-resource-server2/account/*"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "pkce.code.challenge.method": "S256"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "protocolMappers": [
+ {
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {}
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "clientId": "admin-cli",
+ "name": "${client_admin-cli}",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": false,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": true,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "clientId": "broker",
+ "name": "${client_broker}",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": true,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "clientId": "realm-management",
+ "name": "${client_realm-management}",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": true,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "clientId": "security-admin-console",
+ "name": "${client_security-admin-console}",
+ "rootUrl": "${authAdminUrl}",
+ "baseUrl": "/admin/solace-oauth-resource-server2/console/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/admin/solace-oauth-resource-server2/console/*"
+ ],
+ "webOrigins": [
+ "+"
+ ],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "pkce.code.challenge.method": "S256"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "protocolMappers": [
+ {
+ "name": "locale",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "locale",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "locale",
+ "jsonType.label": "String"
+ }
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "clientId": "solclient_oauth",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "j6gWnw13iqzJfFZzlqzaQabQgXza4oHl",
+ "redirectUris": [
+ "localhost:8080"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": true,
+ "serviceAccountsEnabled": true,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "id.token.as.detached.signature": "false",
+ "saml.assertion.signature": "false",
+ "access.token.lifespan": "60",
+ "saml.force.post.binding": "false",
+ "saml.multivalued.roles": "false",
+ "saml.encrypt": "false",
+ "oauth2.device.authorization.grant.enabled": "false",
+ "backchannel.logout.revoke.offline.tokens": "false",
+ "saml.server.signature": "false",
+ "saml.server.signature.keyinfo.ext": "false",
+ "use.refresh.tokens": "true",
+ "exclude.session.state.from.auth.response": "false",
+ "oidc.ciba.grant.enabled": "false",
+ "saml.artifact.binding": "false",
+ "backchannel.logout.session.required": "true",
+ "client_credentials.use_refresh_token": "false",
+ "saml_force_name_id_format": "false",
+ "require.pushed.authorization.requests": "false",
+ "saml.client.signature": "false",
+ "tls.client.certificate.bound.access.tokens": "false",
+ "saml.authnstatement": "false",
+ "display.on.consent.screen": "false",
+ "saml.onetimeuse.condition": "false"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": true,
+ "nodeReRegistrationTimeout": -1,
+ "defaultClientScopes": [],
+ "optionalClientScopes": [
+ "audience-for-solace-oauth",
+ "autz-group-for-solace-oauth",
+ "subject-for-solace-oauth"
+ ]
+ }
+ ],
+ "clientScopes": [
+ {
+ "name": "audience-for-solace-oauth",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true"
+ },
+ "protocolMappers": [
+ {
+ "name": "audience-claim-mapper",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-mapper",
+ "consentRequired": false,
+ "config": {
+ "included.client.audience": "solclient_oauth",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "userinfo.token.claim": "true"
+ }
+ }
+ ]
+ },
+ {
+ "name": "offline_access",
+ "description": "OpenID Connect built-in scope: offline_access",
+ "protocol": "openid-connect",
+ "attributes": {
+ "consent.screen.text": "${offlineAccessScopeConsentText}",
+ "display.on.consent.screen": "true"
+ }
+ },
+ {
+ "name": "microprofile-jwt",
+ "description": "Microprofile - JWT built-in scope",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "false"
+ },
+ "protocolMappers": [
+ {
+ "name": "groups",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "multivalued": "true",
+ "userinfo.token.claim": "true",
+ "user.attribute": "foo",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "groups",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "upn",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "upn",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "name": "subject-for-solace-oauth",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true"
+ },
+ "protocolMappers": [
+ {
+ "name": "subject-for-solace-oauth-mapper",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-hardcoded-claim-mapper",
+ "consentRequired": false,
+ "config": {
+ "claim.value": "default",
+ "userinfo.token.claim": "true",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "sub",
+ "jsonType.label": "String",
+ "access.tokenResponse.claim": "false"
+ }
+ }
+ ]
+ },
+ {
+ "name": "autz-group-for-solace-oauth",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true"
+ },
+ "protocolMappers": [
+ {
+ "name": "authz-group-mapper",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-hardcoded-claim-mapper",
+ "consentRequired": false,
+ "config": {
+ "claim.value": "solclient_oauth_auth_group",
+ "userinfo.token.claim": "true",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "groups",
+ "jsonType.label": "String",
+ "access.tokenResponse.claim": "false"
+ }
+ }
+ ]
+ },
+ {
+ "name": "address",
+ "description": "OpenID Connect built-in scope: address",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${addressScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "name": "address",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-address-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute.formatted": "formatted",
+ "user.attribute.country": "country",
+ "user.attribute.postal_code": "postal_code",
+ "userinfo.token.claim": "true",
+ "user.attribute.street": "street",
+ "id.token.claim": "true",
+ "user.attribute.region": "region",
+ "access.token.claim": "true",
+ "user.attribute.locality": "locality"
+ }
+ }
+ ]
+ },
+ {
+ "name": "profile",
+ "description": "OpenID Connect built-in scope: profile",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${profileScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "name": "website",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "website",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "website",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "given name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "firstName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "given_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "full name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-full-name-mapper",
+ "consentRequired": false,
+ "config": {
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "userinfo.token.claim": "true"
+ }
+ },
+ {
+ "name": "profile",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "profile",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "profile",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "family name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "lastName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "family_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "birthdate",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "birthdate",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "birthdate",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "gender",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "gender",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "gender",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "updated at",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "updatedAt",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "updated_at",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "locale",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "locale",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "locale",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "username",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "preferred_username",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "picture",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "picture",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "picture",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "zoneinfo",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "zoneinfo",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "zoneinfo",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "nickname",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "nickname",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "nickname",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "middle name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "middleName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "middle_name",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "name": "web-origins",
+ "description": "OpenID Connect scope for add allowed web origins to the access token",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "false",
+ "display.on.consent.screen": "false",
+ "consent.screen.text": ""
+ },
+ "protocolMappers": [
+ {
+ "name": "allowed web origins",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-allowed-origins-mapper",
+ "consentRequired": false,
+ "config": {}
+ }
+ ]
+ },
+ {
+ "name": "role_list",
+ "description": "SAML role list",
+ "protocol": "saml",
+ "attributes": {
+ "consent.screen.text": "${samlRoleListScopeConsentText}",
+ "display.on.consent.screen": "true"
+ },
+ "protocolMappers": [
+ {
+ "name": "role list",
+ "protocol": "saml",
+ "protocolMapper": "saml-role-list-mapper",
+ "consentRequired": false,
+ "config": {
+ "single": "false",
+ "attribute.nameformat": "Basic",
+ "attribute.name": "Role"
+ }
+ }
+ ]
+ },
+ {
+ "name": "roles",
+ "description": "OpenID Connect scope for add user roles to the access token",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "false",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${rolesScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {}
+ },
+ {
+ "name": "client roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-client-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "resource_access.${client_id}.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ },
+ {
+ "name": "realm roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "realm_access.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ }
+ ]
+ },
+ {
+ "name": "phone",
+ "description": "OpenID Connect built-in scope: phone",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${phoneScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "name": "phone number",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "phoneNumber",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "phone_number",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "phone number verified",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "phoneNumberVerified",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "phone_number_verified",
+ "jsonType.label": "boolean"
+ }
+ }
+ ]
+ },
+ {
+ "name": "email",
+ "description": "OpenID Connect built-in scope: email",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${emailScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "name": "email",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "email",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "email verified",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "emailVerified",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email_verified",
+ "jsonType.label": "boolean"
+ }
+ }
+ ]
+ }
+ ],
+ "defaultDefaultClientScopes": [
+ "email",
+ "role_list",
+ "roles",
+ "web-origins",
+ "profile"
+ ],
+ "defaultOptionalClientScopes": [
+ "address",
+ "phone",
+ "microprofile-jwt",
+ "offline_access"
+ ],
+ "browserSecurityHeaders": {
+ "contentSecurityPolicyReportOnly": "",
+ "xContentTypeOptions": "nosniff",
+ "xRobotsTag": "none",
+ "xFrameOptions": "SAMEORIGIN",
+ "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
+ "xXSSProtection": "1; mode=block",
+ "strictTransportSecurity": "max-age=31536000; includeSubDomains"
+ },
+ "smtpServer": {},
+ "eventsEnabled": false,
+ "eventsListeners": [
+ "jboss-logging"
+ ],
+ "enabledEventTypes": [],
+ "adminEventsEnabled": false,
+ "adminEventsDetailsEnabled": false,
+ "identityProviders": [],
+ "identityProviderMappers": [],
+ "components": {
+ "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
+ {
+ "name": "Trusted Hosts",
+ "providerId": "trusted-hosts",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "host-sending-registration-request-must-match": [
+ "true"
+ ],
+ "client-uris-must-match": [
+ "true"
+ ]
+ }
+ },
+ {
+ "name": "Max Clients Limit",
+ "providerId": "max-clients",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "max-clients": [
+ "200"
+ ]
+ }
+ },
+ {
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "name": "Consent Required",
+ "providerId": "consent-required",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ },
+ {
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "oidc-full-name-mapper",
+ "saml-user-property-mapper",
+ "oidc-usermodel-attribute-mapper",
+ "saml-role-list-mapper",
+ "oidc-usermodel-property-mapper",
+ "saml-user-attribute-mapper",
+ "oidc-address-mapper",
+ "oidc-sha256-pairwise-sub-mapper"
+ ]
+ }
+ },
+ {
+ "name": "Full Scope Disabled",
+ "providerId": "scope",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ },
+ {
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "saml-user-attribute-mapper",
+ "oidc-full-name-mapper",
+ "oidc-usermodel-property-mapper",
+ "saml-user-property-mapper",
+ "oidc-usermodel-attribute-mapper",
+ "oidc-address-mapper",
+ "saml-role-list-mapper",
+ "oidc-sha256-pairwise-sub-mapper"
+ ]
+ }
+ }
+ ],
+ "org.keycloak.userprofile.UserProfileProvider": [
+ {
+ "providerId": "declarative-user-profile",
+ "subComponents": {},
+ "config": {}
+ }
+ ],
+ "org.keycloak.keys.KeyProvider": [
+ {
+ "name": "rsa-enc-generated",
+ "providerId": "rsa-enc-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ],
+ "algorithm": [
+ "RSA-OAEP"
+ ]
+ }
+ },
+ {
+ "name": "aes-generated",
+ "providerId": "aes-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ]
+ }
+ },
+ {
+ "name": "hmac-generated",
+ "providerId": "hmac-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ],
+ "algorithm": [
+ "HS256"
+ ]
+ }
+ },
+ {
+ "name": "rsa-generated",
+ "providerId": "rsa-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ]
+ }
+ }
+ ]
+ },
+ "internationalizationEnabled": false,
+ "supportedLocales": [],
+ "authenticationFlows": [
+ {
+ "alias": "Account verification options",
+ "description": "Method with which to verity the existing account",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "idp-email-verification",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "ALTERNATIVE",
+ "priority": 20,
+ "flowAlias": "Verify Existing Account by Re-authentication",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "alias": "Authentication Options",
+ "description": "Authentication options.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "basic-auth",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "basic-auth-otp",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-spnego",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 30,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "alias": "Browser - Conditional OTP",
+ "description": "Flow to determine if the OTP is required for the authentication",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-otp-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "alias": "Direct Grant - Conditional OTP",
+ "description": "Flow to determine if the OTP is required for the authentication",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "direct-grant-validate-otp",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "alias": "First broker login - Conditional OTP",
+ "description": "Flow to determine if the OTP is required for the authentication",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-otp-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "alias": "Handle Existing Account",
+ "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "idp-confirm-link",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "flowAlias": "Account verification options",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "alias": "Reset - Conditional OTP",
+ "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "reset-otp",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "alias": "User creation or linking",
+ "description": "Flow for the existing/non-existing user alternatives",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticatorConfig": "create unique user config",
+ "authenticator": "idp-create-user-if-unique",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "ALTERNATIVE",
+ "priority": 20,
+ "flowAlias": "Handle Existing Account",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "alias": "Verify Existing Account by Re-authentication",
+ "description": "Reauthentication of existing account",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "idp-username-password-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 20,
+ "flowAlias": "First broker login - Conditional OTP",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "alias": "browser",
+ "description": "browser based authentication",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "auth-cookie",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-spnego",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "identity-provider-redirector",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 25,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "ALTERNATIVE",
+ "priority": 30,
+ "flowAlias": "forms",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "alias": "clients",
+ "description": "Base authentication for clients",
+ "providerId": "client-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "client-secret",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "client-jwt",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "client-secret-jwt",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 30,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "client-x509",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 40,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "alias": "direct grant",
+ "description": "OpenID Connect Resource Owner Grant",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "direct-grant-validate-username",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "direct-grant-validate-password",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 30,
+ "flowAlias": "Direct Grant - Conditional OTP",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "alias": "docker auth",
+ "description": "Used by Docker clients to authenticate against the IDP",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "docker-http-basic-authenticator",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "alias": "first broker login",
+ "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticatorConfig": "review profile config",
+ "authenticator": "idp-review-profile",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "flowAlias": "User creation or linking",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "alias": "forms",
+ "description": "Username, password, otp and other auth forms.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "auth-username-password-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 20,
+ "flowAlias": "Browser - Conditional OTP",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "alias": "http challenge",
+ "description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "no-cookie-redirect",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "flowAlias": "Authentication Options",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "alias": "registration",
+ "description": "registration flow",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "registration-page-form",
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "flowAlias": "registration form",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "alias": "registration form",
+ "description": "registration form",
+ "providerId": "form-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "registration-user-creation",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "registration-profile-action",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 40,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "registration-password-action",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 50,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "registration-recaptcha-action",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 60,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "alias": "reset credentials",
+ "description": "Reset credentials for a user if they forgot their password or something",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "reset-credentials-choose-user",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "reset-credential-email",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "reset-password",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 30,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 40,
+ "flowAlias": "Reset - Conditional OTP",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "alias": "saml ecp",
+ "description": "SAML ECP Profile Authentication Flow",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "http-basic-authenticator",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ }
+ ],
+ "authenticatorConfig": [
+ {
+ "alias": "create unique user config",
+ "config": {
+ "require.password.update.after.registration": "false"
+ }
+ },
+ {
+ "alias": "review profile config",
+ "config": {
+ "update.profile.on.first.login": "missing"
+ }
+ }
+ ],
+ "requiredActions": [
+ {
+ "alias": "CONFIGURE_TOTP",
+ "name": "Configure OTP",
+ "providerId": "CONFIGURE_TOTP",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 10,
+ "config": {}
+ },
+ {
+ "alias": "terms_and_conditions",
+ "name": "Terms and Conditions",
+ "providerId": "terms_and_conditions",
+ "enabled": false,
+ "defaultAction": false,
+ "priority": 20,
+ "config": {}
+ },
+ {
+ "alias": "UPDATE_PASSWORD",
+ "name": "Update Password",
+ "providerId": "UPDATE_PASSWORD",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 30,
+ "config": {}
+ },
+ {
+ "alias": "UPDATE_PROFILE",
+ "name": "Update Profile",
+ "providerId": "UPDATE_PROFILE",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 40,
+ "config": {}
+ },
+ {
+ "alias": "VERIFY_EMAIL",
+ "name": "Verify Email",
+ "providerId": "VERIFY_EMAIL",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 50,
+ "config": {}
+ },
+ {
+ "alias": "delete_account",
+ "name": "Delete Account",
+ "providerId": "delete_account",
+ "enabled": false,
+ "defaultAction": false,
+ "priority": 60,
+ "config": {}
+ },
+ {
+ "alias": "update_user_locale",
+ "name": "Update User Locale",
+ "providerId": "update_user_locale",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 1000,
+ "config": {}
+ }
+ ],
+ "browserFlow": "browser",
+ "registrationFlow": "registration",
+ "directGrantFlow": "direct grant",
+ "resetCredentialsFlow": "reset credentials",
+ "clientAuthenticationFlow": "clients",
+ "dockerAuthenticationFlow": "docker auth",
+ "attributes": {
+ "cibaBackchannelTokenDeliveryMode": "poll",
+ "cibaExpiresIn": "120",
+ "cibaAuthRequestedUserHint": "login_hint",
+ "oauth2DeviceCodeLifespan": "600",
+ "clientOfflineSessionMaxLifespan": "0",
+ "oauth2DevicePollingInterval": "5",
+ "clientSessionIdleTimeout": "0",
+ "parRequestUriLifespan": "60",
+ "clientSessionMaxLifespan": "0",
+ "frontendUrl": "https://solaceoauth:10443/auth/",
+ "clientOfflineSessionIdleTimeout": "0",
+ "cibaInterval": "5"
+ },
+ "keycloakVersion": "16.1.1",
+ "userManagedAccessAllowed": false,
+ "clientProfiles": {
+ "profiles": []
+ },
+ "clientPolicies": {
+ "policies": []
+ },
+ "users": [
+ {
+ "username": "admin",
+ "enabled": true,
+ "credentials": [
+ {
+ "type": "mysecret!",
+ "value": "mysecret!"
+ }
+ ],
+ "clientRoles": {
+ "realm-management": [
+ "realm-admin"
+ ],
+ "account": [
+ "manage-account"
+ ]
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/oauth/nginx.conf b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/oauth/nginx.conf
new file mode 100644
index 00000000..a3ba5e27
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/oauth/nginx.conf
@@ -0,0 +1,39 @@
+events {
+}
+http {
+ server {
+ listen 1080;
+
+ server_name _;
+ return 301 https://$host:10443$request_uri; # redirect http requests to https
+ }
+ server {
+
+ include /etc/nginx/mime.types;
+
+ listen 10443 ssl;
+
+ ssl_ciphers ALL:@SECLEVEL=0; #required for self signed certs and/or for certs signed with weak ciphers
+ ssl_protocols TLSv1.2 TLSv1.3;
+ ssl_certificate /etc/sslcerts/keycloak.pem;
+ ssl_certificate_key /etc/sslcerts/keycloak.key;
+
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+ add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src http://example.com;";
+ #add_header X-Content-Type-Options nosniff; # cannot apply now because of open keycloak issue https://issues.redhat.com/browse/KEYCLOAK-17076
+ add_header X-XSS-Protection: "1; mode=block";
+
+ proxy_set_header X-Forwarded-For $proxy_protocol_addr; # To forward the original client's IP address
+ proxy_set_header X-Forwarded-Proto $scheme; # to forward the original protocol (HTTP or HTTPS)
+ proxy_set_header Host $host:$server_port; # to forward the original host requested by the client
+
+ location / {
+ root /data/www;
+ try_files $uri $uri/ /index.html; #to support in app routing in SPA
+ }
+
+ location /auth {
+ proxy_pass http://keycloak:8080; #redirect urls starting with /auth to keycloak
+ }
+ }
+}
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/oauth/www/index.html b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/oauth/www/index.html
new file mode 100644
index 00000000..e6047aa1
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/oauth/www/index.html
@@ -0,0 +1,6 @@
+
+
+ Hello World!!
+
+
+
\ No newline at end of file
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/solace.env b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/solace.env
new file mode 100644
index 00000000..04a0dfee
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/solace.env
@@ -0,0 +1,4 @@
+username_admin_globalaccesslevel=admin
+username_admin_password=admin
+system_scaling_maxconnectioncount=1000
+webmanager_redirecthttp_enable=false
diff --git a/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/solace_tls.env b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/solace_tls.env
new file mode 100644
index 00000000..cc90d8a8
--- /dev/null
+++ b/solace-spring-cloud-stream-binder/solace-spring-cloud-stream-binder/src/test/resources/oauth2/solace_tls.env
@@ -0,0 +1,5 @@
+username_admin_globalaccesslevel=admin
+username_admin_password=admin
+system_scaling_maxconnectioncount=1000
+tls_servercertificate_filepath=/run/secrets/server.pem
+webmanager_redirecthttp_enable=false