Compare commits

...

104 Commits

Author SHA1 Message Date
2055df8f72 v3.0.3-jersey pom 2025-03-03 09:09:28 -05:00
991130708b Merge branch 'develop-jersey' into stable-jersey 2025-03-03 09:09:18 -05:00
477905aa33 Merge branch 'stable' into stable-jersey 2025-03-03 09:08:55 -05:00
65eedab7e6 v3.0.3 pom 2025-03-03 09:08:09 -05:00
4d3af40911 Merge branch 'develop' into stable 2025-03-03 09:07:50 -05:00
e074296de7 Merge branch 'develop' into develop-jersey 2025-03-03 09:07:40 -05:00
4353c673db fix HeaderAuthorizationFilter multimap usage 2025-03-03 09:07:15 -05:00
3bcec23ddd updated jersey comments/logging 2025-03-03 09:06:52 -05:00
eb88f0bfbc v3.0.2-jersey pom 2025-02-28 11:27:15 -05:00
079e04d386 Merge branch 'stable' into stable-jersey 2025-02-28 11:26:40 -05:00
d1a4cfdb89 Merge branch 'develop-jersey' into stable-jersey 2025-02-28 11:25:59 -05:00
2c15ce03a2 jersey v3.1.10 2025-02-28 11:25:34 -05:00
093936aabb Merge branch 'develop' into develop-jersey 2025-02-28 11:22:17 -05:00
eb2bd96ced v3.0.2 pom 2025-02-28 11:12:23 -05:00
c2147e1461 Merge branch 'develop' into stable 2025-02-28 11:12:03 -05:00
52cffffa8e upgrade maven plugins 2025-02-28 11:10:17 -05:00
e9bc191945 updated javadocs 2025-02-28 11:07:55 -05:00
795c9fef44 added header-based auth filter 2025-02-28 10:21:57 -05:00
63b48b8cbf Merge branch 'develop-jersey' into stable-jersey 2024-08-15 12:04:51 -04:00
eb7ac34f5e Merge branch 'develop' into stable 2024-08-15 11:52:02 -04:00
3f34ddb59d v3.x; jersey v3.x 2024-08-15 11:51:22 -04:00
b7ac0d25c9 v3.x; upgrade to jakarta 2024-08-15 11:44:12 -04:00
009bf7bb87 v2.0.10-jersey pom 2024-03-05 18:53:01 -05:00
b7c8e4479a Merge branch 'develop-jersey' into stable-jersey 2024-03-05 18:52:40 -05:00
271016dcf9 v2.0.10 pom 2024-03-05 18:49:07 -05:00
00ec8c972d Merge branch 'develop' into stable 2024-03-05 18:47:56 -05:00
255d64728e Merge branch 'develop' into develop-jersey 2024-03-05 18:47:23 -05:00
2cd9cc9484 removing spring annotations to support multiple clients 2024-03-05 18:46:07 -05:00
c61f2ca107 removing spring annotations to support multiple clients 2024-03-05 18:45:46 -05:00
1b2fbe5413 v2.0.9-jersey pom 2024-01-23 10:45:42 -05:00
80990f5335 Merge branch 'develop-jersey' into stable-jersey 2024-01-23 10:45:25 -05:00
f1852afe4e Merge branch 'develop' into develop-jersey 2024-01-23 10:43:58 -05:00
d4cd2f248c v2.0.9 pom 2024-01-23 10:42:51 -05:00
ac6f82a7f4 Merge branch 'develop' into stable 2024-01-23 10:41:11 -05:00
eb654c5488 added logging and param trimming 2024-01-23 10:40:37 -05:00
a8d2740329 v2.0.8-jersey pom 2023-06-29 10:56:57 -04:00
1f50bb8d3f Merge branch 'develop-jersey' into stable-jersey 2023-06-29 10:56:38 -04:00
0258cfcb9a v2.0.8 pom 2023-06-29 10:46:39 -04:00
6fc43b85f7 Merge branch 'develop' into stable 2023-06-29 10:46:11 -04:00
d386f35258 Merge branch 'develop' into develop-jersey 2023-06-29 10:31:57 -04:00
d29ccbef79 added connect/read timeout configurability 2023-06-29 10:31:42 -04:00
512f118354 v2.0.7 pom 2023-06-12 17:23:23 -04:00
7c98ce5545 v2.0.7-jersey pom 2023-06-12 17:19:39 -04:00
4c7e9da980 Merge branch 'develop-jersey' into stable-jersey 2023-06-12 17:19:21 -04:00
21255bfb3d Merge branch 'develop' into stable 2023-06-12 17:16:33 -04:00
bb20a78224 Merge branch 'develop' into develop-jersey 2023-06-12 17:16:14 -04:00
f84279a148 better oauth token expiration handling 2023-06-12 17:16:03 -04:00
518be99d4e v2.0.6-jersey pom 2023-06-12 16:14:51 -04:00
2dd7de15a3 Merge branch 'develop-jersey' into stable-jersey 2023-06-12 16:14:16 -04:00
f10fd39e97 v2.0.6 pom 2023-06-12 16:10:25 -04:00
eaed628f00 Merge branch 'develop' into stable 2023-06-12 16:08:46 -04:00
053b7a36ff removing javax.annotation conflict 2023-06-12 16:08:32 -04:00
fa96a8cfb8 Merge branch 'develop' into develop-jersey 2023-06-12 16:07:35 -04:00
44e82b3a6b fixed build issue without jersey/cxf 2023-06-12 16:05:37 -04:00
ab920aedd7 added OAuth filter logging 2023-06-12 16:02:33 -04:00
ab6b0d8da7 refacter OAuth filter error handling 2023-06-12 16:02:19 -04:00
c25988fc8f removing errand enum enablement 2023-05-30 18:23:39 -04:00
a4a1b3a9e6 v2.0.4-jersey 2023-05-30 18:02:43 -04:00
9e3c749f9b Merge branch 'develop-jersey' into stable-jersey 2023-05-30 18:02:14 -04:00
cd278bcc81 v2.0.4 pom 2023-05-30 18:00:33 -04:00
b64c408029 Merge branch 'develop' into stable 2023-05-30 18:00:06 -04:00
2cbb08e9cc Merge branch 'develop' into develop-jersey 2023-05-30 17:58:54 -04:00
6844044395 Merge branch 'develop' into develop-jersey 2023-05-30 17:54:44 -04:00
722a04fe16 v2.0.3-jersey 2023-05-30 12:45:35 -04:00
1e5fcf6ab8 Merge branch 'develop-jersey' into stable-jersey 2023-05-30 12:45:13 -04:00
ec6e7cc15d v2.0.3 pom 2023-05-30 12:44:41 -04:00
2451c634b7 Merge branch 'develop' into stable 2023-05-30 12:44:21 -04:00
efcfab1795 Merge branch 'develop' into develop-jersey 2023-05-30 12:44:04 -04:00
5c242d88f1 v2.0.2-jersey pom 2023-05-29 10:17:17 -04:00
e56152c47b Merge branch 'develop-jersey' into stable-jersey 2023-05-29 10:16:34 -04:00
ed00346e71 v2.0.2 pom 2023-05-29 10:15:38 -04:00
c001576b65 Merge branch 'develop' into stable 2023-05-29 10:11:16 -04:00
eb843c3cb6 upgraded jersey version 2023-05-29 10:07:46 -04:00
56aca63307 Merge branch 'develop' into develop-jersey 2023-05-29 09:58:48 -04:00
22ab063d4e Merge branch 'develop-jersey' into stable-jersey 2022-10-02 17:58:08 -04:00
6cb7afd9f1 Merge branch 'develop' into stable 2022-10-02 17:56:01 -04:00
5df5388bd2 Merge branch 'develop' into stable 2022-10-02 17:52:21 -04:00
df402790b4 added Jersey impl 2022-10-02 17:50:48 -04:00
5cda622082 v2.0.1 poms 2022-10-01 21:26:18 -04:00
e1191abaaa Merge branch 'develop' into stable 2022-10-01 21:25:26 -04:00
67ea4b72b3 POM release to v2.0.0 2022-10-01 00:53:17 -04:00
a574154c20 Merge branch 'develop' into stable 2022-10-01 00:52:53 -04:00
b1b19d8cb0 Merge branch 'develop' into stable 2022-05-02 18:33:27 -04:00
160766e0ef v1.1.6 pom 2022-04-07 23:36:39 -04:00
e167ac43cc Merge branch 'develop' into stable 2022-04-07 23:36:09 -04:00
a28fc13161 v1.1.5 pom 2022-03-08 13:32:45 -05:00
52c7e3cf6d Merge branch 'develop' into stable 2022-03-08 13:29:28 -05:00
bd7413bb48 v1.1.4 pom 2022-02-02 13:34:16 -05:00
75c518801f Merge branch 'develop' into stable 2022-02-02 13:33:47 -05:00
b3bc04467b v1.1.3 pom 2021-12-21 17:13:26 -05:00
88c8657a34 Merge branch 'develop' into stable 2021-12-21 17:12:58 -05:00
28b2478a08 v1.1.2 pom 2021-12-02 16:25:05 -05:00
dc63abc272 Merge branch 'develop' into stable 2021-12-02 16:20:39 -05:00
dd5d0f504a Merge branch 'develop' into stable 2021-10-27 14:54:54 -04:00
34d03a91e5 Merge branch 'develop' into stable 2021-09-03 12:42:25 -04:00
2584b8d668 v1.1.1 pom 2021-09-03 11:38:52 -04:00
a756b05f6c Merge branch 'develop' into stable 2021-09-03 11:38:22 -04:00
853b4e66cf Merge branch 'develop' into stable 2021-09-01 14:43:43 -04:00
f2032df0f3 Merge branch 'develop' into stable 2021-08-31 13:22:01 -04:00
8d3f4559bd v1.0.2 pom 2021-07-22 15:23:15 -04:00
3f476bba5c Merge branch 'develop' into stable 2021-07-22 15:22:49 -04:00
6292dde23b v1.0.1 pom 2021-07-07 11:07:04 -04:00
659f71c72c Merge branch 'develop' into stable 2021-07-07 11:06:35 -04:00
52f77dddbb v1.0.0 pom 2021-06-16 15:59:04 -04:00
18 changed files with 705 additions and 124 deletions

