Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OAuth2 support for authentication with Solace PubSub+ Broker. #133

Merged
merged 13 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 16 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>com.solace.spring.boot</groupId>
<artifactId>solace-spring-boot-build</artifactId>
<version>2.0.1-SNAPSHOT</version>
<version>2.1.0-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Solace Spring Boot Build</name>
Expand All @@ -15,12 +15,14 @@
<repoName>SolaceProducts</repoName>

<!-- This is the version of Spring Boot we have targeted for this build -->
<spring.boot.version>3.0.6</spring.boot.version>
<spring.boot.version>3.3.1</spring.boot.version>

<solace.spring.boot.java-starter.version>5.0.1-SNAPSHOT
<solace.spring.boot.java-starter.version>5.1.0-SNAPSHOT
</solace.spring.boot.java-starter.version>
<solace.spring.boot.jms-starter.version>5.0.1-SNAPSHOT</solace.spring.boot.jms-starter.version>
<solace.spring.boot.starter.version>2.0.1-SNAPSHOT</solace.spring.boot.starter.version>
<solace.spring.boot.jms-starter.version>5.1.0-SNAPSHOT</solace.spring.boot.jms-starter.version>
<solace.spring.boot.starter.version>2.1.0-SNAPSHOT</solace.spring.boot.starter.version>
<testcontainers.version>1.19.8</testcontainers.version>
<solace.integration.test.support.version>1.1.2</solace.integration.test.support.version>
</properties>

<licenses>
Expand Down Expand Up @@ -54,6 +56,7 @@
<module>solace-spring-boot-samples/solace-java-sample-app</module>
<module>solace-spring-boot-samples/solace-jms-sample-app</module>
<module>solace-spring-boot-samples/solace-jms-sample-app-jndi</module>
<module>solace-spring-boot-samples/solace-java-oauth2-sample-app</module>
<module>solace-spring-boot-starters/solace-java-spring-boot-starter</module>
<module>solace-spring-boot-starters/solace-jms-spring-boot-starter</module>
<module>solace-spring-boot-starters/solace-spring-boot-starter</module>
Expand Down Expand Up @@ -85,6 +88,14 @@
<artifactId>solace-java-spring-boot-starter</artifactId>
<version>${solace.spring.boot.java-starter.version}</version>
</dependency>

<dependency>
<groupId>com.solace.test.integration</groupId>
<artifactId>solace-integration-test-support-bom</artifactId>
<version>${solace.integration.test.support.version}</version>
<!--<type>pom</type>-->
Nephery marked this conversation as resolved.
Show resolved Hide resolved
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
<parent>
<groupId>com.solace.spring.boot</groupId>
<artifactId>solace-spring-boot-parent</artifactId>
<version>2.0.1-SNAPSHOT</version>
<version>2.1.0-SNAPSHOT</version>
<relativePath>../../solace-spring-boot-parent/pom.xml</relativePath>
</parent>

<artifactId>solace-java-spring-boot-autoconfigure</artifactId>
<version>5.0.1-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>Solace Spring Boot Autoconfiguration - Java</name>
Expand All @@ -34,6 +34,13 @@
<version>${solace.jcsmp.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
<optional>true</optional>
</dependency>


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand All @@ -45,5 +52,50 @@
<version>1.19.0</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>1.9.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.solace.test.integration</groupId>
<artifactId>pubsubplus-junit-jupiter</artifactId>
<version>${solace.integration.test.support.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -99,7 +101,22 @@ public class SolaceJavaProperties {
*/
private final Map<String,String> apiProperties = new ConcurrentHashMap<>();

/**
* The Spring Security OAuth2 Client Registration Id
* <code>spring.security.oauth2.client.registration.&lt;registration-id&gt;</code> to use for OAuth2
* token
* retrieval. This field is required when the Solace session is configured to use OAuth2 via
* <code>solace.java.apiProperties.authentication_scheme=AUTHENTICATION_SCHEME_OAUTH2</code>
*/
private String oauth2ClientRegistrationId;

public String getOauth2ClientRegistrationId() {
return oauth2ClientRegistrationId;
}

public void setOauth2ClientRegistrationId(String oauth2ClientRegistrationId) {
this.oauth2ClientRegistrationId = oauth2ClientRegistrationId;
}

public String getHost() {
return host;
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* 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 Log logger = LogFactory.getLog(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;
}
}
Loading
Loading