From 39febc560b09ae0c46a4e5e52d8a6ad27dbdeab4 Mon Sep 17 00:00:00 2001
From: mayur-solace <81177023+mayur-solace@users.noreply.github.com>
Date: Thu, 18 Jul 2024 11:26:11 -0400
Subject: [PATCH] Add OAuth2 support for authentication with Solace PubSub+
Broker. (#133)
* Add OAuth2 support for authentication with Solace PubSub+ Broker
* Disable overwrite settings in setup-java (#40)
* Updated dev docs.
* Fix action permissions (#41)
* fix manual test support install version
* Remove unwanted test dependency as requested in https://github.com/SolaceProducts/solace-spring-boot/issues/101
Use slf4j instead of apache logging
---------
Co-authored-by: Jeffrey D <11084623+Nephery@users.noreply.github.com>
---
.github/workflows/build-test.yml | 27 +-
README.md | 1 +
maven/settings.xml | 9 -
pom.xml | 21 +-
.../pom.xml | 55 +-
.../SolaceJavaAutoConfiguration.java | 15 +-
.../autoconfigure/SolaceJavaProperties.java | 17 +
.../SolaceOAuthClientConfiguration.java | 70 +
...efaultSolaceOAuth2SessionEventHandler.java | 73 +
...faultSolaceSessionOAuth2TokenProvider.java | 76 +
.../SolaceOAuth2SessionEventHandler.java | 17 +
.../SolaceSessionOAuth2TokenProvider.java | 16 +
.../jcsmp/SpringJCSMPFactory.java | 171 +-
.../it/util/semp/SempClientException.java | 135 +
.../config/BrokerConfiguratorBuilder.java | 869 +++++++
.../semp/monitor/BrokerMonitorBuilder.java | 374 +++
.../SolaceJavaAutoConfigurationTest.java | 17 +-
...okerTestContainerWithTlsAndOAuthSetup.java | 117 +
...eeTierBrokerTestContainerWithTlsSetup.java | 99 +
.../MessagingWithClientCertAuthIT.java | 109 +
.../springBootTests/MessagingWithOAuthIT.java | 287 +++
.../springBootTests/SampleApp.java | 14 +
...ltSolaceOAuth2SessionEventHandlerTest.java | 67 +
.../application-clientCertAuthIT.yml | 21 +
.../test/resources/application-oauthIT.yml | 27 +
.../src/test/resources/certs/README.txt | 96 +
.../test/resources/certs/broker/solbroker.crt | 28 +
.../test/resources/certs/broker/solbroker.csr | 20 +
.../test/resources/certs/broker/solbroker.key | 27 +
.../test/resources/certs/broker/solbroker.pem | 55 +
.../certs/client/client-keystore.jks | Bin 0 -> 2441 bytes
.../certs/client/client-truststore.p12 | Bin 0 -> 1814 bytes
.../test/resources/certs/client/client.crt | 25 +
.../test/resources/certs/client/client.csr | 17 +
.../test/resources/certs/client/client.key | 27 +
.../test/resources/certs/client/client.p12 | Bin 0 -> 2754 bytes
.../test/resources/certs/client/client.pem | 52 +
.../resources/certs/keycloak/keycloak.crt | 30 +
.../resources/certs/keycloak/keycloak.csr | 21 +
.../resources/certs/keycloak/keycloak.key | 27 +
.../resources/certs/keycloak/keycloak.pem | 57 +
.../test/resources/certs/keycloak_san.conf | 30 +
.../test/resources/certs/rootCA/rootCA.crt | 32 +
.../test/resources/certs/rootCA/rootCA.der | Bin 0 -> 1435 bytes
.../test/resources/certs/rootCA/rootCA.key | 51 +
.../test/resources/certs/rootCA/rootCA.pem | 83 +
.../test/resources/certs/rootCA/rootCA.srl | 1 +
.../test/resources/certs/solbroker_san.conf | 26 +
...oker-with-tls-and-oauth-docker-compose.yml | 52 +
...ee-tier-broker-with-tls-docker-compose.yml | 26 +
.../src/test/resources/logback-test.xml | 15 +
.../src/test/resources/oauth/README | 60 +
...uth-resource-server-role-realm-export.json | 2276 +++++++++++++++++
.../src/test/resources/oauth/nginx.conf | 39 +
.../src/test/resources/oauth/www/index.html | 6 +
.../src/test/resources/solace.env | 4 +
.../src/test/resources/solace_tls.env | 5 +
.../pom.xml | 10 +-
.../SolaceJmsAutoConfigurationTest.java | 16 +-
solace-spring-boot-bom/README.md | 7 +-
solace-spring-boot-bom/pom.xml | 4 +-
solace-spring-boot-parent/pom.xml | 61 +-
solace-spring-boot-samples/pom.xml | 2 +-
.../solace-java-oauth2-sample-app/README.md | 39 +
.../solace-java-oauth2-sample-app/pom.xml | 105 +
.../src/main/java/demo/DemoApplication.java | 137 +
.../main/java/demo/DemoMessageConsumer.java | 54 +
.../java/demo/DemoPublishEventHandler.java | 37 +
.../src/main/resources/application.yml | 32 +
.../solace-java-sample-app/pom.xml | 20 +-
.../solace-jms-sample-app-jndi/pom.xml | 20 +-
.../solace-jms-sample-app/pom.xml | 20 +-
.../solace-java-spring-boot-starter/README.md | 102 +-
.../solace-java-spring-boot-starter/pom.xml | 4 +-
.../solace-jms-spring-boot-starter/README.md | 4 +-
.../solace-jms-spring-boot-starter/pom.xml | 4 +-
.../solace-spring-boot-starter/pom.xml | 4 +-
77 files changed, 6373 insertions(+), 201 deletions(-)
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solace/spring/boot/autoconfigure/SolaceOAuthClientConfiguration.java
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/DefaultSolaceOAuth2SessionEventHandler.java
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/DefaultSolaceSessionOAuth2TokenProvider.java
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/SolaceOAuth2SessionEventHandler.java
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/SolaceSessionOAuth2TokenProvider.java
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/it/util/semp/SempClientException.java
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/it/util/semp/config/BrokerConfiguratorBuilder.java
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/it/util/semp/monitor/BrokerMonitorBuilder.java
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingServiceFreeTierBrokerTestContainerWithTlsAndOAuthSetup.java
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingServiceFreeTierBrokerTestContainerWithTlsSetup.java
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingWithClientCertAuthIT.java
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingWithOAuthIT.java
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/SampleApp.java
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solacesystems/jcsmp/DefaultSolaceOAuth2SessionEventHandlerTest.java
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/application-clientCertAuthIT.yml
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/application-oauthIT.yml
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/README.txt
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/broker/solbroker.crt
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/broker/solbroker.csr
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/broker/solbroker.key
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/broker/solbroker.pem
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client-keystore.jks
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client-truststore.p12
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client.crt
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client.csr
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client.key
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client.p12
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client.pem
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/keycloak/keycloak.crt
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/keycloak/keycloak.csr
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/keycloak/keycloak.key
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/keycloak/keycloak.pem
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/keycloak_san.conf
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.crt
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.der
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.key
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.pem
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.srl
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/solbroker_san.conf
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/free-tier-broker-with-tls-and-oauth-docker-compose.yml
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/free-tier-broker-with-tls-docker-compose.yml
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/logback-test.xml
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/oauth/README
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/oauth/keycloak/solace-oauth-resource-server-role-realm-export.json
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/oauth/nginx.conf
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/oauth/www/index.html
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/solace.env
create mode 100644 solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/solace_tls.env
create mode 100644 solace-spring-boot-samples/solace-java-oauth2-sample-app/README.md
create mode 100644 solace-spring-boot-samples/solace-java-oauth2-sample-app/pom.xml
create mode 100644 solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/java/demo/DemoApplication.java
create mode 100644 solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/java/demo/DemoMessageConsumer.java
create mode 100644 solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/java/demo/DemoPublishEventHandler.java
create mode 100644 solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/resources/application.yml
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index 9397fa8..87164ec 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -5,10 +5,23 @@ name: build
on:
pull_request:
push:
+ workflow_dispatch:
jobs:
- build:
+ dupe_check:
+ name: Check for Duplicate Workflow Run
+ runs-on: ubuntu-latest
+ outputs:
+ should_skip: ${{ steps.skip_check.outputs.should_skip }}
+ steps:
+ - id: skip_check
+ uses: fkirc/skip-duplicate-actions@v5.3.1
+ with:
+ concurrent_skipping: same_content
+ do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]'
+ build:
+ if: needs.dupe_check.outputs.should_skip != 'true'
runs-on: ubuntu-latest
steps:
@@ -18,7 +31,19 @@ jobs:
with:
distribution: 'zulu'
java-version: '17'
+ overwrite-settings: false
cache: 'maven'
+ - name: Manually Install Test Support If Necessary
+ if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
+ run: |
+ sudo apt-get update -qq
+ sudo apt-get install -y libxml2-utils
+ version="$(xmllint --xpath '/*[local-name()="project"]/*[local-name()="properties"]/*[local-name()="solace.integration.test.support.version"]/text()' pom.xml)"
+ echo "Detected test support version: ${version}"
+
+ git clone --depth 1 --branch "${version}" https://github.com/SolaceDev/solace-integration-test-support.git
+ cd "${GITHUB_WORKSPACE}/solace-integration-test-support"
+ mvn install -Dchangelist= -DskipTests
- name: Build and run Tests
run: mvn clean verify --settings "${GITHUB_WORKSPACE}/maven/settings.xml"
env:
diff --git a/README.md b/README.md
index 1f915c3..483328c 100644
--- a/README.md
+++ b/README.md
@@ -71,6 +71,7 @@ solace-spring-boot-build (root)
--> solace-java-sample-app
--> solace-jms-sample-app
--> solace-jms-sample-app-jndi
+ --> solace-java-oauth2-sample-app
Where:
<-- indicates the parent of the project
diff --git a/maven/settings.xml b/maven/settings.xml
index 90751f6..95753e3 100644
--- a/maven/settings.xml
+++ b/maven/settings.xml
@@ -29,14 +29,5 @@
${env.GITHUB_ACTOR}
${env.GITHUB_TOKEN}
-
- ossrh
- ${env.MAVEN_OSSRH_USER}
- ${env.MAVEN_OSSRH_PASS}
-
-
- gpg.passphrase
- ${env.GPG_PASSPHRASE}
-
diff --git a/pom.xml b/pom.xml
index fedf821..bf31f4e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.solace.spring.boot
solace-spring-boot-build
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
pom
Solace Spring Boot Build
@@ -15,12 +15,14 @@
SolaceProducts
- 3.0.6
+ 3.3.1
- 5.0.1-SNAPSHOT
+ 5.1.0-SNAPSHOT
- 5.0.1-SNAPSHOT
- 2.0.1-SNAPSHOT
+ 5.1.0-SNAPSHOT
+ 2.1.0-SNAPSHOT
+ 1.19.8
+ 1.1.2
@@ -54,6 +56,7 @@
solace-spring-boot-samples/solace-java-sample-app
solace-spring-boot-samples/solace-jms-sample-app
solace-spring-boot-samples/solace-jms-sample-app-jndi
+ solace-spring-boot-samples/solace-java-oauth2-sample-app
solace-spring-boot-starters/solace-java-spring-boot-starter
solace-spring-boot-starters/solace-jms-spring-boot-starter
solace-spring-boot-starters/solace-spring-boot-starter
@@ -85,6 +88,14 @@
solace-java-spring-boot-starter
${solace.spring.boot.java-starter.version}
+
+
+ com.solace.test.integration
+ solace-integration-test-support-bom
+ ${solace.integration.test.support.version}
+ pom
+ import
+
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/pom.xml b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/pom.xml
index 5665d26..4fa5c47 100644
--- a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/pom.xml
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/pom.xml
@@ -5,12 +5,12 @@
com.solace.spring.boot
solace-spring-boot-parent
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
../../solace-spring-boot-parent/pom.xml
solace-java-spring-boot-autoconfigure
- 5.0.1-SNAPSHOT
+ 5.1.0-SNAPSHOT
jar
Solace Spring Boot Autoconfiguration - Java
@@ -34,16 +34,61 @@
${solace.jcsmp.version}
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+ true
+
+
+
org.springframework.boot
spring-boot-starter-test
test
- com.github.stefanbirkner
- system-rules
- 1.19.0
+ org.springframework.boot
+ spring-boot-starter-web
+ test
+
+
+ org.junit-pioneer
+ junit-pioneer
+ 1.9.1
+ test
+
+
+ org.testcontainers
+ testcontainers
+ ${testcontainers.version}
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ ${testcontainers.version}
+ test
+
+
+ com.solace.test.integration
+ pubsubplus-junit-jupiter
+ ${solace.integration.test.support.version}
+ test
+
+
+ org.testcontainers
+ testcontainers
+ ${testcontainers.version}
test
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solace/spring/boot/autoconfigure/SolaceJavaAutoConfiguration.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solace/spring/boot/autoconfigure/SolaceJavaAutoConfiguration.java
index d56e188..f29399c 100644
--- a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solace/spring/boot/autoconfigure/SolaceJavaAutoConfiguration.java
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solace/spring/boot/autoconfigure/SolaceJavaAutoConfiguration.java
@@ -20,6 +20,7 @@
import com.solacesystems.jcsmp.JCSMPChannelProperties;
import com.solacesystems.jcsmp.JCSMPProperties;
+import com.solacesystems.jcsmp.SolaceSessionOAuth2TokenProvider;
import com.solacesystems.jcsmp.SpringJCSMPFactory;
import java.util.Map;
import java.util.Map.Entry;
@@ -33,12 +34,15 @@
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.lang.Nullable;
@Configuration
@AutoConfigureBefore(JmsAutoConfiguration.class)
@ConditionalOnClass({JCSMPProperties.class})
@ConditionalOnMissingBean(SpringJCSMPFactory.class)
@EnableConfigurationProperties(SolaceJavaProperties.class)
+@Import(SolaceOAuthClientConfiguration.class)
public class SolaceJavaAutoConfiguration {
private SolaceJavaProperties properties;
@@ -54,8 +58,9 @@ public SolaceJavaAutoConfiguration(SolaceJavaProperties properties) {
* @return {@link SpringJCSMPFactory} based on {@link JCSMPProperties} bean.
*/
@Bean
- public SpringJCSMPFactory getSpringJCSMPFactory(JCSMPProperties jcsmpProperties) {
- return new SpringJCSMPFactory(jcsmpProperties);
+ public SpringJCSMPFactory getSpringJCSMPFactory(JCSMPProperties jcsmpProperties,
+ @Nullable SolaceSessionOAuth2TokenProvider solaceSessionOAuth2TokenProvider) {
+ return new SpringJCSMPFactory(jcsmpProperties, solaceSessionOAuth2TokenProvider);
}
/**
@@ -87,6 +92,12 @@ public JCSMPProperties getJCSMPProperties() {
cp.setReconnectRetries(properties.getReconnectRetries());
cp.setConnectRetriesPerHost(properties.getConnectRetriesPerHost());
cp.setReconnectRetryWaitInMillis(properties.getReconnectRetryWaitInMillis());
+
+ if (properties.getOauth2ClientRegistrationId() != null) {
+ jcsmpProps.setProperty(SolaceJavaProperties.SPRING_OAUTH2_CLIENT_REGISTRATION_ID,
+ properties.getOauth2ClientRegistrationId());
+ }
+
return jcsmpProps;
}
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solace/spring/boot/autoconfigure/SolaceJavaProperties.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solace/spring/boot/autoconfigure/SolaceJavaProperties.java
index 2a380fc..b617401 100644
--- a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solace/spring/boot/autoconfigure/SolaceJavaProperties.java
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solace/spring/boot/autoconfigure/SolaceJavaProperties.java
@@ -27,6 +27,8 @@
@ConfigurationProperties("solace.java")
public class SolaceJavaProperties {
+ public static final String SPRING_OAUTH2_CLIENT_REGISTRATION_ID = "SPRING_OAUTH2_CLIENT_REGISTRATION_ID";
+
/**
* Solace Message Router Host address. Port is optional and intelligently defaulted by the Solace Java API.
*/
@@ -99,7 +101,22 @@ public class SolaceJavaProperties {
*/
private final Map apiProperties = new ConcurrentHashMap<>();
+ /**
+ * The Spring Security OAuth2 Client Registration Id
+ * spring.security.oauth2.client.registration.<registration-id>
to use for OAuth2
+ * token
+ * retrieval. This field is required when the Solace session is configured to use OAuth2 via
+ * solace.java.apiProperties.authentication_scheme=AUTHENTICATION_SCHEME_OAUTH2
+ */
+ private String oauth2ClientRegistrationId;
+
+ public String getOauth2ClientRegistrationId() {
+ return oauth2ClientRegistrationId;
+ }
+ public void setOauth2ClientRegistrationId(String oauth2ClientRegistrationId) {
+ this.oauth2ClientRegistrationId = oauth2ClientRegistrationId;
+ }
public String getHost() {
return host;
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solace/spring/boot/autoconfigure/SolaceOAuthClientConfiguration.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solace/spring/boot/autoconfigure/SolaceOAuthClientConfiguration.java
new file mode 100644
index 0000000..a96a62f
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solace/spring/boot/autoconfigure/SolaceOAuthClientConfiguration.java
@@ -0,0 +1,70 @@
+package com.solace.spring.boot.autoconfigure;
+
+import com.solacesystems.jcsmp.DefaultSolaceSessionOAuth2TokenProvider;
+import com.solacesystems.jcsmp.JCSMPProperties;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+
+/**
+ * Configuration class for Solace OAuth client. This configuration is only active when the
+ * 'solace.java.apiProperties.AUTHENTICATION_SCHEME' property is set to
+ * 'AUTHENTICATION_SCHEME_OAUTH2'.
+ */
+@Configuration
+@ConditionalOnProperty(prefix = "solace.java.apiProperties", name = "AUTHENTICATION_SCHEME",
+ havingValue = "AUTHENTICATION_SCHEME_OAUTH2")
+@Import(OAuth2ClientAutoConfiguration.class)
+public class SolaceOAuthClientConfiguration {
+
+ /**
+ * Creates and configures an OAuth2AuthorizedClientManager for Solace session. This manager is
+ * configured with OAuth2AuthorizedClientProvider for client credentials and refresh token.
+ *
+ * @param clientRegistrationRepository Repository of client registrations.
+ * @param oAuth2AuthorizedClientService Service for authorized OAuth2 clients.
+ * @return Configured OAuth2AuthorizedClientManager.
+ */
+ @Bean
+ public AuthorizedClientServiceOAuth2AuthorizedClientManager solaceOAuthAuthorizedClientServiceAndManager(
+ ClientRegistrationRepository clientRegistrationRepository,
+ OAuth2AuthorizedClientService oAuth2AuthorizedClientService) {
+ final OAuth2AuthorizedClientProvider clientCredentialsAuthorizedClientProvider =
+ OAuth2AuthorizedClientProviderBuilder.builder()
+ .authorizationCode()
+ .clientCredentials()
+ .refreshToken()
+ .build();
+
+ final AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
+ new AuthorizedClientServiceOAuth2AuthorizedClientManager(
+ clientRegistrationRepository, oAuth2AuthorizedClientService);
+ authorizedClientManager.setAuthorizedClientProvider(clientCredentialsAuthorizedClientProvider);
+
+ return authorizedClientManager;
+ }
+
+ /**
+ * Creates a SolaceSessionOAuth2TokenProvider for providing OAuth2 access tokens for Solace
+ * sessions.
+ *
+ * @param jcsmpProperties The JCSMP properties.
+ * @param solaceOAuthAuthorizedClientServiceAndManager The OAuth2AuthorizedClientManager for
+ * Solace session.
+ * @return Configured SolaceSessionOAuth2TokenProvider.
+ */
+ @Bean
+ public DefaultSolaceSessionOAuth2TokenProvider solaceSessionOAuth2TokenProvider(
+ JCSMPProperties jcsmpProperties,
+ AuthorizedClientServiceOAuth2AuthorizedClientManager solaceOAuthAuthorizedClientServiceAndManager) {
+ return new DefaultSolaceSessionOAuth2TokenProvider(jcsmpProperties,
+ solaceOAuthAuthorizedClientServiceAndManager);
+ }
+}
\ No newline at end of file
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/DefaultSolaceOAuth2SessionEventHandler.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/DefaultSolaceOAuth2SessionEventHandler.java
new file mode 100644
index 0000000..40ba2e8
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/DefaultSolaceOAuth2SessionEventHandler.java
@@ -0,0 +1,73 @@
+package com.solacesystems.jcsmp;
+
+import static com.solacesystems.jcsmp.JCSMPProperties.AUTHENTICATION_SCHEME;
+import static com.solacesystems.jcsmp.JCSMPProperties.AUTHENTICATION_SCHEME_OAUTH2;
+import static com.solacesystems.jcsmp.SessionEvent.RECONNECTING;
+import java.util.Objects;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Default implementation of SolaceOAuth2SessionEventHandler. This class handles the OAuth2 token
+ * refresh logic when the session is reconnecting.
+ */
+public class DefaultSolaceOAuth2SessionEventHandler implements SolaceOAuth2SessionEventHandler {
+
+ private static final Logger logger = LoggerFactory.getLogger(DefaultSolaceOAuth2SessionEventHandler.class);
+
+ protected final SolaceSessionOAuth2TokenProvider solaceSessionOAuth2TokenProvider;
+ protected final JCSMPProperties jcsmpProperties;
+ protected JCSMPSession jcsmpSession;
+
+ /**
+ * Constructs a new DefaultSolaceOAuth2SessionEventHandler with the provided JCSMP properties and
+ * OAuth2 token provider.
+ *
+ * @param jcsmpProperties The JCSMP properties.
+ * @param solaceSessionOAuth2TokenProvider The OAuth2 token provider.
+ */
+ public DefaultSolaceOAuth2SessionEventHandler(JCSMPProperties jcsmpProperties,
+ SolaceSessionOAuth2TokenProvider solaceSessionOAuth2TokenProvider) {
+ this.jcsmpProperties = jcsmpProperties;
+ this.solaceSessionOAuth2TokenProvider = solaceSessionOAuth2TokenProvider;
+
+ Objects.requireNonNull(jcsmpProperties);
+ if (isAuthSchemeOAuth2()) {
+ Objects.requireNonNull(solaceSessionOAuth2TokenProvider);
+ }
+ }
+
+ @Override
+ public void handleEvent(SessionEventArgs sessionEventArgs) {
+ final SessionEvent event = sessionEventArgs.getEvent();
+ if (event == RECONNECTING && isAuthSchemeOAuth2()) {
+ refreshOAuth2AccessToken();
+ }
+ }
+
+ protected boolean isAuthSchemeOAuth2() {
+ return AUTHENTICATION_SCHEME_OAUTH2.equalsIgnoreCase(
+ jcsmpProperties.getStringProperty(AUTHENTICATION_SCHEME));
+ }
+
+ private void refreshOAuth2AccessToken() {
+ try {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Refreshing OAuth2 access token");
+ }
+
+ final String newAccessToken = solaceSessionOAuth2TokenProvider.getAccessToken();
+ this.jcsmpSession.setProperty(JCSMPProperties.OAUTH2_ACCESS_TOKEN, newAccessToken);
+ } catch (JCSMPException e) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Exception while fetching/providing refreshed access token: ", e);
+ }
+ }
+ }
+
+ @Override
+ public void setJcsmpSession(JCSMPSession jcsmpSession) {
+ this.jcsmpSession = jcsmpSession;
+ }
+}
\ No newline at end of file
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/DefaultSolaceSessionOAuth2TokenProvider.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/DefaultSolaceSessionOAuth2TokenProvider.java
new file mode 100644
index 0000000..5ee5809
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/DefaultSolaceSessionOAuth2TokenProvider.java
@@ -0,0 +1,76 @@
+package com.solacesystems.jcsmp;
+
+import static com.solacesystems.jcsmp.JCSMPProperties.USERNAME;
+import com.solace.spring.boot.autoconfigure.SolaceJavaProperties;
+import java.util.Objects;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager;
+import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+
+/**
+ * Default implementation of SolaceSessionOAuth2TokenProvider. This class fetches and returns the
+ * current OAuth2 access token using the provided JCSMP properties and OAuth2 authorized client
+ * manager.
+ */
+public class DefaultSolaceSessionOAuth2TokenProvider implements SolaceSessionOAuth2TokenProvider {
+
+ private static final Logger logger = LoggerFactory.getLogger(
+ DefaultSolaceSessionOAuth2TokenProvider.class);
+
+ private final JCSMPProperties jcsmpProperties;
+ private final AuthorizedClientServiceOAuth2AuthorizedClientManager solaceOAuthAuthorizedClientServiceAndManager;
+
+ /**
+ * Constructs a new DefaultSolaceSessionOAuth2TokenProvider with the provided JCSMP properties and
+ * OAuth2 authorized client manager.
+ *
+ * @param jcsmpProperties The JCSMP properties.
+ * @param solaceOAuthAuthorizedClientServiceAndManager The OAuth2 authorized client manager.
+ */
+ public DefaultSolaceSessionOAuth2TokenProvider(JCSMPProperties jcsmpProperties,
+ AuthorizedClientServiceOAuth2AuthorizedClientManager solaceOAuthAuthorizedClientServiceAndManager) {
+ Objects.requireNonNull(jcsmpProperties);
+ Objects.requireNonNull(solaceOAuthAuthorizedClientServiceAndManager);
+ this.jcsmpProperties = jcsmpProperties;
+ this.solaceOAuthAuthorizedClientServiceAndManager = solaceOAuthAuthorizedClientServiceAndManager;
+ }
+
+ @Override
+ public String getAccessToken() {
+ try {
+ final String clientUserName = Objects.toString(
+ jcsmpProperties.getStringProperty(USERNAME), "spring-default-client-username");
+ final String oauth2ClientRegistrationId = jcsmpProperties
+ .getStringProperty(SolaceJavaProperties.SPRING_OAUTH2_CLIENT_REGISTRATION_ID);
+
+ if (logger.isInfoEnabled()) {
+ logger.info(String.format("Fetching OAuth2 access token using client registration ID: %s",
+ oauth2ClientRegistrationId));
+ }
+
+ final OAuth2AuthorizeRequest authorizeRequest =
+ OAuth2AuthorizeRequest.withClientRegistrationId(oauth2ClientRegistrationId)
+ .principal(clientUserName)
+ .build();
+
+ //Perform the actual authorization request using the authorized client service and authorized
+ //client manager. This is where the JWT is retrieved from the OAuth/OIDC servers.
+ final OAuth2AuthorizedClient oAuth2AuthorizedClient =
+ solaceOAuthAuthorizedClientServiceAndManager.authorize(authorizeRequest);
+
+ //Get the token from the authorized client object
+ final OAuth2AccessToken accessToken = Objects.requireNonNull(oAuth2AuthorizedClient)
+ .getAccessToken();
+
+ return accessToken.getTokenValue();
+ } catch (Throwable t) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Exception while fetching OAuth2 access token.", t);
+ }
+ throw t;
+ }
+ }
+}
\ No newline at end of file
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/SolaceOAuth2SessionEventHandler.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/SolaceOAuth2SessionEventHandler.java
new file mode 100644
index 0000000..5ff0bff
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/SolaceOAuth2SessionEventHandler.java
@@ -0,0 +1,17 @@
+package com.solacesystems.jcsmp;
+
+/**
+ * The JCSMP {@link SessionEventHandler} when OAuth2 authentication scheme is being used.
+ * Implementing classes should handle the OAuth2 token refresh logic when the session is
+ * reconnecting. Refer {@link DefaultSolaceOAuth2SessionEventHandler} for the default
+ * implementation.
+ */
+public interface SolaceOAuth2SessionEventHandler extends SessionEventHandler {
+
+ /**
+ * Sets the JCSMP session associated with this event handler.
+ *
+ * @param jcsmpSession The JCSMP session associated with this event handler.
+ */
+ void setJcsmpSession(JCSMPSession jcsmpSession);
+}
\ No newline at end of file
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/SolaceSessionOAuth2TokenProvider.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/SolaceSessionOAuth2TokenProvider.java
new file mode 100644
index 0000000..bd3f37e
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/SolaceSessionOAuth2TokenProvider.java
@@ -0,0 +1,16 @@
+package com.solacesystems.jcsmp;
+
+/**
+ * Interface for providing OAuth2 access tokens for Solace sessions. Implementing classes should
+ * provide a method to fetch and return the current OAuth2 access token. Refer
+ * {@link DefaultSolaceSessionOAuth2TokenProvider} for a default implementation.
+ */
+public interface SolaceSessionOAuth2TokenProvider {
+
+ /**
+ * Fetches and returns the current OAuth2 access token.
+ *
+ * @return The current OAuth2 access token.
+ */
+ String getAccessToken();
+}
\ No newline at end of file
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/SpringJCSMPFactory.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/SpringJCSMPFactory.java
index 25c8423..4d54b2c 100644
--- a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/SpringJCSMPFactory.java
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/SpringJCSMPFactory.java
@@ -19,88 +19,115 @@
package com.solacesystems.jcsmp;
+import static com.solacesystems.jcsmp.JCSMPProperties.AUTHENTICATION_SCHEME;
+import org.springframework.lang.Nullable;
+
/**
- * Wrapper of JCSMP Singleton Factory to more easily work within Spring Auto Configuration environments.
+ * Wrapper of JCSMP Singleton Factory to more easily work within Spring Auto Configuration
+ * environments.
*/
public class SpringJCSMPFactory {
-
- protected JCSMPProperties jcsmpProperties;
- public SpringJCSMPFactory(JCSMPProperties properties) {
- this.jcsmpProperties = (JCSMPProperties)properties.clone();
- }
+ protected JCSMPProperties jcsmpProperties;
+ protected SolaceSessionOAuth2TokenProvider solaceSessionOAuth2TokenProvider;
+ public SpringJCSMPFactory(JCSMPProperties properties,
+ @Nullable SolaceSessionOAuth2TokenProvider solaceSessionOAuth2TokenProvider) {
+ this.jcsmpProperties = (JCSMPProperties) properties.clone();
+ this.solaceSessionOAuth2TokenProvider = solaceSessionOAuth2TokenProvider;
+ }
- /**
- * Acquires a {@link JCSMPSession} implementation for the specified
- * properties in the default Context
.
- *
- * @return A {@link JCSMPSession} implementation with the specified
- * properties.
- * @throws InvalidPropertiesException
- * Thrown if the required properties are not provided, or if
- * unsupported properties (and combinations) are detected.
- */
- public JCSMPSession createSession() throws InvalidPropertiesException {
- return JCSMPFactory.onlyInstance().createSession(jcsmpProperties);
- }
- /**
- * Acquires a {@link JCSMPSession} and associates it to the given
- * {@link Context}.
- *
- * @param context
- * The Context
in which the new session will be
- * created and associated with. If null
, the
- * default context is used.
- * @return A newly constructed session in context
.
- * @throws InvalidPropertiesException
- * on error
- */
- public JCSMPSession createSession(Context context) throws InvalidPropertiesException {
- return JCSMPFactory.onlyInstance().createSession(jcsmpProperties, context);
- }
+ /**
+ * Acquires a {@link JCSMPSession} implementation for the specified properties in the default
+ * Context
.
+ *
+ * @return A {@link JCSMPSession} implementation with the specified properties.
+ * @throws InvalidPropertiesException Thrown if the required properties are not provided, or if
+ * unsupported properties (and combinations) are detected.
+ */
+ public JCSMPSession createSession() throws InvalidPropertiesException {
+ return createSession(null);
+ }
- /**
- * Acquires a {@link JCSMPSession} and associates it to the given
- * {@link Context}.
- *
- * @param context
- * The Context
in which the new session will be
- * created and associated with. If null
, uses the
- * default context.
- * @param eventHandler
- * A callback instance for handling session events.
- * @return A newly constructed session in the context
Context.
- * @throws InvalidPropertiesException
- * on error
- */
- public JCSMPSession createSession(
- Context context,
- SessionEventHandler eventHandler) throws InvalidPropertiesException {
- return JCSMPFactory.onlyInstance().createSession(jcsmpProperties, context, eventHandler);
- }
+ /**
+ * Acquires a {@link JCSMPSession} and associates it to the given {@link Context}.
+ *
+ * @param context The Context
in which the new session will be created and associated
+ * with. If null
, the default context is used.
+ * @return A newly constructed session in context
.
+ * @throws InvalidPropertiesException on error
+ */
+ public JCSMPSession createSession(Context context) throws InvalidPropertiesException {
+ return createSession(context, null);
+ }
- /* CONTEXT OPERATIONS */
- /**
- * Returns a reference to the default Context
. There is a
- * single instance of a default context in the API.
- *
- * @return The default Context
instance.
- */
- public Context getDefaultContext() {
- return JCSMPFactory.onlyInstance().getDefaultContext();
+ /**
+ * Acquires a {@link JCSMPSession} and associates it to the given {@link Context}.
+ * If the authentication scheme is OAuth2, it fetches and sets the initial OAuth2 token.
+ * If the event handler is null, it creates a new session event handler that will handle OAuth2 token refreshes.
+ *
+ * @param context The Context
in which the new session will be created and
+ * associated with. If null
, uses the default context.
+ * @param eventHandler A callback instance for handling session events.
+ * @return A newly constructed session in the context
Context.
+ * @throws InvalidPropertiesException on error
+ */
+ public JCSMPSession createSession(
+ Context context,
+ SessionEventHandler eventHandler) throws InvalidPropertiesException {
+ final String authScheme = jcsmpProperties.getStringProperty(AUTHENTICATION_SCHEME);
+ if (JCSMPProperties.AUTHENTICATION_SCHEME_OAUTH2.equalsIgnoreCase(authScheme)) {
+ return createSessionWithOAuth2(context, eventHandler);
+ } else {
+ return JCSMPFactory.onlyInstance().createSession(jcsmpProperties, context, eventHandler);
}
+ }
- /**
- * Creates a new Context
with the provided properties.
- *
- * @param properties
- * Configuration settings for the new Context
. If
- * null
, the default configuration settings are used.
- * @return Newly-created Context
instance.
- */
- public Context createContext(ContextProperties properties) {
- return JCSMPFactory.onlyInstance().createContext(properties);
+ private JCSMPSession createSessionWithOAuth2(Context context,
+ SessionEventHandler eventHandler) throws InvalidPropertiesException {
+ if (eventHandler != null && !(eventHandler instanceof SolaceOAuth2SessionEventHandler)) {
+ throw new IllegalArgumentException(String.format(
+ "Event handler must be an instance of %s when using OAuth2 authentication scheme.",
+ SolaceOAuth2SessionEventHandler.class.getName()));
}
+
+ //A JCSMP SessionEventHandler, to handle OAuth2 token refreshes
+ final SolaceOAuth2SessionEventHandler solaceOAuth2SessionEventHandler =
+ eventHandler != null ? (SolaceOAuth2SessionEventHandler) eventHandler
+ : new DefaultSolaceOAuth2SessionEventHandler(this.jcsmpProperties,
+ this.solaceSessionOAuth2TokenProvider);
+
+ //Fetch and set the initial OAuth2 token
+ final String accessToken = this.solaceSessionOAuth2TokenProvider.getAccessToken();
+ this.jcsmpProperties.setProperty(JCSMPProperties.OAUTH2_ACCESS_TOKEN, accessToken);
+
+ final JCSMPSession jcsmpSession = JCSMPFactory.onlyInstance()
+ .createSession(this.jcsmpProperties, context, solaceOAuth2SessionEventHandler);
+ //inject the JCSMP Session into the event handler
+ solaceOAuth2SessionEventHandler.setJcsmpSession(jcsmpSession);
+ return jcsmpSession;
+ }
+
+ /* CONTEXT OPERATIONS */
+ /**
+ * Returns a reference to the default Context
. There is a single instance of a
+ * default context in the API.
+ *
+ * @return The default Context
instance.
+ */
+ public Context getDefaultContext() {
+ return JCSMPFactory.onlyInstance().getDefaultContext();
+ }
+
+ /**
+ * Creates a new Context
with the provided properties.
+ *
+ * @param properties Configuration settings for the new Context
. If
+ * null
, the default configuration settings are used.
+ * @return Newly-created Context
instance.
+ */
+ public Context createContext(ContextProperties properties) {
+ return JCSMPFactory.onlyInstance().createContext(properties);
+ }
}
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/it/util/semp/SempClientException.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/it/util/semp/SempClientException.java
new file mode 100644
index 0000000..3a475da
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/it/util/semp/config/BrokerConfiguratorBuilder.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/it/util/semp/config/BrokerConfiguratorBuilder.java
new file mode 100644
index 0000000..b85a406
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/it/util/semp/config/BrokerConfiguratorBuilder.java
@@ -0,0 +1,869 @@
+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.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.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 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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/it/util/semp/monitor/BrokerMonitorBuilder.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/it/util/semp/monitor/BrokerMonitorBuilder.java
new file mode 100644
index 0000000..71e8dfb
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/SolaceJavaAutoConfigurationTest.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/SolaceJavaAutoConfigurationTest.java
index 94cb427..f87644b 100644
--- a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/SolaceJavaAutoConfigurationTest.java
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/SolaceJavaAutoConfigurationTest.java
@@ -25,31 +25,28 @@
import com.solacesystems.jcsmp.JCSMPProperties;
import com.solacesystems.jcsmp.JCSMPSession;
import com.solacesystems.jcsmp.SpringJCSMPFactory;
-import org.junit.After;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-@RunWith(SpringJUnit4ClassRunner.class)
-public class SolaceJavaAutoConfigurationTest {
+class SolaceJavaAutoConfigurationTest {
@Configuration public static class EmptyConfiguration { }
private AnnotationConfigApplicationContext context;
private final Class configClass = SolaceJavaAutoConfiguration.class;
- @After
- public void tearDown() {
+ @AfterEach
+ void tearDown() {
if (this.context != null) {
this.context.close();
}
}
@Test
- public void defaultNativeConnectionFactory() throws InvalidPropertiesException {
+ void defaultNativeConnectionFactory() throws InvalidPropertiesException {
load("");
SpringJCSMPFactory jcsmpFactory = this.context.getBean(SpringJCSMPFactory.class);
assertNotNull(jcsmpFactory);
@@ -77,7 +74,7 @@ public void defaultNativeConnectionFactory() throws InvalidPropertiesException {
}
@Test
- public void customNativeConnectionFactory() throws InvalidPropertiesException {
+ void customNativeConnectionFactory() throws InvalidPropertiesException {
load("solace.java.host=192.168.1.80:55500",
"solace.java.clientUsername=bob", "solace.java.clientPassword=password",
"solace.java.msgVpn=newVpn", "solace.java.clientName=client-name",
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingServiceFreeTierBrokerTestContainerWithTlsAndOAuthSetup.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingServiceFreeTierBrokerTestContainerWithTlsAndOAuthSetup.java
new file mode 100644
index 0000000..3585d71
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingServiceFreeTierBrokerTestContainerWithTlsAndOAuthSetup.java
@@ -0,0 +1,117 @@
+package com.solace.spring.boot.autoconfigure.springBootTests;
+
+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/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/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingServiceFreeTierBrokerTestContainerWithTlsSetup.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingServiceFreeTierBrokerTestContainerWithTlsSetup.java
new file mode 100644
index 0000000..9f1d47c
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingServiceFreeTierBrokerTestContainerWithTlsSetup.java
@@ -0,0 +1,99 @@
+package com.solace.spring.boot.autoconfigure.springBootTests;
+
+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 MessagingServiceFreeTierBrokerTestContainerWithTlsSetup {
+
+ String FULL_DOCKER_COMPOSE_FILE_PATH = "src/test/resources/free-tier-broker-with-tls-docker-compose.yml";
+ String PUBSUB_BROKER_SERVICE_NAME = "solbroker";
+
+ Logger LOGGER = LoggerFactory.getLogger(
+ MessagingServiceFreeTierBrokerTestContainerWithTlsSetup.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)
+
+ .waitingFor(PUBSUB_BROKER_SERVICE_NAME,
+ Wait.forHttp("/").forPort(8080).withStartupTimeout(Duration.ofSeconds(120)));
+
+ @BeforeAll
+ static void startContainer() {
+ System.setProperty("javax.net.ssl.trustStore",
+ new File("src/test/resources/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");
+ }
+
+ @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",
+ "clientCertAuth_test_final_debug.log");
+ containerState.copyFileFromContainer("/usr/sw/jail/logs/event.log",
+ "clientCertAuth_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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingWithClientCertAuthIT.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingWithClientCertAuthIT.java
new file mode 100644
index 0000000..345bb6a
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingWithClientCertAuthIT.java
@@ -0,0 +1,109 @@
+package com.solace.spring.boot.autoconfigure.springBootTests;
+
+import static org.junit.jupiter.api.Assertions.fail;
+import com.solace.it.util.semp.config.BrokerConfiguratorBuilder;
+import com.solace.it.util.semp.config.BrokerConfiguratorBuilder.BrokerConfigurator;
+import com.solace.test.integration.semp.v2.SempV2Api;
+import com.solacesystems.jcsmp.JCSMPProperties;
+import com.solacesystems.jcsmp.JCSMPSession;
+import com.solacesystems.jcsmp.SpringJCSMPFactory;
+import java.io.File;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import org.assertj.core.util.Files;
+import org.junit.jupiter.api.BeforeAll;
+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.context.ActiveProfiles;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+
+@SpringBootTest(
+ classes = SampleApp.class,
+ webEnvironment = WebEnvironment.RANDOM_PORT
+)
+@AutoConfigureMockMvc()
+@ActiveProfiles("clientCertAuthIT")
+class MessagingWithClientCertAuthIT implements
+ MessagingServiceFreeTierBrokerTestContainerWithTlsSetup {
+
+ private static final Logger logger = LoggerFactory.getLogger(MessagingWithClientCertAuthIT.class);
+ private static BrokerConfigurator solaceConfigUtil;
+ final static String MSG_VPN_DEFAULT = "default";
+
+ @Autowired
+ SpringJCSMPFactory springJCSMPFactory;
+
+ @Autowired
+ JCSMPProperties jcsmpProperties;
+
+ @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("solace.java.host",
+ () -> String.format("tcps://%s:%s", solaceHost, solaceSecureSMFPort));
+
+ registry.add("solace.java.apiProperties.SSL_TRUST_STORE",
+ () -> new File("src/test/resources/certs/client/client-truststore.p12").getAbsolutePath());
+ registry.add("solace.java.apiProperties.SSL_KEY_STORE",
+ () -> new File("src/test/resources/certs/client/client-keystore.jks").getAbsolutePath());
+ }
+
+ @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 sempV2Api = new SempV2Api(sempUrl, "admin", "admin");
+ solaceConfigUtil = BrokerConfiguratorBuilder.create(sempV2Api).build();
+
+ logger.debug("Prepare to upload CA cert to the broker");
+ final URL resource = MessagingWithClientCertAuthIT.class.getClassLoader()
+ .getResource("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");
+ }
+
+ //Enable client certificate authentication on the Solace PubSub+ Broker
+ solaceConfigUtil.vpns().enableClientCertAuth(MSG_VPN_DEFAULT);
+ } catch (URISyntaxException e) {
+ fail(e);
+ }
+ }
+
+ private boolean isClientCertificateAuthentication() {
+ return JCSMPProperties.AUTHENTICATION_SCHEME_CLIENT_CERTIFICATE.equalsIgnoreCase(
+ jcsmpProperties.getStringProperty(JCSMPProperties.AUTHENTICATION_SCHEME));
+ }
+
+ @Test
+ void canConnectWhenAuthenticationSchemeIsClientCertificate() {
+ if (!isClientCertificateAuthentication()) {
+ fail("Was expecting the authentication scheme to be client certificate.");
+ }
+
+ try {
+ JCSMPSession jcsmpSession = springJCSMPFactory.createSession();
+ jcsmpSession.connect();
+ logger.info("Session connected successfully.");
+ jcsmpSession.closeSession();
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingWithOAuthIT.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingWithOAuthIT.java
new file mode 100644
index 0000000..47c3c90
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingWithOAuthIT.java
@@ -0,0 +1,287 @@
+package com.solace.spring.boot.autoconfigure.springBootTests;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.fail;
+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.config.model.ConfigMsgVpnAuthorizationGroup;
+import com.solace.test.integration.semp.v2.monitor.model.MonitorMsgVpnClient;
+import com.solacesystems.jcsmp.DefaultSolaceOAuth2SessionEventHandler;
+import com.solacesystems.jcsmp.JCSMPProperties;
+import com.solacesystems.jcsmp.JCSMPSession;
+import com.solacesystems.jcsmp.SessionEventArgs;
+import com.solacesystems.jcsmp.SessionEventHandler;
+import com.solacesystems.jcsmp.SolaceSessionOAuth2TokenProvider;
+import com.solacesystems.jcsmp.SpringJCSMPFactory;
+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.Tag;
+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.context.ActiveProfiles;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+
+@SpringBootTest(
+ classes = SampleApp.class,
+ webEnvironment = WebEnvironment.RANDOM_PORT
+)
+@AutoConfigureMockMvc()
+@ActiveProfiles("oauthIT")
+class MessagingWithOAuthIT implements
+ MessagingServiceFreeTierBrokerTestContainerWithTlsAndOAuthSetup {
+
+ private static final Logger logger = LoggerFactory.getLogger(MessagingWithOAuthIT.class);
+ private static BrokerConfigurator solaceConfigUtil;
+ private static BrokerMonitor solaceMonitorUtil;
+ private static SempV2Api sempV2Api;
+
+ final static String OAUTH_PROFILE_NAME = "SolaceOauthResourceServer";
+ final static String AUTHORIZATION_GROUP_NAME = "solclient_oauth_auth_group";
+ final static String MSG_VPN_DEFAULT = "default";
+
+ @Autowired
+ SpringJCSMPFactory springJCSMPFactory;
+
+ @Autowired
+ JCSMPProperties jcsmpProperties;
+
+ @Autowired
+ SolaceSessionOAuth2TokenProvider solaceSessionOAuth2TokenProvider;
+
+ @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("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.my-auth-server.token-uri",
+ () -> String.format(
+ "https://%s:%s/auth/realms/solace-oauth-resource-server-role/protocol/openid-connect/token",
+ nginxHost, nginxSecurePort));
+ }
+
+ @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();
+
+ logger.debug("Prepare to upload CA cert to the broker");
+ final URL resource = MessagingWithOAuthIT.class.getClassLoader()
+ .getResource("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_DEFAULT);
+ } catch (URISyntaxException e) {
+ fail(e);
+ }
+ }
+
+ private static void deleteOAuthSetup(String msgVpnName) {
+ solaceConfigUtil.vpns().disableOAuthAuth(msgVpnName);
+ solaceConfigUtil.vpns().deleteOAuthProfile(msgVpnName, OAUTH_PROFILE_NAME);
+ solaceConfigUtil.vpns().deleteAuthorizationGroup(msgVpnName, AUTHORIZATION_GROUP_NAME);
+ }
+
+ private static void setupOAuth(String msgVpnName) {
+ solaceConfigUtil.vpns().enableOAuthAuth(msgVpnName);
+ solaceConfigUtil.vpns().createOAuthProfile(msgVpnName, oAuthProfileResourceServer());
+ solaceConfigUtil.vpns().createAuthorizationGroup(msgVpnName, authorizationGroup1());
+ }
+
+ private static ConfigMsgVpnAuthenticationOauthProfile oAuthProfileResourceServer() {
+ final String AUTHORIZATION_GROUP_CLAIM_NAME = "";
+ final String ENDPOINT_JWKS = "https://solaceoauth:10443/auth/realms/solace-oauth-resource-server-role/protocol/openid-connect/certs";
+ final String ENDPOINT_USERINFO = "https://solaceoauth:10443/auth/realms/solace-oauth-resource-server-role/protocol/openid-connect/userinfo";
+ final String REALM2_ISSUER_IDENTIFIER = "https://solaceoauth:10443/auth/realms/solace-oauth-resource-server-role";
+
+ 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);
+ }
+
+ private static ConfigMsgVpnAuthorizationGroup authorizationGroup1() {
+ return new ConfigMsgVpnAuthorizationGroup()
+ .authorizationGroupName(AUTHORIZATION_GROUP_NAME)
+ .enabled(true)
+ .aclProfileName("default")
+ .clientProfileName("default");
+ }
+
+ private boolean isOAuth2() {
+ return JCSMPProperties.AUTHENTICATION_SCHEME_OAUTH2.equalsIgnoreCase(
+ jcsmpProperties.getStringProperty(JCSMPProperties.AUTHENTICATION_SCHEME));
+ }
+
+ @Test
+ void canConnectWhenAuthenticationSchemeIsOAuth2() {
+ if (!isOAuth2()) {
+ fail("Was expecting the authentication scheme to be OAuth2");
+ }
+
+ try {
+ JCSMPSession jcsmpSession = springJCSMPFactory.createSession();
+ jcsmpSession.connect();
+ logger.info("Session connected successfully.");
+ jcsmpSession.closeSession();
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+
+ @Test
+ @Tag("SLOW")
+ void canRefreshTokenWhenAuthenticationSchemeIsOAuth2() {
+ if (!isOAuth2()) {
+ fail("Was expecting the authentication scheme to be OAuth2");
+ }
+
+ try {
+ CountDownLatch refreshedTokenLatch = new CountDownLatch(1);
+ SessionEventHandler sessionEventHandler = new DefaultSolaceOAuth2SessionEventHandler(
+ jcsmpProperties, solaceSessionOAuth2TokenProvider) {
+ @Override
+ public void handleEvent(SessionEventArgs sessionEventArgs) {
+ super.handleEvent(sessionEventArgs);
+ logger.info("Token refreshed successfully.");
+ refreshedTokenLatch.countDown();
+ }
+ };
+
+ JCSMPSession jcsmpSession = springJCSMPFactory.createSession(
+ springJCSMPFactory.getDefaultContext(), sessionEventHandler);
+ jcsmpSession.connect();
+ logger.info("Session connected successfully.");
+ logger.info("Wait for session reconnect, to refresh token. Will take about 1 minute.");
+ boolean success = refreshedTokenLatch.await(3, TimeUnit.MINUTES);
+ if (!success) {
+ fail("Timed out waiting for token refresh");
+ }
+ jcsmpSession.closeSession();
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ @Test
+ @Tag("SLOW")
+ void canRefreshTokenWhenForceReconnect() {
+ if (!isOAuth2()) {
+ fail("Was expecting the authentication scheme to be OAuth2");
+ }
+
+ try {
+ int numberOfReconnects = 10;
+ CountDownLatch refreshedTokenLatch = new CountDownLatch(numberOfReconnects);
+ SessionEventHandler sessionEventHandler = new DefaultSolaceOAuth2SessionEventHandler(
+ jcsmpProperties, solaceSessionOAuth2TokenProvider) {
+ @Override
+ public void handleEvent(SessionEventArgs sessionEventArgs) {
+ super.handleEvent(sessionEventArgs);
+ logger.info("Token refreshed successfully.");
+ refreshedTokenLatch.countDown();
+ }
+ };
+
+ JCSMPSession jcsmpSession = springJCSMPFactory.createSession(
+ springJCSMPFactory.getDefaultContext(), sessionEventHandler);
+ jcsmpSession.connect();
+ logger.info("Session connected successfully.");
+
+ AtomicBoolean failed = new AtomicBoolean(false);
+ Thread t = new Thread(() -> {
+ for (int i = 0; i < numberOfReconnects; i++) {
+ try {
+ Thread.sleep(10_000);
+ MonitorMsgVpnClient msgVpnClient = solaceMonitorUtil.vpnClients()
+ .queryVpnClients(MSG_VPN_DEFAULT)
+ .stream()
+ .filter(client -> client.getClientUsername().startsWith("default"))
+ .findFirst().orElse(null);
+
+ if (msgVpnClient == null) {
+ throw new RuntimeException("Client not found");
+ }
+
+ try {
+ logger.info("Forcing Session Reconnect for client: {}", msgVpnClient.getClientName());
+ sempV2Api.action()
+ .doMsgVpnClientDisconnect(MSG_VPN_DEFAULT, msgVpnClient.getClientName(),
+ new ActionMsgVpnClientDisconnect());
+ } catch (ApiException e) {
+ throw new RuntimeException(e);
+ }
+ } catch (Exception e) {
+ failed.set(true);
+ return;
+ }
+ }
+ });
+
+ t.start();
+
+ logger.info("Wait for session reconnect, to refresh token");
+ boolean success = refreshedTokenLatch.await(3, TimeUnit.MINUTES);
+ if (!success) {
+ fail("Timed out waiting for token refresh");
+ }
+
+ jcsmpSession.closeSession();
+
+ assertFalse(failed.get(), "Failed to force reconnect");
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/SampleApp.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/SampleApp.java
new file mode 100644
index 0000000..2db89d4
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/SampleApp.java
@@ -0,0 +1,14 @@
+package com.solace.spring.boot.autoconfigure.springBootTests;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+
+@SpringBootApplication
+@EnableWebSecurity
+public class SampleApp {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SampleApp.class, args);
+ }
+}
\ No newline at end of file
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solacesystems/jcsmp/DefaultSolaceOAuth2SessionEventHandlerTest.java b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solacesystems/jcsmp/DefaultSolaceOAuth2SessionEventHandlerTest.java
new file mode 100644
index 0000000..bc8a786
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solacesystems/jcsmp/DefaultSolaceOAuth2SessionEventHandlerTest.java
@@ -0,0 +1,67 @@
+package com.solacesystems.jcsmp;
+
+import static com.solacesystems.jcsmp.JCSMPProperties.OAUTH2_ACCESS_TOKEN;
+import static com.solacesystems.jcsmp.SessionEvent.DOWN_ERROR;
+import static com.solacesystems.jcsmp.SessionEvent.RECONNECTING;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+public class DefaultSolaceOAuth2SessionEventHandlerTest {
+
+ private SolaceSessionOAuth2TokenProvider mockTokenProvider;
+ private JCSMPSession mockSession;
+
+ private DefaultSolaceOAuth2SessionEventHandler eventHandler;
+
+ @BeforeEach
+ void setUp() {
+ mockTokenProvider = Mockito.mock(SolaceSessionOAuth2TokenProvider.class);
+ mockSession = Mockito.mock(JCSMPSession.class);
+ JCSMPProperties jcsmpProperties = new JCSMPProperties();
+ jcsmpProperties.setProperty(JCSMPProperties.AUTHENTICATION_SCHEME,
+ JCSMPProperties.AUTHENTICATION_SCHEME_OAUTH2);
+ eventHandler = new DefaultSolaceOAuth2SessionEventHandler(jcsmpProperties, mockTokenProvider);
+ eventHandler.setJcsmpSession(mockSession);
+ }
+
+ @Test
+ void shouldRefreshTokenOnReconnectingEvent() throws JCSMPException {
+ when(mockTokenProvider.getAccessToken()).thenReturn("newAccessToken");
+ SessionEventArgs reconnecting = new SessionEventArgs(RECONNECTING, "Reconnecting", null, 0);
+
+ eventHandler.handleEvent(reconnecting);
+
+ verify(mockTokenProvider, times(1)).getAccessToken();
+ verify(mockSession, times(1)).setProperty(OAUTH2_ACCESS_TOKEN, "newAccessToken");
+ }
+
+ @Test
+ void shouldNotRefreshTokenOnNonReconnectingEvent() throws JCSMPException {
+ SessionEventArgs downError = new SessionEventArgs(DOWN_ERROR, "DownError", null, 0);
+
+ eventHandler.handleEvent(downError);
+
+ verify(mockTokenProvider, never()).getAccessToken();
+ verify(mockSession, never()).setProperty(eq(OAUTH2_ACCESS_TOKEN), anyString());
+ }
+
+ @Test
+ void shouldHandleExceptionWhenRefreshingToken() throws JCSMPException {
+ doThrow(new JCSMPException("Test exception")).when(mockSession)
+ .setProperty(eq(OAUTH2_ACCESS_TOKEN), anyString());
+ SessionEventArgs reconnecting = new SessionEventArgs(RECONNECTING, "Reconnecting", null, 0);
+
+ eventHandler.handleEvent(reconnecting);
+
+ verify(mockTokenProvider, times(1)).getAccessToken();
+ // No need to verify logging, just ensure no exception is thrown to the caller
+ }
+}
\ No newline at end of file
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/application-clientCertAuthIT.yml b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/application-clientCertAuthIT.yml
new file mode 100644
index 0000000..f1c92ae
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/application-clientCertAuthIT.yml
@@ -0,0 +1,21 @@
+solace:
+ java:
+ host: tcps://localhost:55443
+ msgVpn: default
+ clientUsername: default
+ clientPassword: ignored
+ connectRetries: 3
+ reconnectRetries: 3
+ connectRetriesPerHost: 1
+ reconnectRetryWaitInMillis: 2000
+ apiProperties:
+ SSL_VALIDATE_CERTIFICATE: true
+ SSL_VALIDATE_CERTIFICATE_DATE: true
+ SSL_VALIDATE_CERTIFICATE_HOST: true
+ AUTHENTICATION_SCHEME: AUTHENTICATION_SCHEME_CLIENT_CERTIFICATE
+ SSL_TRUST_STORE: certs/client/client-truststore.p12 #will be replaced by absolute path in test
+ SSL_TRUST_STORE_PASSWORD: changeMe123
+ SSL_TRUST_STORE_FORMAT: PKCS12
+ SSL_KEY_STORE: certs/client/client-keystore.jks #will be replaced by absolute path in test
+ SSL_KEY_STORE_PASSWORD: changeMe123
+ SSL_KEY_STORE_FORMAT: JKS
\ No newline at end of file
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/application-oauthIT.yml b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/application-oauthIT.yml
new file mode 100644
index 0000000..1db77a7
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/application-oauthIT.yml
@@ -0,0 +1,27 @@
+spring:
+ security:
+ oauth2:
+ client:
+ registration:
+ my-oauth2-client:
+ provider: my-auth-server
+ client-id: solclient_oauth
+ client-secret: j6gWnw13iqzJfFZzlqzaQabQgXza4oHl
+ authorization-grant-type: client_credentials
+ scope: openid
+ provider:
+ my-auth-server:
+ token-uri: https://localhost:10443/auth/realms/solace-oauth-resource-server-role/protocol/openid-connect/token
+
+solace:
+ java:
+ host: tcps://localhost:55443
+ msgVpn: default
+ connectRetries: 3
+ reconnectRetries: 3
+ connectRetriesPerHost: 1
+ reconnectRetryWaitInMillis: 2000
+ oauth2ClientRegistrationId: my-oauth2-client
+ apiProperties:
+ SSL_VALIDATE_CERTIFICATE: false ## Because using self-signed certificate
+ AUTHENTICATION_SCHEME: AUTHENTICATION_SCHEME_OAUTH2
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/README.txt b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/README.txt
new file mode 100644
index 0000000..d1e517c
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/broker/solbroker.crt b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/broker/solbroker.crt
new file mode 100644
index 0000000..70b2af5
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/broker/solbroker.csr b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/broker/solbroker.csr
new file mode 100644
index 0000000..3969f7c
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/broker/solbroker.key b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/broker/solbroker.key
new file mode 100644
index 0000000..c2632ab
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/broker/solbroker.pem b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/broker/solbroker.pem
new file mode 100644
index 0000000..21e12a1
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client-keystore.jks b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client-keystore.jks
new file mode 100644
index 0000000000000000000000000000000000000000..68b57de0087f508d0969a182afbfe92d9f256df7
GIT binary patch
literal 2441
zcmb_d`8N~_8=lRW!HnIw%@^S+CdP~{t~Dx4F=LP=*)^6iqs%q7R49ZR8cUM3#mJJS
z6tYB7$`FPS%4B5Xy4Ga--1~jsIrk6vet3U)o^zh_F6TMVyEDHt4*&o_dj>1Kg>!7H?dFlA#YyJ91$M3WIRNnayyTjBU`?PY1Qt^BjTDWx-H1r
zi=#=EmF5`pCj56=$sq@IC?A!Cnz*l&8wxyuPTECv@O~`_gf1
z0-F?^AiCAxCIWj|+)5SC=m<6Kxna=l%@Do`6^8R%Rh
ztB6J0Su7UEJbXya^h|$Um`kl|9voHd7P(zA`4~;Z6Nj*lymzkZ$lRT{C_&sLfzKHD4dfBIN%9u>?SmQEyk
z@~)0geX=AeiWXsRNru&(mAyPM^1Y+7WWM&Rpt^f8ucSMSZEJ!IOsg5HFihl&zPup%C^d>5nbqNPD?
z23dzdHmc`oYExEZj&)8iV6^i!RuwW;L)+}#8(3Rr7|MG@T!;4>?P~a9!&bxRsMfUL
z{SIwNNf70
z{ODDolog)GMvs%fF8kHfR!CXG$CVmSBSCKeHwmYTezkYY51(niKNVkUrJLXFj!qr>
zVdk28@3OP58BtZEx>NpVn;Dp8b+213c`i3;^PsJ1G*`2N0<|hp+Glcl$kJNy@aY2?
zW>>f%w!k9Dv7_J|FBG#hDOT374=t`)WBnuk4(oD+c}xwRj8}#||2Y?Zf9F{EyYQQ<
zZuI+5|1H;P?-H`m>06B5yB{M#e=fcydZ`Q}KICA(h)r!Kb6wVV2Hnl1~69TY!h
zPqwcPJ6EC4U8
zm0WP`y;hKnRau3dOM4!M!K5Do-MK=1sbDn}`>=Y4X>7y|jP0NN;LE&eS$49m+oh8}
zX4&Q>0B7s=OnS~NB6i~p3+2WlGR47j{=an(Kk*i9jew;}X*>v-XHFlFQ`RqC3D?RV
zCumrkQ?7b;1g^J7zifjyr==D3`hV0pFPrm(huw#7xcfHh@zcL!QOFgmhcnzuL(9E1
zG{eP%4uNDSLc|=M_fx==uC)d%LIwxnfu6-7pm5AJ>uc)>AW&2YfIEX1#lfLqX9!dp
zWO)=XfkXZUU}C}qD&6No5DhQBr`ThJ#h@pBs6KQbJPIfK7a<~sbfi&y`~nU*MupP@
zLc{;31&axjXf*l(%cD326hd1UcLa~a>*?re>$;;5x_c0(_g{!R`~Pb$g_HPeDzF$l
zoJRT2i@?PLmH)XePzV5y2Z{ssco9%M5D4h_v(zKt*FA6Yc~%1=3yxMb$Se)|D1)s@
zq~$f(j%NvvpedKoZb8duSwUUQ80jL3mGK3nX;|y8>$pkPTtAex-Hbk=awmklyw*A%
z_PdN{sLSmepTDOqmq!2bM5#X8z@mpZS{@-9$&p1%g{yc~GYX%5X_n?{kEW!!~TFEH=hVh;{-@;O`+7=JyZsPmbZejnya36eG0ZEOjL=f8AT-Ibku?wwB8mECOH
zCv>`DB~_O_ZF!laKF11vgK3P@K;{U?CaR
zW7iN>p@i~P$Ze^*B=Xj^Wh!OOy1;XmKNL2<{a79Ki^#!Sbr})FR}$@N)oOl{Kby9(
zN9^7WsPgMzEGPM4iF3!anN)^*vf*Oy-JHxZ4Vz;X!>b5QPjQR=CZM~J0ny?0L6;66
znxOQ3Q2B+0Ubm)W31i=T%UVF*vNqL8Z%B_!aL8H2MSVA~d!UdlMuv^WQ%V@62{iTk
zV6RxiOD+yNab;=J_PzIAO*!5M8)|rwkGzB+W&4y1Ejgdi>q{dksZpZ}lT}1NQHoLY
zR2}p2Jja&0NRyZu@Q`6E^}*K|t4R3S*FDFPMX*t5MjqcTyi_GkUEVEw{{?$h+=qfl
ziM5NwWba_#c5LBvzjD&W4}4s@J{HXpw!QaZ*+~BAq1Eyg5W1Ccy|S!kK<=jWGQE1t
zU+1V{{k5DQ!WpqIe9uMTJeWl%vZCZU4v8M7k1lJpyB<@t5|h2}ZG`^CK-v4gb=`T}
y_2Yc6`eiSTu0BegfjVgS#%{#z)68wI)*n*oO}Ij?xr=c{Vht~Ll)Y7TDF5H7%SG$}
literal 0
HcmV?d00001
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client-truststore.p12 b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client-truststore.p12
new file mode 100644
index 0000000000000000000000000000000000000000..e915ff1ea09720477b5e06419085fbdcce13178c
GIT binary patch
literal 1814
zcmV+x2kH1Qf(H@;0Ru3C2D}CdDuzgg_YDCD0ic2gtptJwsW5^Dr7(g9p#}*mhDe6@
z4FLxRpn?XHFoFh-0s#Opf(C~M2`Yw2hW8Bt2LUi<1_>&LNQUAwWg6_XiA`9Ige)zU@!s#ClCSwATSID2r7n1hW8Bu2?YQ!
z9R>+thDZTr0|Wso1P~jmDE3MhL|LB}f+I8bpE`ho1`tEEW?^M+2@oi1hnvKl6swsX
zt{fSBJoFU?9lwnhxlN(Qpk#m
zh{de~;LVL+RND>z_*by?=Atny|?aUB6X>;_vU
zxQAFeRRlBA2zO`Di?qS0jFz*Nl@M)JdFI!U
zb8mmtZQ?hsOl=5
zqc3OZ>dgcM(rO*ywnbU#LV|DZLih7O2@Sv>T8(~IlC_K^0XvmfGM$A=Ge-}9xlO1C
zpnvUcBfqt0GFitSvN7tIMPLxgm952B#ocF5
z8NbJd8H!2K*#7%{qj|g7F&JdrfzyxE9vI`Fin3b@N2^(r%6{9((kez@aApxNvF5G2
z*Xp{>zKO<0GPpK$(xZ5pV@ro%na
zcU^)s`&iN2rP!=N@^MJXxAs3KrvJxHVMRn+^&eLJI-e6N98$mH*YtG`Xx7qs>0DWQ
zw}$gS%rF3$M*|}!v?8)D+;h^i*X}25FP&?Ks@yr$gT-%_GzJgbkwAmXD;kzwSTF(&
zVl}i>iEJgl8z^j7#U@M#6Yg>`U#G-y8*Z|!>L7;^pDy3Y*4w2><<*Dl*I30ue~FAQ
zRu1P90ecW6^N*T%~09n1%-X3
zP&(k!D%sG{heJ!%U0tb-K@skFv8LIvwy?anYeJ2N7A
z1fZGcu3YuE>ysh$paA{im%dTI#!G+&qZ{M$-Li=zzBJDE{*6Qs-GD+mh)*vWFl5VVeAxziYCRA0VGk3TV&4n(++hhASely6
z6n*%SKo`8$B--^R;25A=<#(V97uhq#b$%9z^h~{bd&v5>v>hLtt${4S)JRp{_s#(Q
zoO2#kJC*Nwccr={&nF5#&*7MU>)#Kcsv_234|Z>?L2wR@yPOqml7zWgQNU}-9Ve@4
zGtrj_y;^SBR!!~O?pm=?s5Bc&TkECpjG0;O9+&AWUUPz5!~Cqyi}FK=*=A1a(yO5y
zgA{AINU4F-JOT_JG;T;V_vYBT8oI*%_Xcz!edZw`91q1D1u=hOsxOnD*LhubzeyPA
zm{6Nh&b}4CQm4&0NC9O71OfpC00bbt7bl%O
zh*_%Sn)6p%-rtGMF8=~J)nDqHsIh+9u?)`y6m-!>m%8`?><86VTs%}8d9+|{{sICg
E5a^y)OaK4?
literal 0
HcmV?d00001
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client.crt b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client.crt
new file mode 100644
index 0000000..12c61d8
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client.csr b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client.csr
new file mode 100644
index 0000000..cb3ef9f
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client.key b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client.key
new file mode 100644
index 0000000..6847c05
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client.p12 b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client.p12
new file mode 100644
index 0000000000000000000000000000000000000000..52ca2b62bf7cba19c8dfccb06eaa3f693dc1c9ea
GIT binary patch
literal 2754
zcmY+^cQhM{9tZFsVucnlDx}n?(Gq(LV$a%p#cYd~S|u)G6}`1;#U7=_O|@vLRjUn3
zsfKWgQLPbDE)}EV^`7(IyYKz+JHK7NQzg#jMHF~9?QI>4{D^G`zu7X-2XdjU)bf^z_wbNqL4QAwrh
z^z_VtL=5mPXNf*)+4FdeWVpS3Ty`?>F)Hlsu$mpN?4ru4U&(G%f#P3w*jsdkX2Hsa
z?M6XPrXeIhM(&Wyc?HyKa#=MiB&dGYHkDBP={^F@oLMq6Mr@0}7V8ErkNlzZ`e?2I
z?nUw~e3QL1l1sUr+Twpxz7j(YpS9p~Xg7Quk8*>5=
zzFvCU>}f`8^5}r@DeX1EYUb*W3GPEkUDu)A9tX8@F1-%o&?Gt5sC)FA<0Z4}SkB-l
z5jEHDyt?rHO0^>|M_!w%+K!ZU$~gC`CsJ0&j2g|^V8*XOLO8&wJOFH!f`oDGOn@lO
zQQ$+^yT((cZ7=w5Ybu=g^&Y?2e-QXBLW$%yK%H_jz#!^cME0I**7mib0v65d6Cn0Q
zV3U#{S69O6kaE@&B(@GZ@8M#z>um`RiVrtik&usp^Vr!f8=>{i7mWm#VxqMLi(kGV9v@o|>O?}xorV(3o(HRsQ6fnQa(I~Ums^J%4V6U(xkT3qRj=
ziNawQcO3Tj?EMT7(&mBHr{PNpRx+F8Gs18i?>d8l*ugswa_*kX8wOkxJK$++b|5)B
zXf1aRfCG6Wzdyx&5TZ|ggynM=Kc|woVCp4S1
zt&|z~)Y5ONAJ)l%j>!tUj7rWEU*8nywMp+Xv4ejzUi@7sIJ@hBKZZYq_cELKo`sq<
zdb0A8K#f(tz2sSj5(1Ylp&%z)u{E)^4D34!|p(O`TmdQSW
zL9#1btHfw2CIyR=`0x*|A;mW;>R83P*_mJVEmsh^5llh#<{xcU#=yPHmy|>NJrgt<(5PcjI;q$<6nmavHgcXRyz7$
zPEvjWx%Gc4GW@M#x!8U^8P}Tnw~9av5HIe#nahb7s9nHMWUDPafJ(l7$lwz~xGh}l
zj7ro!g^~0neme?`m^2F?;&9OTyS90@grwGqLo*urXe?X?gXBynJ
z)sG78`>|c7{c{PuLcvDQkVa|#PD3Am#9*H&oVWu$NA*81s<6j_Bn(&CW0?}gS?^u6
zM}>py2Ny(i8RmsAjH%Km5|$LrvhQlB8a+tkz_3x=i8^LTu3K?%>L~TYDtbhk?q2g*
z(76Oopdt?P?J?3Ima~cb1SL9s1G;72)jV@@+k(w(bRtbVnC`K5cp%I4x_1TJKJ$i-
z;Pakh_5{^;3T9WF=6TACcgEM&qb2w6d31EElv*AeG_Jm|u5={OTGRM_`XPrh!)HK)
zI-=B=aj?tdj`rR9$~?88aZ&b-PnpIe-r>K4CTsWNRB>Ibj%6lQcg7o6n%sNDFV^}B>?#)K1>bbql)8!sw-|OR
z_vjk(Z-G?_m>ppX)^NpuCj(Re-Ten2MS`?3OY>pf*%DeBaw?GU)mo2hL~61#>+9jS
z`nI|9*M9cCVx(q~6+JR&2d@q!Efg7+@Juk%#C2@89;{o_y%E
z9qnmSxdn-9^zfy;-WX+AYZ&|q(3JfP1$$PlN?ulu^gemN6U)d9#UTBX=8yAV%A1!G
zw*tH=JX2`H>kJ_iE>$vMo!2j%JudhUYn5(I9a;)#e*;~Y$U2j-UBjO0`?nG1YM5k~
zw>z}~<*DhjcJ_*gBE;bkJNItyxB<%k!e@M&0*P@f-FD(DJ~V+Y{aUT<9CF#AFsBO#
zj!N#A2_s%bo$$wNHOB&&@`@m0)PjmR_KM8)wwmfkUf4zFF5$L9^%}|G4Qw#9px6$T
zr6JzWJ|Nsqh`S*9BQ5Mn<|W7B^rrW{N-=Y++I}n0Vf*RA8JDBD%9;?>>N{>if1Gfx
z+8yF}-yqYg;`Mc0lt7>z)gTZAQTV*7yYN?k>>?MS}r`#>buo3U?c>3skEZ0x*VHW0GEf0
yz*#_yG8_!_0*nA4WYn~f#kGepxI4@9vR4Q?y~IHO6UeOWskGSI5t;Niq5lFYL>BV^
literal 0
HcmV?d00001
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client.pem b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/client/client.pem
new file mode 100644
index 0000000..7d3d891
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/keycloak/keycloak.crt b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/keycloak/keycloak.crt
new file mode 100644
index 0000000..062f94b
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/keycloak/keycloak.csr b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/keycloak/keycloak.csr
new file mode 100644
index 0000000..60032eb
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/keycloak/keycloak.key b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/keycloak/keycloak.key
new file mode 100644
index 0000000..6d9102d
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/keycloak/keycloak.pem b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/keycloak/keycloak.pem
new file mode 100644
index 0000000..d6020f3
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/keycloak_san.conf b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/keycloak_san.conf
new file mode 100644
index 0000000..be980a7
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.crt b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.crt
new file mode 100644
index 0000000..43fd900
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.der b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.der
new file mode 100644
index 0000000000000000000000000000000000000000..ca4a7fe1d46cd39da6f5736aaf1921c3ff23e15a
GIT binary patch
literal 1435
zcmXqLVx4Z##9Y6CnTe5!NhCPzk)!^7k@?bv&9Skq_cG=^+%n04myJ`a&7BGV`zp<>!|uI6E51iSrtn7?>Lx7@C-v8=FOm^O^v;
z2BuIhZ7pwNQbP7DBP#=Q6B9p!K@$@fQxg*-!_-Sm2bZ;_N_Z8&Fu8Cq+&`^-_P4ZR
zHnqpUf9uXMH(Q~5`HYTfDbx7}b%j;)ceq7X{7Z@Pn>E>h#ld&(7g4!84-(ToqHmQy
z{(kpdQTClrTow9V_ivlqE;mx>dK7YY+mPFdi6W?;Ju?$?ue8nd$w}?
ze|Jfd^Cs7=tr@SGA8Kztmn@K`%5>7QWxw++L+x;6&o*H?J;yMH(L
zP2OZ-d*u?d^VPfdtm{qoYclQ5ki7E!?7|k=RN0hiCuWu_yzm#ETHGX%$h%kl)Xgu+
zCxzShubQc_?ZbAj1HVgO%~@Z#vWcbhqgn~8DaYDrU+(*SIxKZo!pF|KL8Ow?w0qj#
zb)UGN9{VxF-^W4ccKk=FIXS12&Tv=#zT&wdsMSiV?8vgKyVQ1S)K~d-^@t^35A84RTKnUBpFfER?3So{{v#w_K5A}cHMivd$zb)a>aoFRh5SQ
zk@t2xX6fx~H9hOTcm5&9%c{pz57qA8(34`5z+~^jBxg8LR_h&4@Y9mUWqFbutA`KEHFLG^0A1qh=_T03d)2(nlqI%`Q!?#+vP@gyZ#!;gQS&NBn-qFuq)sP
zDG+94{LjK_zzn32lN~UR0h1jg!z8soH}rS!H~Y)7`i|qiZeQ~aPp_@~yKuqZ}RcK_qec
zHpTC{n{xHo=f!V)&VE*PQ-|}@`@8=A|9EUeRc1=+QpU-@^S*A|7VG{i>6q-?w!;s;
zCwy6~QpL&gVTZ3%KzE{2=h0FNp10&`oy#|Jxc~r|N@eE&
literal 0
HcmV?d00001
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.key b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.key
new file mode 100644
index 0000000..58cb536
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.pem b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.pem
new file mode 100644
index 0000000..c43bc76
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.srl b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.srl
new file mode 100644
index 0000000..3ddaad8
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/rootCA/rootCA.srl
@@ -0,0 +1 @@
+6C05509733CDE0014B58B238944F91CF0D5C1BE9
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/solbroker_san.conf b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/certs/solbroker_san.conf
new file mode 100644
index 0000000..37bf92f
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/free-tier-broker-with-tls-and-oauth-docker-compose.yml b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/free-tier-broker-with-tls-and-oauth-docker-compose.yml
new file mode 100644
index 0000000..d5bca32
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-server-role-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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/free-tier-broker-with-tls-docker-compose.yml b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/free-tier-broker-with-tls-docker-compose.yml
new file mode 100644
index 0000000..805d378
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/free-tier-broker-with-tls-docker-compose.yml
@@ -0,0 +1,26 @@
+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
+
+secrets:
+ server.pem:
+ file: "certs/broker/solbroker.pem" ## The server certificate for the Solace PubSub+ broker
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/logback-test.xml b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..a9e0d03
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/logback-test.xml
@@ -0,0 +1,15 @@
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger -%msg%n%rEx{full, org}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/oauth/README b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/oauth/README
new file mode 100644
index 0000000..ae85175
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/oauth/keycloak/solace-oauth-resource-server-role-realm-export.json b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/oauth/keycloak/solace-oauth-resource-server-role-realm-export.json
new file mode 100644
index 0000000..3d2e063
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/oauth/keycloak/solace-oauth-resource-server-role-realm-export.json
@@ -0,0 +1,2276 @@
+{
+ "id": "solace-oauth-resource-server-role",
+ "realm": "solace-oauth-resource-server-role",
+ "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-server-role",
+ "description": "${role_default-roles}",
+ "composite": true,
+ "composites": {
+ "realm": [
+ "offline_access",
+ "uma_authorization"
+ ],
+ "client": {
+ "account": [
+ "manage-account",
+ "view-profile"
+ ]
+ }
+ },
+ "clientRole": false,
+ "containerId": "solace-oauth-resource-server-role",
+ "attributes": {}
+ },
+ {
+ "id": "2e5b3f5c-6bb7-4f35-94c3-a277948e2f06",
+ "name": "uma_authorization",
+ "description": "${role_uma_authorization}",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "solace-oauth-resource-server-role",
+ "attributes": {}
+ },
+ {
+ "id": "844b9405-65bf-4b12-8cd6-e88860d2afe1",
+ "name": "offline_access",
+ "description": "${role_offline-access}",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "solace-oauth-resource-server-role",
+ "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-server-role",
+ "description": "${role_default-roles}",
+ "composite": true,
+ "clientRole": false,
+ "containerId": "solace-oauth-resource-server-role"
+ },
+ "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-server-role"
+ ],
+ "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-server-role/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/realms/solace-oauth-resource-server-role/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-server-role/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/realms/solace-oauth-resource-server-role/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-server-role/console/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/admin/solace-oauth-resource-server-role/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/oauth/nginx.conf b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/oauth/nginx.conf
new file mode 100644
index 0000000..a3ba5e2
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/oauth/www/index.html b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/oauth/www/index.html
new file mode 100644
index 0000000..e6047aa
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/oauth/www/index.html
@@ -0,0 +1,6 @@
+
+
+ Hello World!!
+
+
+
\ No newline at end of file
diff --git a/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/solace.env b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/solace.env
new file mode 100644
index 0000000..04a0dfe
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/solace_tls.env b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/solace_tls.env
new file mode 100644
index 0000000..cc90d8a
--- /dev/null
+++ b/solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/resources/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
diff --git a/solace-spring-boot-autoconfigure/solace-jms-spring-boot-autoconfigure/pom.xml b/solace-spring-boot-autoconfigure/solace-jms-spring-boot-autoconfigure/pom.xml
index d0b9b70..224ce6c 100644
--- a/solace-spring-boot-autoconfigure/solace-jms-spring-boot-autoconfigure/pom.xml
+++ b/solace-spring-boot-autoconfigure/solace-jms-spring-boot-autoconfigure/pom.xml
@@ -5,12 +5,12 @@
com.solace.spring.boot
solace-spring-boot-parent
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
../../solace-spring-boot-parent/pom.xml
solace-jms-spring-boot-autoconfigure
- 5.0.1-SNAPSHOT
+ 5.1.0-SNAPSHOT
jar
Solace Spring Boot Autoconfiguration - JMS
@@ -47,9 +47,9 @@
test
- com.github.stefanbirkner
- system-rules
- 1.19.0
+ org.junit-pioneer
+ junit-pioneer
+ 1.9.1
test
diff --git a/solace-spring-boot-autoconfigure/solace-jms-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/SolaceJmsAutoConfigurationTest.java b/solace-spring-boot-autoconfigure/solace-jms-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/SolaceJmsAutoConfigurationTest.java
index 989821b..22b6c06 100644
--- a/solace-spring-boot-autoconfigure/solace-jms-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/SolaceJmsAutoConfigurationTest.java
+++ b/solace-spring-boot-autoconfigure/solace-jms-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/SolaceJmsAutoConfigurationTest.java
@@ -18,24 +18,24 @@
*/
package com.solace.spring.boot.autoconfigure;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import com.solacesystems.jms.SolConnectionFactoryImpl;
-import org.junit.After;
-import org.junit.Test;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.core.JmsTemplate;
-public class SolaceJmsAutoConfigurationTest {
+class SolaceJmsAutoConfigurationTest {
@Configuration public static class EmptyConfiguration {}
private AnnotationConfigApplicationContext context;
private final Class configClass = SolaceJmsAutoConfiguration.class;
- @After
+ @AfterEach
public void tearDown() {
if (this.context != null) {
this.context.close();
@@ -43,7 +43,7 @@ public void tearDown() {
}
@Test
- public void defaultNativeConnectionFactory() {
+ void defaultNativeConnectionFactory() {
load("");
JmsTemplate jmsTemplate = this.context.getBean(JmsTemplate.class);
SolConnectionFactoryImpl connectionFactory = this.context
@@ -57,7 +57,7 @@ public void defaultNativeConnectionFactory() {
}
@Test
- public void customNativeConnectionFactory() {
+ void customNativeConnectionFactory() {
load("solace.jms.host=192.168.1.80:55500",
"solace.jms.clientUsername=bob", "solace.jms.clientPassword=password",
"solace.jms.msgVpn=newVpn");
diff --git a/solace-spring-boot-bom/README.md b/solace-spring-boot-bom/README.md
index c78b73a..8789590 100644
--- a/solace-spring-boot-bom/README.md
+++ b/solace-spring-boot-bom/README.md
@@ -21,6 +21,7 @@ Consult the table below to determine which version of the BOM you need to use:
| 2.6.4 | 1.2.x |
| 2.7.7 | 1.3.0 |
| 3.0.6 | 2.0.0 |
+| 3.3.1 | 2.1.0 |
## Including the BOM
@@ -33,7 +34,7 @@ In addition to showing how to include the BOM, the following snippets also shows
com.solace.spring.boot
solace-spring-boot-bom
- 2.0.0
+ 2.1.0
pom
import
@@ -61,7 +62,7 @@ apply plugin: 'io.spring.dependency-management'
dependencyManagement {
imports {
- mavenBom "com.solace.spring.boot:solace-spring-boot-bom:2.0.0"
+ mavenBom "com.solace.spring.boot:solace-spring-boot-bom:2.1.0"
}
}
@@ -73,7 +74,7 @@ dependencies {
### Using it with Gradle 5
```groovy
dependencies {
- implementation(platform("com.solace.spring.boot:solace-spring-boot-bom:2.0.0"))
+ implementation(platform("com.solace.spring.boot:solace-spring-boot-bom:2.1.0"))
implementation("com.solace.spring.boot:solace-spring-boot-starter")
}
```
diff --git a/solace-spring-boot-bom/pom.xml b/solace-spring-boot-bom/pom.xml
index 55495e1..c6289f5 100644
--- a/solace-spring-boot-bom/pom.xml
+++ b/solace-spring-boot-bom/pom.xml
@@ -5,11 +5,11 @@
com.solace.spring.boot
solace-spring-boot-build
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
solace-spring-boot-bom
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
pom
Solace Spring Boot BOM
diff --git a/solace-spring-boot-parent/pom.xml b/solace-spring-boot-parent/pom.xml
index 3392d45..8073004 100644
--- a/solace-spring-boot-parent/pom.xml
+++ b/solace-spring-boot-parent/pom.xml
@@ -5,12 +5,12 @@
com.solace.spring.boot
solace-spring-boot-build
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
../pom.xml
solace-spring-boot-parent
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
pom
Solace Spring Boot Parent
@@ -24,29 +24,18 @@
17
17
- 5.0.1-SNAPSHOT
- 5.0.1-SNAPSHOT
+ 5.1.0-SNAPSHOT
+ 5.1.0-SNAPSHOT
- 10.20.0
- 10.20.0
+ 10.22.0
+ 10.22.0
- 2.12.4
+ 2.22.2
+ 2.22.2
-
-
- org.apache.logging.log4j
- log4j-bom
- 2.19.0
- pom
- import
-
-
org.springframework.boot
@@ -73,16 +62,48 @@
- org.apache.maven.plugins
maven-surefire-plugin
${surefire.plugin.version}
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
+ --add-opens java.base/java.net=ALL-UNNAMED
+
+
+ junit.jupiter.execution.parallel.enabled=false
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ ${failsafe.plugin.version}
+
+
+ integration-test
+
+ integration-test
+ verify
+
+
+
+ --add-opens java.base/java.lang=ALL-UNNAMED
+ --add-opens java.base/java.util=ALL-UNNAMED
+ --add-opens java.base/java.net=ALL-UNNAMED
+
+
+
+ junit.jupiter.execution.parallel.enabled=false
+
+
+
+
+
+
diff --git a/solace-spring-boot-samples/pom.xml b/solace-spring-boot-samples/pom.xml
index 0c9ea29..60cf5ff 100644
--- a/solace-spring-boot-samples/pom.xml
+++ b/solace-spring-boot-samples/pom.xml
@@ -4,7 +4,7 @@
com.solace.spring.boot
solace-spring-boot-samples
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
pom
Solace Spring Boot Samples Parent
diff --git a/solace-spring-boot-samples/solace-java-oauth2-sample-app/README.md b/solace-spring-boot-samples/solace-java-oauth2-sample-app/README.md
new file mode 100644
index 0000000..810cd01
--- /dev/null
+++ b/solace-spring-boot-samples/solace-java-oauth2-sample-app/README.md
@@ -0,0 +1,39 @@
+# Solace Spring Boot Sample - Java
+
+This is a simple sample project to demonstrate the autoconfiguration of the Solace Java API using the `solace-spring-boot-starter` (or the `solace-java-spring-boot-starter` in particular).
+
+Please refer to the [Spring Boot Auto-Configuration for the Solace Java API](../../solace-spring-boot-starters/solace-java-spring-boot-starter) project for more detail.
+
+## Contents
+
+* [Acquiring a Solace PubSub+ Service](#acquiring-a-solace-pubsub-service)
+* [Configuring the Application to use your Solace PubSub+ Service Credentials](#configuring-the-sample-to-use-your-solace-pubsub-service-credentials)
+* [Running the Sample](#running-the-sample)
+
+## Acquiring a Solace PubSub+ Service
+
+To run the samples you will need a Solace PubSub+ Event Broker.
+Here are two ways to quickly get started if you don't already have a PubSub+ instance:
+
+1. Get a free Solace PubSub+ event broker cloud instance
+ * Visit https://solace.com/products/event-broker/cloud/
+ * Create an account and instance for free
+2. Run the Solace PubSub+ event broker locally
+ * Visit https://solace.com/downloads/
+ * A variety of download options are available to run the software locally
+ * Follow the instructions for whatever download option you choose
+
+## Configuring the Sample to use your Solace PubSub+ Service Credentials
+
+Please consult the [Spring Boot Auto-Configuration for the Solace Java API](../../solace-spring-boot-starters/solace-java-spring-boot-starter/README.md#configure-the-application-to-use-your-solace-pubsub-service-credentials) documentation for the details on how to connect this sample to a Solace PubSub+ service.
+
+## Running the Sample
+
+The simplest way to run the sample is from the project root folder using maven. For example:
+
+```shell script
+cd solace-spring-boot-samples/solace-java-oauth2-sample-app
+mvn spring-boot:run
+```
+
+Hint: look for "Sending Hello World" and "TextMessage received: Hello World" in the displayed logs.
\ No newline at end of file
diff --git a/solace-spring-boot-samples/solace-java-oauth2-sample-app/pom.xml b/solace-spring-boot-samples/solace-java-oauth2-sample-app/pom.xml
new file mode 100644
index 0000000..7e91c0d
--- /dev/null
+++ b/solace-spring-boot-samples/solace-java-oauth2-sample-app/pom.xml
@@ -0,0 +1,105 @@
+
+
+ 4.0.0
+
+
+ com.solace.spring.boot
+ solace-spring-boot-samples
+ 2.1.0-SNAPSHOT
+
+
+ solace-java-oauth2-sample-app
+ 2.1.0-SNAPSHOT
+ jar
+
+ Solace Spring Boot Sample - Java
+ Sample Application using the custom auto-config
+ https://github.com/SolaceProducts/solace-spring-boot
+
+
+ UTF-8
+ demo.DemoApplication
+ 17
+ 3.3.1
+ 2.1.0-SNAPSHOT
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring.boot.version}
+ pom
+ import
+
+
+
+ com.solace.spring.boot
+ solace-spring-boot-bom
+ ${solace.spring.boot.version}
+ pom
+ import
+
+
+
+
+
+
+ com.solace.spring.boot
+ solace-spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ 3.1.1
+
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+
+ ${java.version}
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring.boot.version}
+
+ true
+ false
+
+
+
+
+ repackage
+
+
+
+
+
+
+
diff --git a/solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/java/demo/DemoApplication.java b/solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/java/demo/DemoApplication.java
new file mode 100644
index 0000000..442a3b4
--- /dev/null
+++ b/solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/java/demo/DemoApplication.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package demo;
+
+import com.solacesystems.jcsmp.DeliveryMode;
+import com.solacesystems.jcsmp.JCSMPFactory;
+import com.solacesystems.jcsmp.JCSMPProperties;
+import com.solacesystems.jcsmp.JCSMPSession;
+import com.solacesystems.jcsmp.SpringJCSMPFactory;
+import com.solacesystems.jcsmp.TextMessage;
+import com.solacesystems.jcsmp.Topic;
+import com.solacesystems.jcsmp.XMLMessageConsumer;
+import com.solacesystems.jcsmp.XMLMessageProducer;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.concurrent.TimeUnit;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.stereotype.Component;
+
+@SpringBootApplication
+@EnableWebSecurity
+public class DemoApplication {
+
+ public static void main(String[] args) {
+ //In development environment, ignore certificates when using self-signed certs. This is not recommended for production
+ //Or alternatively, import the Solace broker's CA certificate into the Java Truststore
+ ignoreCertificates();
+ SpringApplication.run(DemoApplication.class, args);
+ }
+
+ @Component
+ static class Runner implements CommandLineRunner {
+
+ private static final Logger logger = LoggerFactory.getLogger(Runner.class);
+
+ private final Topic topic = JCSMPFactory.onlyInstance().createTopic("tutorial/topic");
+
+ @Autowired
+ private SpringJCSMPFactory solaceFactory;
+
+ // Examples of other beans that can be used together to generate a customized SpringJCSMPFactory
+ @Autowired(required = false)
+ private JCSMPProperties jcsmpProperties;
+
+ private DemoMessageConsumer msgConsumer = new DemoMessageConsumer();
+ private DemoPublishEventHandler pubEventHandler = new DemoPublishEventHandler();
+
+ public void run(String... strings) throws Exception {
+ final String msg = "Hello World";
+ final JCSMPSession session = solaceFactory.createSession();
+
+ XMLMessageConsumer cons = session.getMessageConsumer(msgConsumer);
+
+ session.addSubscription(topic);
+ logger.info("Connected. Awaiting message...");
+ cons.start();
+
+ // Consumer session is now hooked up and running!
+
+ /** Anonymous inner-class for handling publishing events */
+ XMLMessageProducer prod = session.getMessageProducer(pubEventHandler);
+ // Publish-only session is now hooked up and running!
+
+ TextMessage jcsmpMsg = JCSMPFactory.onlyInstance().createMessage(TextMessage.class);
+ jcsmpMsg.setText(msg);
+ jcsmpMsg.setDeliveryMode(DeliveryMode.PERSISTENT);
+
+ logger.info("============= Sending {}", msg);
+ prod.send(jcsmpMsg, topic);
+
+ try {
+ // block here until message received, and latch will flip
+ msgConsumer.getLatch().await(10, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ logger.error("I was awoken while waiting");
+ }
+
+ Thread.sleep(600_000);
+ // Close consumer
+ cons.close();
+ logger.info("Exiting.");
+ session.closeSession();
+ }
+ }
+
+
+ private static void ignoreCertificates() {
+ TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[]{};
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] certs, String authType) {
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] certs, String authType) {
+ }
+ }};
+
+ try {
+ SSLContext sc = SSLContext.getInstance("TLS");
+ sc.init(null, trustAllCerts, new SecureRandom());
+ HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/java/demo/DemoMessageConsumer.java b/solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/java/demo/DemoMessageConsumer.java
new file mode 100644
index 0000000..baac7e2
--- /dev/null
+++ b/solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/java/demo/DemoMessageConsumer.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package demo;
+
+import java.util.concurrent.CountDownLatch;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.solacesystems.jcsmp.BytesXMLMessage;
+import com.solacesystems.jcsmp.JCSMPException;
+import com.solacesystems.jcsmp.TextMessage;
+import com.solacesystems.jcsmp.XMLMessageListener;
+
+public class DemoMessageConsumer implements XMLMessageListener {
+
+ private CountDownLatch latch = new CountDownLatch(1);
+ private static final Logger logger = LoggerFactory.getLogger(DemoMessageConsumer.class);
+
+ public void onReceive(BytesXMLMessage msg) {
+ if (msg instanceof TextMessage) {
+ logger.info("============= TextMessage received: {}", ((TextMessage) msg).getText());
+ } else {
+ logger.info("============= Message received.");
+ }
+ latch.countDown(); // unblock main thread
+ }
+
+ public void onException(JCSMPException e) {
+ logger.info("Consumer received exception:", e);
+ latch.countDown(); // unblock main thread
+ }
+
+ public CountDownLatch getLatch() {
+ return latch;
+ }
+
+}
diff --git a/solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/java/demo/DemoPublishEventHandler.java b/solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/java/demo/DemoPublishEventHandler.java
new file mode 100644
index 0000000..2d1b59f
--- /dev/null
+++ b/solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/java/demo/DemoPublishEventHandler.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package demo;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.solacesystems.jcsmp.JCSMPException;
+import com.solacesystems.jcsmp.JCSMPStreamingPublishEventHandler;
+
+public class DemoPublishEventHandler implements JCSMPStreamingPublishEventHandler {
+ private static final Logger logger = LoggerFactory.getLogger(DemoPublishEventHandler.class);
+
+ public void responseReceived(String messageID) {
+ logger.info("Producer received response for msg: {}", messageID);
+ }
+
+ public void handleError(String messageID, JCSMPException e, long timestamp) {
+ logger.info("Producer received error for msg: {}@{} - {}%n", messageID, timestamp, e);
+ }
+}
diff --git a/solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/resources/application.yml b/solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/resources/application.yml
new file mode 100644
index 0000000..6435377
--- /dev/null
+++ b/solace-spring-boot-samples/solace-java-oauth2-sample-app/src/main/resources/application.yml
@@ -0,0 +1,32 @@
+spring:
+ security:
+ oauth2:
+ client:
+ registration:
+ my-oauth2-client:
+ provider: my-auth-server
+ client-id: replace-client-id-here
+ client-secret: replace-client-secret-here
+ authorization-grant-type: client_credentials
+ #scope: optional-scopes
+ provider:
+ my-auth-server:
+ token-uri: replace-token-uri-here
+
+solace:
+ java:
+ host: tcps://localhost:55443 #Solace PubSub+ Broker secure connection URL
+ msgVpn: default
+ #clientUsername: not-required_will-be-ignored
+ #clientPassword: not-required_will-be-ignored
+ connectRetries: 3
+ reconnectRetries: 3
+ connectRetriesPerHost: 1
+ reconnectRetryWaitInMillis: 3000
+ oauth2ClientRegistrationId: my-oauth2-client # The registrationId of the OAuth2 client in the Spring Security configuration above
+ apiProperties:
+ #SSL_VALIDATE_CERTIFICATE: false # Set to false in local only, if using self-signed certificates
+ AUTHENTICATION_SCHEME: AUTHENTICATION_SCHEME_OAUTH2 # The authentication scheme to be used for connecting to the Solace PubSub+ Broker
+
+server:
+ port: 8090
\ No newline at end of file
diff --git a/solace-spring-boot-samples/solace-java-sample-app/pom.xml b/solace-spring-boot-samples/solace-java-sample-app/pom.xml
index 3075cfd..8edd15d 100644
--- a/solace-spring-boot-samples/solace-java-sample-app/pom.xml
+++ b/solace-spring-boot-samples/solace-java-sample-app/pom.xml
@@ -5,11 +5,11 @@
com.solace.spring.boot
solace-spring-boot-samples
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
solace-java-sample-app
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
jar
Solace Spring Boot Sample - Java
@@ -20,24 +20,12 @@
UTF-8
demo.DemoApplication
17
- 3.0.6
- 2.0.1-SNAPSHOT
+ 3.3.1
+ 2.1.0-SNAPSHOT
-
-
- org.apache.logging.log4j
- log4j-bom
- 2.19.0
- pom
- import
-
-
org.springframework.boot
spring-boot-dependencies
diff --git a/solace-spring-boot-samples/solace-jms-sample-app-jndi/pom.xml b/solace-spring-boot-samples/solace-jms-sample-app-jndi/pom.xml
index 62fee7b..6c9a5fb 100644
--- a/solace-spring-boot-samples/solace-jms-sample-app-jndi/pom.xml
+++ b/solace-spring-boot-samples/solace-jms-sample-app-jndi/pom.xml
@@ -5,11 +5,11 @@
com.solace.spring.boot
solace-spring-boot-samples
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
solace-jms-sample-app-jndi
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
jar
Solace Spring Boot Sample - JMS (JNDI)
@@ -20,24 +20,12 @@
UTF-8
jndidemo.JndiDemoApplication
17
- 3.0.6
- 2.0.1-SNAPSHOT
+ 3.3.1
+ 2.1.0-SNAPSHOT
-
-
- org.apache.logging.log4j
- log4j-bom
- 2.19.0
- pom
- import
-
-
org.springframework.boot
spring-boot-dependencies
diff --git a/solace-spring-boot-samples/solace-jms-sample-app/pom.xml b/solace-spring-boot-samples/solace-jms-sample-app/pom.xml
index af98062..872eb9d 100644
--- a/solace-spring-boot-samples/solace-jms-sample-app/pom.xml
+++ b/solace-spring-boot-samples/solace-jms-sample-app/pom.xml
@@ -5,11 +5,11 @@
com.solace.spring.boot
solace-spring-boot-samples
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
solace-jms-sample-app
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
jar
Solace Spring Boot Sample - JMS
@@ -20,24 +20,12 @@
UTF-8
jmsdemo.DemoApplication
17
- 3.0.6
- 2.0.1-SNAPSHOT
+ 3.3.1
+ 2.1.0-SNAPSHOT
-
-
- org.apache.logging.log4j
- log4j-bom
- 2.19.0
- pom
- import
-
-
org.springframework.boot
spring-boot-dependencies
diff --git a/solace-spring-boot-starters/solace-java-spring-boot-starter/README.md b/solace-spring-boot-starters/solace-java-spring-boot-starter/README.md
index ff824aa..1e66d33 100644
--- a/solace-spring-boot-starters/solace-java-spring-boot-starter/README.md
+++ b/solace-spring-boot-starters/solace-java-spring-boot-starter/README.md
@@ -6,6 +6,7 @@ This project provides Spring Boot Auto-Configuration and an associated Spring Bo
* [Overview](#overview)
* [Using Auto-Configuration in your App](#using-auto-configuration-in-your-app)
+* [Using OAuth2 Authentication Scheme](#using-oauth2-authentication-scheme)
* [Resources](#resources)
---
@@ -39,7 +40,7 @@ Note that you'll need to include version 3.1.0 or later to use Spring Boot relea
```groovy
// Solace Java API & auto-configuration
-compile("com.solace.spring.boot:solace-java-spring-boot-starter:5.0.0")
+compile("com.solace.spring.boot:solace-java-spring-boot-starter:5.1.0")
```
#### Using it with Maven
@@ -49,7 +50,7 @@ compile("com.solace.spring.boot:solace-java-spring-boot-starter:5.0.0")
com.solace.spring.boot
solace-java-spring-boot-starter
- 5.0.0
+ 5.1.0
```
@@ -92,6 +93,7 @@ solace.java.connectRetries
solace.java.reconnectRetries
solace.java.connectRetriesPerHost
solace.java.reconnectRetryWaitInMillis
+solace.java.oauth2ClientRegistrationId ##Set it when OAuth2 authentication scheme enabled. Reference to the Spring OAuth2 client registration-id.
```
Where reasonable, sensible defaults are always chosen. So a developer using a Solace PubSub+ message broker and wishing to use the default message-vpn may only set the `solace.java.host`.
@@ -108,6 +110,102 @@ solace.java.apiProperties.client_channel_properties.keepAliveIntervalInMillis=30
Note that the direct configuration of `solace.java.` properties takes precedence over the `solace.java.apiProperties.`.
+## Using OAuth2 Authentication Scheme
+
+This Spring Boot starter for Solace Java API supports OAuth2 authentication scheme. It requires a version of Solace PubSub+ broker that supports OAuth2 authentication scheme.
+
+The Solace PubSub+ Broker should be setup for OAuth2 authentication. Refer to
+the [Solace PubSub+: Configuring-OAuth-Authorization](https://docs.solace.com/Security/Configuring-OAuth-Authorization.htm)
+for more information.
+See [Azure OAuth Setup](https://solace.com/blog/azure-oauth-setup-for-solace-rest-and-smf-clients/)
+for example.
+
+You may also like to check
+the [OAuth2 Integration Test](../../solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/test/java/com/solace/spring/boot/autoconfigure/springBootTests/MessagingWithOAuthIT.java)
+for more information.
+
+> [!NOTE]
+> The OAuth profile on Solace PubSub+ broker should be setup for Resource Server role. This Solace
+> Java API Starer OAuth2 authentication scheme supports ```client_credentials``` grant type out-of-the
+> box.
+
+> [!TIP]
+> The OAuth2 grant type ```client_credentials``` is used for machine to machine authentication, it
+> is recommended that Token expiry time is not too short as it may cause frequent token refreshes and
+> impact the performance.
+
+### Using OAuth2 Authentication Scheme with Solace Java API
+
+To use OAuth2 authentication scheme with Solace Java API, follow these steps:
+
+Firstly, add the required dependencies to your `build.gradle` file:
+
+```groovy
+compile("org.springframework.boot:spring-boot-starter-oauth2-client")
+```
+
+or `pom.xml` file:
+
+```xml
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+
+```
+
+Secondly, add `@EnableWebSecurity` annotation to your Spring Boot application class:
+
+```java
+
+@SpringBootApplication
+@EnableWebSecurity
+public class DemoApplication {
+
+}
+```
+
+Finally, configure the Spring OAuth2 Client Registration provider through following properties in
+your `application.properties` file:
+
+```
+##spring.security.oauth2.client.registration..provider=
+spring.security.oauth2.client.registration.my-oauth2-client.provider=my-auth-server
+spring.security.oauth2.client.registration.my-oauth2-client.client-id=replace-client-id-here
+spring.security.oauth2.client.registration.my-oauth2-client.client-secret=replace-client-secret-here
+spring.security.oauth2.client.registration.my-oauth2-client.authorization-grant-type=client_credentials ## only client_credentials grant type is supported
+
+##spring.security.oauth2.client.provider..token-uri=
+spring.security.oauth2.client.provider.my-auth-server.token-uri=replace-token-uri-here
+
+solace.java.host=tcps://localhost:55443 ## OATUH2 authentication scheme requires a secure connection to the broker
+solace.java.msgVpn=replace-msgVpn-here
+solace.java.oauth2ClientRegistrationId=my-oauth2-client ## Refers to the Spring OAuth2 client registration-id defined above
+solace.java.apiProperties.AUTHENTICATION_SCHEME=AUTHENTICATION_SCHEME_OAUTH2
+```
+
+See
+the [Solace Java API OAuth2 Sample](../../solace-spring-boot-samples/solace-java-oauth2-sample-app)
+for an example of how to use OAuth2 authentication scheme.
+
+### Customizing OAuth2 Token Injection and Token Refresh
+
+The Solace Java API OAuth2 authentication scheme supports customizing the OAuth2 token injection and
+token refresh.
+
+Create your custom implementation of
+the [SolaceSessionOAuth2TokenProvider](../../solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/SolaceSessionOAuth2TokenProvider.java)
+interface to injection initial token.
+Refer [DefaultSolaceSessionOAuth2TokenProvider](../../solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/DefaultSolaceSessionOAuth2TokenProvider.java)
+for sample implementation.
+
+Similarly, create your custom implementation of
+the [SolaceOAuth2SessionEventHandler](../../solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/SolaceOAuth2SessionEventHandler.java)
+interface to refresh token.
+Refer [DefaultSolaceOAuth2SessionEventHandler](../../solace-spring-boot-autoconfigure/solace-java-spring-boot-autoconfigure/src/main/java/com/solacesystems/jcsmp/DefaultSolaceOAuth2SessionEventHandler.java)
+for sample implementation.
+
+
## Resources
For more information about Spring Boot Auto-Configuration and Starters try these resources:
diff --git a/solace-spring-boot-starters/solace-java-spring-boot-starter/pom.xml b/solace-spring-boot-starters/solace-java-spring-boot-starter/pom.xml
index ac3aca1..a735d48 100644
--- a/solace-spring-boot-starters/solace-java-spring-boot-starter/pom.xml
+++ b/solace-spring-boot-starters/solace-java-spring-boot-starter/pom.xml
@@ -5,12 +5,12 @@
com.solace.spring.boot
solace-spring-boot-parent
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
../../solace-spring-boot-parent/pom.xml
solace-java-spring-boot-starter
- 5.0.1-SNAPSHOT
+ 5.1.0-SNAPSHOT
jar
Solace Spring Boot Starter - Java
diff --git a/solace-spring-boot-starters/solace-jms-spring-boot-starter/README.md b/solace-spring-boot-starters/solace-jms-spring-boot-starter/README.md
index 5a7b089..c37a492 100644
--- a/solace-spring-boot-starters/solace-jms-spring-boot-starter/README.md
+++ b/solace-spring-boot-starters/solace-jms-spring-boot-starter/README.md
@@ -52,7 +52,7 @@ Note that you'll need to include version 3.1.0 or later to use Spring Boot relea
#### Using it with Gradle
```groovy
-compile("com.solace.spring.boot:solace-jms-spring-boot-starter:5.0.0")
+compile("com.solace.spring.boot:solace-jms-spring-boot-starter:5.1.0")
```
#### Using it with Maven
@@ -61,7 +61,7 @@ compile("com.solace.spring.boot:solace-jms-spring-boot-starter:5.0.0")
com.solace.spring.boot
solace-jms-spring-boot-starter
- 5.0.0
+ 5.1.0
```
diff --git a/solace-spring-boot-starters/solace-jms-spring-boot-starter/pom.xml b/solace-spring-boot-starters/solace-jms-spring-boot-starter/pom.xml
index 99de113..4408d75 100644
--- a/solace-spring-boot-starters/solace-jms-spring-boot-starter/pom.xml
+++ b/solace-spring-boot-starters/solace-jms-spring-boot-starter/pom.xml
@@ -5,12 +5,12 @@
com.solace.spring.boot
solace-spring-boot-parent
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
../../solace-spring-boot-parent/pom.xml
solace-jms-spring-boot-starter
- 5.0.1-SNAPSHOT
+ 5.1.0-SNAPSHOT
jar
Solace Spring Boot Starter - JMS
diff --git a/solace-spring-boot-starters/solace-spring-boot-starter/pom.xml b/solace-spring-boot-starters/solace-spring-boot-starter/pom.xml
index fe89f09..ed353f1 100644
--- a/solace-spring-boot-starters/solace-spring-boot-starter/pom.xml
+++ b/solace-spring-boot-starters/solace-spring-boot-starter/pom.xml
@@ -5,12 +5,12 @@
com.solace.spring.boot
solace-spring-boot-parent
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
../../solace-spring-boot-parent/pom.xml
solace-spring-boot-starter
- 2.0.1-SNAPSHOT
+ 2.1.0-SNAPSHOT
jar
Solace Spring Boot Starter