View File

@@ -1,7 +1,7 @@
# Common ReST Client Library # Common ReST Client Library
This project provides a library for Spring and POJO-based REST client instantiation. This project provides a library for Spring and POJO-based REST client instantiation. It includes special classes for the Jersey JAXRS-based client frameworks.
## Usage ## Usage
@@ -15,7 +15,7 @@ First, you will need to include the library in your project.
<dependency> <dependency>
<groupId>com.inteligr8</groupId> <groupId>com.inteligr8</groupId>
<artifactId>common-rest-client</artifactId> <artifactId>common-rest-client</artifactId>
<version>...</version> <version>...-jersey</version>
</dependency> </dependency>
... ...
</dependencies> </dependencies>
@@ -23,4 +23,65 @@ First, you will need to include the library in your project.
</project> </project>
``` ```
See the `cxf` and `jersey` branches for examples and more documentation. ### Spring Framework
#### Single Client
If you will only be declaring a single client in your Spring context, this is easy. You will just need two things. First, inject the single client into any of your Spring beans. You may do inject it into more than one. An example is below:
```java
@Component
public class ... {
@Autowired
@Qualifier("client.jersey") // may be optional
private Client client;
}
```
Next, you need to configure that client. You can do that by providing a single implementation of the `ClientConfiguration` (or `ClientJerseyConfiguration`) interface.
```java
@Configuration
public class ... implements ClientJerseyConfiguration {
...
}
```
#### Multiple Clients
If you will or may have multiple clients in your Spring context, there is an extra step. You will still need to define a `ClientConfiguration` for each. On top of that, you will need to create specialized implementations of each client. That special implementation will reference the configuration directly. An example is below.
```java
@Component("my.client")
public class MyClient extends ClientJerseyImpl {
@Autowired
public MyClient(MyClientConfiguration config) {
super(config);
}
}
```
You can then inject your client(s) into your Spring beans. Like the example below:
```java
@Component
public class ... {
@Autowired
private MyClient client;
@PostConstruct
public void init() {
MyJaxRsApi api = this.client.getApi(MyJaxRsApi.class);
}
}
### POJO
You do not have to use the Spring framework to use these classes. You can instantiate them directly. But you wil still need to create a `ClientConfiguration` as mentioned above.
```java
MyClientConfiguration config = new MyClientConfiguration();
...
ClientJerseyImpl client = new ClientJerseyImpl(config);
MyJaxRsApi api = client.getApi(MyJaxRsApi.class);
```

93
pom.xml
View File

@@ -6,11 +6,11 @@
<groupId>com.inteligr8</groupId> <groupId>com.inteligr8</groupId>
<artifactId>common-rest-client</artifactId> <artifactId>common-rest-client</artifactId>
<version>2.0-SNAPSHOT</version> <version>3.0.3-jersey</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>ReST API Client for Java</name> <name>ReST API Client for Java</name>
<description>A common library for building REST API clients</description> <description>A common library for building Jersey REST API clients</description>
<url>https://bitbucket.org/inteligr8/common-rest-client</url> <url>https://bitbucket.org/inteligr8/common-rest-client</url>
<licenses> <licenses>
@@ -40,15 +40,21 @@
<properties> <properties>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding> <project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source> <maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target> <maven.compiler.target>11</maven.compiler.target>
<junit.version>5.7.2</junit.version> <junit.version>5.12.0</junit.version>
<spring.version>5.3.27</spring.version> <spring.version>6.0.23</spring.version>
<jackson.version>2.15.1</jackson.version> <jackson.version>2.17.3</jackson.version>
<jersey.version>3.1.10</jersey.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId> <artifactId>spring-context</artifactId>
@@ -60,8 +66,8 @@
<version>${jackson.version}</version> <version>${jackson.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId> <groupId>com.fasterxml.jackson.jakarta.rs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId> <artifactId>jackson-jakarta-rs-json-provider</artifactId>
<version>${jackson.version}</version> <version>${jackson.version}</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -69,15 +75,20 @@
<artifactId>jackson-datatype-jsr310</artifactId> <artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version> <version>${jackson.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.17.0</version>
</dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
<version>1.7.36</version> <version>2.0.17</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>jakarta.ws.rs</groupId> <groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId> <artifactId>jakarta.ws.rs-api</artifactId>
<version>2.1.6</version> <version>3.1.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
@@ -97,13 +108,67 @@
<version>4.5.14</version> <version>4.5.14</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- Jersey libraries -->
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-proxy-client</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.4.0</version>
<executions>
<execution>
<id>add-jaxrs-src</id>
<goals><goal>add-source</goal></goals>
<configuration>
<sources>
<source>src/main/jersey</source>
</sources>
</configuration>
</execution>
<execution>
<id>add-test-src</id>
<goals><goal>add-test-source</goal></goals>
<configuration>
<sources>
<source>src/test/jersey</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>3.1.0</version> <version>3.4.0</version>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
@@ -114,7 +179,7 @@
</plugin> </plugin>
<plugin> <plugin>
<artifactId>maven-failsafe-plugin</artifactId> <artifactId>maven-failsafe-plugin</artifactId>
<version>3.1.0</version> <version>3.4.0</version>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
@@ -170,7 +235,7 @@
<plugin> <plugin>
<groupId>org.sonatype.plugins</groupId> <groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId> <artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.13</version> <version>1.6.14</version>
<configuration> <configuration>
<serverId>ossrh</serverId> <serverId>ossrh</serverId>
<nexusUrl>https://s01.oss.sonatype.org/</nexusUrl> <nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>

View File

@@ -14,7 +14,7 @@
*/ */
package com.inteligr8.rs; package com.inteligr8.rs;
import javax.ws.rs.client.ClientRequestFilter; import jakarta.ws.rs.client.ClientRequestFilter;
/** /**
* This is a marker that allows the developer to segregate, restrict, or limit * This is a marker that allows the developer to segregate, restrict, or limit

View File

@@ -17,8 +17,10 @@ package com.inteligr8.rs;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.Base64; import java.util.Base64;
import javax.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientRequestContext;
import javax.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.HttpHeaders;
import org.apache.commons.lang3.StringUtils;
/** /**
* This class implements a simple 2-credential (username &amp; password) based * This class implements a simple 2-credential (username &amp; password) based
@@ -32,12 +34,14 @@ public class BasicAuthorizationFilter implements AuthorizationFilter {
private final String password; private final String password;
/** /**
* This constructor instantiates the filter with required fields.
*
* @param username A username or access key. * @param username A username or access key.
* @param password A password or secret key. * @param password A password or secret key.
*/ */
public BasicAuthorizationFilter(String username, String password) { public BasicAuthorizationFilter(String username, String password) {
this.username = username; this.username = StringUtils.trimToNull(username);
this.password = password; this.password = StringUtils.trimToNull(password);
} }
/** /**

View File

@@ -14,8 +14,8 @@
*/ */
package com.inteligr8.rs; package com.inteligr8.rs;
import javax.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientRequestContext;
import javax.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.HttpHeaders;
/** /**
* This class implements a simple long living or proxied token-based * This class implements a simple long living or proxied token-based
@@ -32,6 +32,8 @@ public class BearerTokenAuthorizationFilter implements AuthorizationFilter {
private final String token; private final String token;
/** /**
* This constructor instantiates the filter with required fields.
*
* @param token A 'Bearer' token. * @param token A 'Bearer' token.
*/ */
public BearerTokenAuthorizationFilter(String token) { public BearerTokenAuthorizationFilter(String token) {

View File

@@ -14,34 +14,41 @@
*/ */
package com.inteligr8.rs; package com.inteligr8.rs;
import javax.ws.rs.client.ClientBuilder; import java.util.concurrent.TimeUnit;
import javax.ws.rs.client.WebTarget;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider;
/** /**
* A class that provides pre-configured JAX-RS Client &amp; WebTarget objects. * A class that provides pre-configured Jakarta RS Client &amp; WebTarget objects.
* *
* @author brian@inteligr8.com * @author brian@inteligr8.com
*/ */
public abstract class Client { public abstract class Client {
private final Object sync = new Object(); private final Object sync = new Object();
private javax.ws.rs.client.Client client; private jakarta.ws.rs.client.Client client;
/** /**
* This method retrieves the configuration for the client.
*
* @return The client configuration. * @return The client configuration.
*/ */
public abstract ClientConfiguration getConfig(); public abstract ClientConfiguration getConfig();
/** /**
* @return A pre-configured JAX-RS client (no URL) with configured authorization. * This method retrieves an anonymous cached instance of the underlying
* Jakarta RS client.
*
* @return A pre-configured Jakarta RS client (no URL) with configured authorization.
*/ */
public final javax.ws.rs.client.Client getClient() { public final jakarta.ws.rs.client.Client getClient() {
synchronized (this.sync) { synchronized (this.sync) {
if (this.client == null) if (this.client == null)
this.client = this.buildClient((AuthorizationFilter)null); this.client = this.buildClient((AuthorizationFilter)null);
@@ -51,10 +58,13 @@ public abstract class Client {
} }
/** /**
* This method retrieves either an anonymous cached instance or builds an
* authorized instance of the underlying Jakarta RS client.
*
* @param authFilter A dynamic authorization filter. * @param authFilter A dynamic authorization filter.
* @return A pre-configured JAX-RS client (no URL) with the specified authorization. * @return A pre-configured Jakarta RS client (no URL) with the specified authorization.
*/ */
public javax.ws.rs.client.Client getClient(AuthorizationFilter authFilter) { public jakarta.ws.rs.client.Client getClient(AuthorizationFilter authFilter) {
if (authFilter == null) { if (authFilter == null) {
return this.getClient(); return this.getClient();
} else { } else {
@@ -63,18 +73,17 @@ public abstract class Client {
} }
/** /**
* This method builds a new Jakarta RS client with optional authorization.
*
* @param authFilter A dynamic authorization filter. * @param authFilter A dynamic authorization filter.
* @return A pre-configured JAX-RS client (no URL) with the specified authorization. * @return A pre-configured Jakarta RS client (no URL) with the specified authorization.
*/ */
public final javax.ws.rs.client.Client buildClient(AuthorizationFilter authFilter) { public final jakarta.ws.rs.client.Client buildClient(AuthorizationFilter authFilter) {
ObjectMapper om = new ObjectMapper(); ObjectMapper om = new ObjectMapper();
om.registerModules(new JavaTimeModule()); om.registerModules(new JavaTimeModule());
this.getConfig().configureJacksonMapper(om); this.getConfig().configureJacksonMapper(om);
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider(om, JacksonJaxbJsonProvider.DEFAULT_ANNOTATIONS); JacksonJsonProvider provider = new JacksonJsonProvider(om, JacksonJsonProvider.BASIC_ANNOTATIONS);
provider.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
provider.disable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE);
this.getConfig().configureJacksonProvider(provider); this.getConfig().configureJacksonProvider(provider);
if (this.getConfig().isWrapRootValueEnabled()) if (this.getConfig().isWrapRootValueEnabled())
@@ -85,27 +94,36 @@ public abstract class Client {
ClientBuilder clientBuilder = ClientBuilder.newBuilder() ClientBuilder clientBuilder = ClientBuilder.newBuilder()
.register(provider) .register(provider)
.register(new LoggingFilter()); .register(new LoggingFilter());
if (this.getConfig().getConnectTimeoutInMillis() != null)
clientBuilder.connectTimeout(this.getConfig().getConnectTimeoutInMillis(), TimeUnit.MILLISECONDS);
if (this.getConfig().getResponseTimeoutInMillis() != null)
clientBuilder.readTimeout(this.getConfig().getResponseTimeoutInMillis(), TimeUnit.MILLISECONDS);
if (authFilter == null) if (authFilter == null)
authFilter = this.getConfig().createAuthorizationFilter(); authFilter = this.getConfig().createAuthorizationFilter();
if (authFilter != null) if (authFilter != null)
clientBuilder.register(authFilter); clientBuilder.register(authFilter);
this.buildClient(clientBuilder); this.buildClient(clientBuilder);
this.getConfig().configureClient(clientBuilder); this.getConfig().configureClient(clientBuilder);
return clientBuilder.build(); return clientBuilder.build();
} }
/** /**
* @param clientBuilder A client builder * This method allows sub-classes to extend the Jakarta RS client builder
* before the client is built.
*
* @param clientBuilder A Jakarta RS client builder.
*/ */
public void buildClient(ClientBuilder clientBuilder) { public void buildClient(ClientBuilder clientBuilder) {
// for extension purposes // for extension purposes
} }
/** /**
* @return A pre-configured JAX-RS target (client w/ base URL) with configured authorization. * This method builds an anonymous Jakarta RS target.
*
* @return A pre-configured Jakarta RS target (client w/ base URL) with configured authorization.
*/ */
public final WebTarget getTarget() { public final WebTarget getTarget() {
return this.getClient() return this.getClient()
@@ -113,8 +131,10 @@ public abstract class Client {
} }
/** /**
* This method builds an authorized Jakarta RS target.
*
* @param authFilter A dynamic authorization filter. * @param authFilter A dynamic authorization filter.
* @return A pre-configured JAX-RS target (client w/ base URL) with the specified authorization. * @return A pre-configured Jakarta RS target (client w/ base URL) with the specified authorization.
*/ */
public WebTarget getTarget(AuthorizationFilter authFilter) { public WebTarget getTarget(AuthorizationFilter authFilter) {
if (authFilter == null) { if (authFilter == null) {
@@ -126,10 +146,10 @@ public abstract class Client {
} }
/** /**
* This method retrieves a JAX-RS implementation of the specified API. * This method retrieves a Jakarta RS implementation of the specified API.
* *
* @param <T> A JAX-RS annotated API class. * @param <T> A Jakarta RS annotated API class.
* @param apiClass A JAX-RS annotated API class. * @param apiClass A Jakarta RS annotated API class.
* @return An instance of the API class. * @return An instance of the API class.
*/ */
public final <T> T getApi(Class<T> apiClass) { public final <T> T getApi(Class<T> apiClass) {
@@ -137,12 +157,12 @@ public abstract class Client {
} }
/** /**
* This method retrieves a JAX-RS implementation of the specified API with * This method retrieves a Jakarta RS implementation of the specified API with
* the specified authorization. * the specified authorization.
* *
* @param <T> A JAX-RS annotated API class. * @param <T> A Jakarta RS annotated API class.
* @param authFilter A dynamic authorization filter. * @param authFilter A dynamic authorization filter.
* @param apiClass A JAX-RS annotated API class. * @param apiClass A Jakarta RS annotated API class.
* @return An instance of the API class. * @return An instance of the API class.
*/ */
public abstract <T> T getApi(AuthorizationFilter authFilter, Class<T> apiClass); public abstract <T> T getApi(AuthorizationFilter authFilter, Class<T> apiClass);

View File

@@ -16,10 +16,10 @@ package com.inteligr8.rs;
import java.net.URI; import java.net.URI;
import javax.ws.rs.client.ClientBuilder;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider;
import jakarta.ws.rs.client.ClientBuilder;
/** /**
* This interface defines the configurable parameters of the clients; primarily * This interface defines the configurable parameters of the clients; primarily
@@ -30,120 +30,189 @@ import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
public interface ClientConfiguration { public interface ClientConfiguration {
/** /**
* @return The base or root URL of the service. * This method retrieves the base/root URL of the client service.
*
* @return The URL.
*/ */
String getBaseUrl(); String getBaseUrl();
/** /**
* @return The username for BASIC authentication. * This method retrieves the username to use in HTTP BASIC authentication/authorization.
*
* @return A username.
*/ */
default String getBasicAuthUsername() { default String getBasicAuthUsername() {
return null; return null;
} }
/** /**
* @return The corresponding password for the username in BASIC authentication. * This method retrieves the password to use in HTTP BASIC authentication/authorization.
*
* @return The corresponding password for the username.
*/ */
default String getBasicAuthPassword() { default String getBasicAuthPassword() {
return null; return null;
} }
/** /**
* @return The client ID for Client Enforcement authentication. * This method retrieves the client identifier to use in Client Enforcement authorization.
*
* @return A client identifier.
*/ */
default String getClientId() { default String getClientId() {
return null; return null;
} }
/** /**
* @return The corresponding client secret for the client ID in Client Enforcement authentication. * This method retrieves the client secret to use in Client Enforcement authorization.
*
* @return The corresponding client secret for the client identifier.
*/ */
default String getClientSecret() { default String getClientSecret() {
return null; return null;
} }
/** /**
* @return The token for BEARER authorization. * This method retrieves the token to use in HTTP BEARER authorization.
* This is provided in a response to the token URL.
*
* @return An access token.
*/ */
default String getBearerToken() { default String getBearerToken() {
return null; return null;
} }
/** /**
* @return The token URL for OAuth authorization. * This method retrieves the token URL to use for OAuth authorization.
* The value can be pulled from OAuth endpoint well-known meta-data. That
* endpoint or the token URL itself may also be provided OAuth IdP
* administrator.
*
* @return An OAuth token URL.
*/ */
default String getOAuthTokenUrl() { default String getOAuthTokenUrl() {
return null; return null;
} }
/** /**
* @return The client ID provided by the OAuth IdP administrator. * This method retrieves the client identifier to use in OAuth
* authorization. This is provided by the OAuth IdP administrator or
* tooling.
*
* @return A client identifier.
*/ */
default String getOAuthClientId() { default String getOAuthClientId() {
return this.getClientId(); return this.getClientId();
} }
/** /**
* @return The corresponding client secret for the client ID provided by the OAuth IdP administrator. * This method retrieves the client secret to use in OAuth authorization.
* This is provided by the OAuth IdP administrator or tooling.
*
* @return The corresponding client secret for the client identifier.
*/ */
default String getOAuthClientSecret() { default String getOAuthClientSecret() {
return this.getClientSecret(); return this.getClientSecret();
} }
/** /**
* @return The authorization code used in the OAuth Authorization Code flow. * This method retrieves the authorization code to use in OAuth
* Authorization Code flow. This is provided by the OAuth IdP
* administrator or tooling.
*
* @return An authorization code.
*/ */
default String getOAuthAuthCode() { default String getOAuthAuthCode() {
return null; return null;
} }
/** /**
* @return The redirect URL used in the OAuth Authorization Code flow. * This method retrieves the redirect URL to use in OAuth Authorization
* Code flow. This has meaning to the client-side web application.
*
* @return A URL for the OAuth flow to redirect to when complete.
*/ */
default String getOAuthAuthRedirectUri() { default String getOAuthAuthRedirectUri() {
return null; return null;
} }
/** /**
* @return The username used in the OAuth Password Grant flow. * This method retrieves the username to use in OAuth Password Grant flow.
* This is provided by the OAuth IdP administrator or tooling.
*
* @return A username.
*/ */
default String getOAuthUsername() { default String getOAuthUsername() {
return null; return null;
} }
/** /**
* @return The corresponding password for the username used in the OAuth Password Grant flow. * This method retrieves the password to use in OAuth Password Grant flow.
* This is provided by the OAuth IdP administrator or tooling.
*
* @return The corresponding password for the username.
*/ */
default String getOAuthPassword() { default String getOAuthPassword() {
return null; return null;
} }
/**
* This method retrieves the connection (before request sent) timeout for
* the client.
*
* @return A timeout in milliseconds.
*/
default Integer getConnectTimeoutInMillis() {
return null;
}
/**
* This method retrieves the response (after request sent) timeout for the
* client.
*
* @return A timeout in milliseconds.
*/
default Integer getResponseTimeoutInMillis() {
return null;
}
/** /**
* @return true to enable Jackson UNWRAP_ROOT_VALUE feature; false otherwise. * This method enables/disables the JackSON UNWRAP_ROOT_VALUE feature.
*
* @return `true` to enable; `false` otherwise.
*/ */
default boolean isUnwrapRootValueEnabled() { default boolean isUnwrapRootValueEnabled() {
return false; return false;
} }
/** /**
* @return true to enable Jackson WRAP_ROOT_VALUE feature; false otherwise. * This method enables/disables the JackSON WRAP_ROOT_VALUE feature.
*
* @return `true` to enable; `false` otherwise.
*/ */
default boolean isWrapRootValueEnabled() { default boolean isWrapRootValueEnabled() {
return false; return false;
} }
/** /**
* @param mapper A Jackson object mapper to configure. * This method allows sub-classes to extend the JackSON mapper
* configuration and behavior.
*
* @param mapper A JackSON object mapper.
*/ */
default void configureJacksonMapper(ObjectMapper mapper) { default void configureJacksonMapper(ObjectMapper mapper) {
} }
/** /**
* @param provider A Jackson JAX-RS provider to configure. * This method allows sub-classes to extend the JackSON JSON provider.
*
* @param provider A JackSON Jakarta RS provider.
*/ */
default void configureJacksonProvider(JacksonJaxbJsonProvider provider) { default void configureJacksonProvider(JacksonJsonProvider provider) {
} }

View File

@@ -14,7 +14,9 @@
*/ */
package com.inteligr8.rs; package com.inteligr8.rs;
import javax.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientRequestContext;
import org.apache.commons.lang3.StringUtils;
/** /**
* This class is the base for implementations of client authorization similar * This class is the base for implementations of client authorization similar
@@ -45,8 +47,8 @@ public class ClientEnforcementAuthorizationFilter implements AuthorizationFilter
* @param clientSecret A secret corresponding to the client ID. * @param clientSecret A secret corresponding to the client ID.
*/ */
public ClientEnforcementAuthorizationFilter(String clientId, String clientSecret) { public ClientEnforcementAuthorizationFilter(String clientId, String clientSecret) {
this.clientId = clientId; this.clientId = StringUtils.trimToNull(clientId);
this.clientSecret = clientSecret; this.clientSecret = StringUtils.trimToNull(clientSecret);
} }
/** /**

View File

@@ -0,0 +1,66 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.rs;
import jakarta.annotation.PostConstruct;
import jakarta.ws.rs.client.ClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class that provides pre-configured Jakarta RS Client &amp; WebTarget
* objects.
*
* @author brian@inteligr8.com
*/
public class ClientImpl extends Client {
private final Logger logger = LoggerFactory.getLogger(ClientImpl.class);
private ClientConfiguration config;
/**
* This constructor is for Spring or POJO use.
*
* @param config The client configuration.
*/
public ClientImpl(ClientConfiguration config) {
this.config = config;
}
/**
* This method is a placeholder.
*/
@PostConstruct
public void register() {
this.logger.info("API Base URL: {}", this.getConfig().getBaseUrl());
}
@Override
public void buildClient(ClientBuilder clientBuilder) {
}
@Override
public ClientConfiguration getConfig() {
return this.config;
}
@Override
public <T> T getApi(AuthorizationFilter authFilter, Class<T> apiClass) {
throw new UnsupportedOperationException();
}
}

View File

@@ -14,8 +14,8 @@
*/ */
package com.inteligr8.rs; package com.inteligr8.rs;
import javax.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientRequestContext;
import javax.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.HttpHeaders;
/** /**
* This class implements a proxied or forwarded authorization header based * This class implements a proxied or forwarded authorization header based
@@ -32,6 +32,8 @@ public class ForwardingAuthorizationFilter implements AuthorizationFilter {
private final String authorizationHeaderValue; private final String authorizationHeaderValue;
/** /**
* This constructor instantiates the filter with required fields.
*
* @param authorizationHeaderValue A previously used or formulated 'Authorization' header. * @param authorizationHeaderValue A previously used or formulated 'Authorization' header.
*/ */
public ForwardingAuthorizationFilter(String authorizationHeaderValue) { public ForwardingAuthorizationFilter(String authorizationHeaderValue) {

View File

@@ -0,0 +1,68 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.rs;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map.Entry;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import jakarta.ws.rs.client.ClientRequestContext;
/**
* This class implements a header-based authorization filter.
*
* @author brian@inteligr8.com
*/
public class HeaderAuthorizationFilter implements AuthorizationFilter {
private final MultiValueMap<String, Object> headers = new LinkedMultiValueMap<>();
/**
* This constructor instantiates the filter with required fields.
*
* @param headerName A header name.
* @param headerValue A header value.
*/
public HeaderAuthorizationFilter(String headerName, Object headerValue) {
this.headers.add(headerName, headerValue);
}
/**
* This method adds another header name/value to outgoing requests.
*
* @param headerName A header name.
* @param headerValue A header value.
* @return This class for fluent chaining.
*/
public HeaderAuthorizationFilter add(String headerName, Object headerValue) {
this.headers.add(headerName, headerValue);
return this;
}
/**
* This method applies the 'Authorization' header to the {@link ClientRequestContext}.
*
* @param requestContext A request context.
*/
@Override
public void filter(ClientRequestContext requestContext) throws UnsupportedEncodingException {
for (Entry<String, List<Object>> header : this.headers.entrySet())
requestContext.getHeaders().put(header.getKey(), header.getValue());
}
}

View File

@@ -16,12 +16,12 @@ package com.inteligr8.rs;
import java.io.IOException; import java.io.IOException;
import javax.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter; import jakarta.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.ClientResponseContext; import jakarta.ws.rs.client.ClientResponseContext;
import javax.ws.rs.client.ClientResponseFilter; import jakarta.ws.rs.client.ClientResponseFilter;
import javax.ws.rs.core.Form; import jakarta.ws.rs.core.Form;
import javax.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -58,7 +58,7 @@ public class LoggingFilter implements ClientRequestFilter, ClientResponseFilter
logger.trace("request: {} {}: {}", requestContext.getMethod(), requestContext.getUri(), logger.trace("request: {} {}: {}", requestContext.getMethod(), requestContext.getUri(),
((Form)requestContext.getEntity()).asMap()); ((Form)requestContext.getEntity()).asMap());
} else { } else {
this.loggerRequest.trace("request: {} {}: failed to output form", requestContext.getMethod(), requestContext.getUri()); logger.trace("request: {} {}: failed to output form", requestContext.getMethod(), requestContext.getUri());
} }
} else { } else {
this.logUnhandledRequest(requestContext, logger); this.logUnhandledRequest(requestContext, logger);

View File

@@ -16,7 +16,11 @@ package com.inteligr8.rs;
import java.net.URI; import java.net.URI;
import javax.ws.rs.core.Form; import jakarta.ws.rs.core.Form;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* This class implements the OAuth Authorization Code flow as an authorization * This class implements the OAuth Authorization Code flow as an authorization
@@ -25,6 +29,8 @@ import javax.ws.rs.core.Form;
* @author brian@inteligr8.com * @author brian@inteligr8.com
*/ */
public class OAuthAuthorizationCodeAuthorizationFilter extends OAuthAuthorizationFilter { public class OAuthAuthorizationCodeAuthorizationFilter extends OAuthAuthorizationFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final String code; private final String code;
private final URI redirectUri; private final URI redirectUri;
@@ -68,17 +74,22 @@ public class OAuthAuthorizationCodeAuthorizationFilter extends OAuthAuthorizatio
public OAuthAuthorizationCodeAuthorizationFilter(String tokenUrl, String clientId, String clientSecret, String code, URI redirectUri) { public OAuthAuthorizationCodeAuthorizationFilter(String tokenUrl, String clientId, String clientSecret, String code, URI redirectUri) {
super(tokenUrl, clientId, clientSecret); super(tokenUrl, clientId, clientSecret);
this.code = code; this.code = StringUtils.trimToNull(code);
this.redirectUri = redirectUri; this.redirectUri = redirectUri;
} }
@Override @Override
protected Form createForm() { protected Form createForm() {
Form form = new Form().param("grant_type", "authorization_code") this.logger.debug("Using OAuth grant_type 'authorization_code'");
.param("code", this.code); Form form = new Form().param("grant_type", "authorization_code");
if (this.redirectUri != null) if (this.redirectUri != null)
form.param("redirect_uri", this.redirectUri.toString()); form.param("redirect_uri", this.redirectUri.toString());
return form; return form;
} }
@Override
protected void extendFormSensitive(Form form) {
form.param("code", this.code);
}
} }

View File

@@ -16,15 +16,22 @@ package com.inteligr8.rs;
import java.util.Map; import java.util.Map;
import javax.ws.rs.WebApplicationException; import jakarta.ws.rs.WebApplicationException;
import javax.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.Client;
import javax.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity; import jakarta.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.WebTarget; import jakarta.ws.rs.client.Entity;
import javax.ws.rs.core.Form; import jakarta.ws.rs.client.WebTarget;
import javax.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.Form;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status.Family;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider;
/** /**
* This class is the base for implementations of OAuth authorization flows. * This class is the base for implementations of OAuth authorization flows.
@@ -33,6 +40,8 @@ import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
*/ */
public abstract class OAuthAuthorizationFilter implements AuthorizationFilter { public abstract class OAuthAuthorizationFilter implements AuthorizationFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final String tokenUrl; private final String tokenUrl;
private final String clientId; private final String clientId;
private final String clientSecret; private final String clientSecret;
@@ -40,6 +49,7 @@ public abstract class OAuthAuthorizationFilter implements AuthorizationFilter {
private String accessToken; private String accessToken;
private long expiration; private long expiration;
private String refreshToken; private String refreshToken;
private Long refreshTokenExpiration;
/** /**
* This constructor creates an OAuth-based authorization filter using the * This constructor creates an OAuth-based authorization filter using the
@@ -79,9 +89,9 @@ public abstract class OAuthAuthorizationFilter implements AuthorizationFilter {
*/ */
public OAuthAuthorizationFilter(String tokenUrl, String clientId, String clientSecret, String scope) { public OAuthAuthorizationFilter(String tokenUrl, String clientId, String clientSecret, String scope) {
this.tokenUrl = tokenUrl; this.tokenUrl = tokenUrl;
this.clientId = clientId; this.clientId = StringUtils.trimToNull(clientId);
this.clientSecret = clientSecret; this.clientSecret = StringUtils.trimToNull(clientSecret);
this.scope = scope; this.scope = StringUtils.trimToNull(scope);
} }
/** /**
@@ -91,8 +101,29 @@ public abstract class OAuthAuthorizationFilter implements AuthorizationFilter {
*/ */
@Override @Override
public void filter(ClientRequestContext requestContext) { public void filter(ClientRequestContext requestContext) {
if (this.accessToken == null || System.currentTimeMillis() > this.expiration) if (this.accessToken == null) {
this.requestToken(); this.requestToken();
} else if (System.currentTimeMillis() >= this.expiration) {
this.logger.trace("Access token expired; retrieving new one with refresh token");
if (this.refreshTokenExpiration != null && System.currentTimeMillis() >= this.refreshTokenExpiration.longValue()) {
this.logger.debug("Refresh token expired; performing full authentication");
this.refreshToken = null;
this.requestToken();
} else {
try {
this.requestToken();
} catch (WebApplicationException wae) {
if (wae.getResponse().getStatusInfo().getFamily() == Family.CLIENT_ERROR) {
this.logger.debug("Received OAuth response {} using refresh token; performing full authentication", wae.getResponse().getStatus());
this.refreshToken = null;
this.requestToken();
} else {
throw wae;
}
}
}
}
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + this.accessToken); requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + this.accessToken);
} }
@@ -111,29 +142,53 @@ public abstract class OAuthAuthorizationFilter implements AuthorizationFilter {
} }
form.param("client_id", this.clientId); form.param("client_id", this.clientId);
if (this.clientSecret != null)
form.param("client_secret", this.clientSecret);
if (this.scope != null) if (this.scope != null)
form.param("scope", this.scope); form.param("scope", this.scope);
this.extendRefreshTokenForm(form);
this.logger.trace("Sending OAuth request: {}", form);
if (this.refreshToken != null) {
this.extendRefreshFormSensitive(form);
} else {
this.extendFormSensitive(form);
}
if (this.clientSecret != null)
form.param("client_secret", this.clientSecret);
Entity<Form> entity = Entity.form(form); Entity<Form> entity = Entity.form(form);
WebTarget target = ClientBuilder.newBuilder() Client client = ClientBuilder.newBuilder()
.register(new JacksonJaxbJsonProvider()) .register(new JacksonJsonProvider())
.build() .build();
.target(this.tokenUrl); WebTarget target = client.target(this.tokenUrl);
long requestSendTime = System.currentTimeMillis();
@SuppressWarnings("unchecked") Response response = target.request().post(entity);
Map<String, Object> response = target.request().post(entity, Map.class);
if (response.containsKey("error")) this.logger.debug("Received OAuth response: {}", response.getStatus());
throw new WebApplicationException((String)response.get("error"), 400);
@SuppressWarnings("unchecked")
Map<String, Object> responseMap = response.readEntity(Map.class);
this.logger.trace("Received OAuth response: {}", responseMap);
this.accessToken = (String)response.get("access_token"); if (response.getStatusInfo().getFamily() != Family.SUCCESSFUL) {
this.expiration = System.currentTimeMillis() + ((Number)response.get("expires_in")).longValue() * 1000L; String code = (String) responseMap.get("error");
this.refreshToken = (String)response.get("refresh_token"); if (code != null) {
this.extendRefreshTokenResponse(response); String description = (String) responseMap.get("error_description");
throw new WebApplicationException(code + ": " + description, response.getStatus());
} else {
throw new WebApplicationException(response);
}
}
this.accessToken = (String)responseMap.get("access_token");
this.expiration = requestSendTime + ((Number)responseMap.get("expires_in")).longValue() * 1000L;
this.refreshToken = (String)responseMap.get("refresh_token");
if (responseMap.containsKey("refresh_token_expires_in"))
this.refreshTokenExpiration = requestSendTime + ((Number)responseMap.get("refresh_token_expires_in")).longValue() * 1000L;
} }
protected Form createRefreshForm() { protected Form createRefreshForm() {
@@ -143,10 +198,9 @@ public abstract class OAuthAuthorizationFilter implements AuthorizationFilter {
protected abstract Form createForm(); protected abstract Form createForm();
protected void extendRefreshTokenForm(Form form) { protected void extendRefreshFormSensitive(Form form) {
}
protected void extendRefreshTokenResponse(Map<String, Object> response) {
} }
protected abstract void extendFormSensitive(Form form);
} }

View File

@@ -14,7 +14,10 @@
*/ */
package com.inteligr8.rs; package com.inteligr8.rs;
import javax.ws.rs.core.Form; import jakarta.ws.rs.core.Form;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* This class implements the OAuth Client Credential flow as an authorization * This class implements the OAuth Client Credential flow as an authorization
@@ -23,6 +26,8 @@ import javax.ws.rs.core.Form;
* @author brian@inteligr8.com * @author brian@inteligr8.com
*/ */
public class OAuthClientCredentialAuthorizationFilter extends OAuthAuthorizationFilter { public class OAuthClientCredentialAuthorizationFilter extends OAuthAuthorizationFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/** /**
* @param tokenUrl The URL to the OAuth IdP token service. * @param tokenUrl The URL to the OAuth IdP token service.
@@ -35,7 +40,12 @@ public class OAuthClientCredentialAuthorizationFilter extends OAuthAuthorization
@Override @Override
protected Form createForm() { protected Form createForm() {
this.logger.debug("Using OAuth grant_type 'client_credentials'");
return new Form().param("grant_type", "client_credentials"); return new Form().param("grant_type", "client_credentials");
} }
@Override
protected void extendFormSensitive(Form form) {
}
} }

View File

@@ -14,7 +14,11 @@
*/ */
package com.inteligr8.rs; package com.inteligr8.rs;
import javax.ws.rs.core.Form; import jakarta.ws.rs.core.Form;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* This class implements the OAuth Password Grant flow as an authorization * This class implements the OAuth Password Grant flow as an authorization
@@ -23,6 +27,8 @@ import javax.ws.rs.core.Form;
* @author brian@inteligr8.com * @author brian@inteligr8.com
*/ */
public class OAuthPasswordGrantAuthorizationFilter extends OAuthAuthorizationFilter { public class OAuthPasswordGrantAuthorizationFilter extends OAuthAuthorizationFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final String username; private final String username;
private final String password; private final String password;
@@ -46,15 +52,20 @@ public class OAuthPasswordGrantAuthorizationFilter extends OAuthAuthorizationFil
*/ */
public OAuthPasswordGrantAuthorizationFilter(String tokenUrl, String clientId, String clientSecret, String username, String password) { public OAuthPasswordGrantAuthorizationFilter(String tokenUrl, String clientId, String clientSecret, String username, String password) {
super(tokenUrl, clientId, clientSecret); super(tokenUrl, clientId, clientSecret);
this.username = username; this.username = StringUtils.trimToNull(username);
this.password = password; this.password = StringUtils.trimToNull(password);
} }
@Override @Override
protected Form createForm() { protected Form createForm() {
this.logger.debug("Using OAuth grant_type 'password': {}", this.username);
return new Form().param("grant_type", "password") return new Form().param("grant_type", "password")
.param("username", this.username) .param("username", this.username);
.param("password", this.password); }
@Override
protected void extendFormSensitive(Form form) {
form.param("password", this.password);
} }
} }

View File

@@ -0,0 +1,35 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.rs;
/**
* This interface defines additional configurations specific to the Jersey
* Jakarta RS library and its nuances.
*
* @author brian@inteligr8.com
*/
public interface ClientJerseyConfiguration extends ClientConfiguration {
/**
* Jersey is automatically strict in its adherence to the ReST API
* specifications. It requires a body to PUT calls by default.
*
* @return true to require body in PUT calls; false to make it optional
*/
default boolean isPutBodyRequired() {
return true;
}
}

View File

@@ -0,0 +1,101 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.rs;
import jakarta.annotation.PostConstruct;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.ext.RuntimeDelegate;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.proxy.WebResourceFactory;
import org.glassfish.jersey.internal.RuntimeDelegateImpl;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class that provides pre-configured JAX-RS Client &amp; WebTarget objects
* for Jersey.
*
* @author brian@inteligr8.com
*/
public class ClientJerseyImpl extends Client {
private final Logger logger = LoggerFactory.getLogger(ClientJerseyImpl.class);
private ClientJerseyConfiguration config;
/**
* This constructor is for Spring or POJO use.
* @param config The client configuration.
*/
public ClientJerseyImpl(ClientJerseyConfiguration config) {
this.config = config;
}
/**
* This method registers the Jersey library as the default provider for the
* Jakarta RS specification.
*/
@PostConstruct
public void register() {
if (RuntimeDelegate.getInstance() == null) {
this.logger.info("Setting Jakarta RS runtime delegate to the Jersey library");
RuntimeDelegate.setInstance(new RuntimeDelegateImpl());
} else if (RuntimeDelegate.getInstance() instanceof RuntimeDelegateImpl) {
this.logger.info("Jakarta RS runtime delegate already the Jersey library");
} else {
this.logger.warn("Setting Jakarta RS runtime delegate to the Jersey library; was: " + RuntimeDelegate.getInstance().getClass().getName());
RuntimeDelegate.setInstance(new RuntimeDelegateImpl());
}
if (this.logger.isInfoEnabled())
this.logger.info("API Base URL: " + this.getConfig().getBaseUrl());
}
/**
* @param clientBuilder A client builder.
*/
@Override
public void buildClient(ClientBuilder clientBuilder) {
clientBuilder.register(MultiPartFeature.class);
if (!this.getConfig().isPutBodyRequired()) {
// allow PUT operations without body data
clientBuilder.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true);
}
}
/**
* @return The client configuration.
*/
public ClientJerseyConfiguration getConfig() {
return this.config;
}
/**
* This method retrieves a Jakarta RS implementation of the specified API
* with the specified authorization.
*
* @param authFilter A dynamic authorization filter.
* @param apiClass A Jakarta RS annotation API class.
* @return An instance of the API class.
*/
@Override
public <T> T getApi(AuthorizationFilter authFilter, Class<T> apiClass) {
return WebResourceFactory.newResource(apiClass, this.getTarget(authFilter));
}
}