diff --git a/.github/workflows/ci-cd.yaml b/.github/workflows/ci-cd.yaml
new file mode 100644
index 0000000..c12545a
--- /dev/null
+++ b/.github/workflows/ci-cd.yaml
@@ -0,0 +1,130 @@
+name: Build and Publish
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ release:
+ types: [published]
+
+permissions:
+ contents: write
+ packages: write
+
+jobs:
+ build-and-publish:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: 17
+ cache: maven
+ server-id: ${{ github.event_name == 'release' && 'central' || 'github' }}
+ server-username: ${{ github.event_name == 'release' && 'CENTRAL_USERNAME' || 'GITHUB_ACTOR' }}
+ server-password: ${{ github.event_name == 'release' && 'CENTRAL_TOKEN' || 'GITHUB_TOKEN' }}
+ gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
+
+ - name: Read version from pom.xml
+ id: get-version
+ run: |
+ VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
+ echo "pom_version=$VERSION" >> $GITHUB_OUTPUT
+
+ - name: Validate pom.xml version has -SNAPSHOT suffix
+ run: |
+ POM_VERSION=${{ steps.get-version.outputs.pom_version }}
+ echo "POM version: $POM_VERSION"
+ if [[ "$POM_VERSION" != *"-SNAPSHOT" ]]; then
+ echo "::error::pom.xml version must end with -SNAPSHOT, but is '$POM_VERSION'"
+ exit 1
+ fi
+
+ - name: Validate release tag matches POM version
+ if: github.event_name == 'release'
+ run: |
+ TAG_VERSION=${GITHUB_REF#refs/tags/v}
+ POM_VERSION=${{ steps.get-version.outputs.pom_version }}
+ EXPECTED_TAG="${POM_VERSION%-SNAPSHOT}"
+ echo "GitHub tag: $TAG_VERSION"
+ echo "Expected from POM: $EXPECTED_TAG"
+ if [ "$TAG_VERSION" != "$EXPECTED_TAG" ]; then
+ echo "::error::Tag v$TAG_VERSION does not match pom.xml version $POM_VERSION without -SNAPSHOT suffix"
+ exit 1
+ fi
+
+ - name: Build and test
+ run: mvn --batch-mode verify
+ env:
+ GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
+
+ - name: Publish Test Results
+ uses: dorny/test-reporter@dc3a92680fcc15842eef52e8c4606ea7ce6bd3f3 # v2.1.1
+ if: success() || failure()
+ with:
+ name: Test Results
+ path: target/surefire-reports/*.xml
+ reporter: java-junit
+ fail-on-error: false
+
+ - name: Publish to GitHub Packages
+ if: github.event_name == 'push'
+ run: mvn --batch-mode deploy -DskipTests -DaltDeploymentRepository=github::https://maven.pkg.github.com/${{ github.repository }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set release version for Maven Central
+ if: github.event_name == 'release'
+ run: |
+ POM_VERSION=${{ steps.get-version.outputs.pom_version }}
+ RELEASE_VERSION="${POM_VERSION%-SNAPSHOT}"
+ mvn versions:set -DnewVersion="$RELEASE_VERSION" -DgenerateBackupPoms=false
+
+ - name: Update release with Maven Central links
+ if: github.event_name == 'release'
+ run: |
+ RELEASE_VERSION="${{ steps.get-version.outputs.pom_version }}"
+ RELEASE_VERSION="${RELEASE_VERSION%-SNAPSHOT}"
+
+ # Get existing release body first
+ EXISTING_BODY=$(gh release view ${{ github.event.release.tag_name }} --json body -q .body)
+
+ # Start with existing body if it exists
+ if [ -n "$EXISTING_BODY" ] && [ "$EXISTING_BODY" != "null" ]; then
+ echo "$EXISTING_BODY" > release_body.md
+ echo "" >> release_body.md
+ echo "---" >> release_body.md
+ echo "" >> release_body.md
+ fi
+
+ # Append Maven Central links
+ cat << EOF >> release_body.md
+ ## 📦 Maven Central
+
+ This release is available at https://central.sonatype.com/artifact/com.scalepoint/oauth-token-client/${RELEASE_VERSION}
+
+ \`\`\`xml
+
+ com.scalepoint
+ oauth-token-client
+ ${RELEASE_VERSION}
+
+ \`\`\`
+ EOF
+
+ # Update the release
+ gh release edit ${{ github.event.release.tag_name }} --notes-file release_body.md
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Publish to Maven Central
+ if: github.event_name == 'release'
+ run: mvn --batch-mode deploy -DskipTests
+ env:
+ CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
+ CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}
diff --git a/README.md b/README.md
index 1eb0cd0..23e7e15 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ Install with Maven:
com.scalepoint
oauth-token-client
- 1.1.1
+ 2.0.0
```
diff --git a/pom.xml b/pom.xml
index acbc55a..490b0f0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,23 +1,24 @@
-
+
4.0.0
com.scalepoint
oauth-token-client
- 1.2-SNAPSHOT
+ 2.0.0-SNAPSHOT
jar
OAuth2 Token Endpoint Client
Client helper for OAuth 2.0 Token endpoint. Supports "Client Credentials" flow with "client_secret", RS256 JWT "client_assertion" and custom grants.
- https://github.com/Scalepoint/oauth-token-java-client
+ https://github.com/scalepoint-tech/oauth-token-java-client
- Scalepoint Technologies Ltd.
+ Scalepoint A/S
Yuriy Ostapenko
- uncleyo@users.noreply.github.com
- Scalepoint Technologies Ltd.
+ yuriyostapenko@users.noreply.github.com
+ Scalepoint A/S
https://scalepoint.com
@@ -28,149 +29,139 @@
- scm:git:git@github.com:Scalepoint/oauth-token-java-client.git
- scm:git:git@github.com:Scalepoint/oauth-token-java-client.git
- https://github.com/Scalepoint/oauth-token-java-client.git
+ scm:git:git@github.com:scalepoint-tech/oauth-token-java-client.git
+ scm:git:git@github.com:scalepoint-tech/oauth-token-java-client.git
+ https://github.com/scalepoint-tech/oauth-token-java-client.git
UTF-8
+ 17
+ 0.8.0
+ 3.14.0
+ 3.3.1
+ 3.11.2
+ 3.2.8
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.5.1
-
- 1.6
- 1.6
-
-
-
- org.apache.maven.plugins
- maven-jar-plugin
- 2.6
-
-
-
- true
-
-
-
-
-
- org.apache.maven.plugins
- maven-source-plugin
- 3.0.0
-
-
- attach-sources
-
- jar
-
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- 2.10.3
-
-
- attach-javadocs
-
- jar
-
-
-
-
-
- org.codehaus.mojo
- animal-sniffer-maven-plugin
- 1.15
-
-
- org.codehaus.mojo.signature
- java16
- 1.1
-
-
-
-
- ensure-java-1.6-class-library
- compile
-
- check
-
-
-
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
- 1.6
-
-
- sign-artifacts
- verify
-
- sign
-
-
-
-
-
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.7
- true
-
- ossrh
- https://oss.sonatype.org/
- false
-
-
-
-
-
-
io.jsonwebtoken
jjwt
- 0.6.0
+ 0.12.6
com.fasterxml.jackson.core
jackson-databind
- 2.12.7.1
-
-
- net.jodah
- expiringmap
- 0.5.6
+ 2.19.2
org.testng
testng
- 6.9.10
+ 7.11.0
test
org.mock-server
mockserver-netty
- 3.10.4
+ 5.15.0
test
-
- org.apache.httpcomponents
- httpclient
- 4.5.13
- test
-
-
-
+
+
+
+ ci
+
+
+ env.CI
+
+
+
+
+
+ org.sonatype.central
+ central-publishing-maven-plugin
+ ${central-publishing-maven-plugin.version}
+ true
+
+ central
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+ true
+ true
+
+ -Xlint:all
+ -Werror
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ ${maven-source-plugin.version}
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${maven-javadoc-plugin.version}
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ ${maven-gpg-plugin.version}
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+ --pinentry-mode
+ loopback
+
+
+
+
+
+
+
+
+
+
+
+
+ central
+ https://central.sonatype.com/api/v1/publish
+
+
+ github
+ https://maven.pkg.github.com/scalepoint-tech/oauth-token-java-client
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/scalepoint/oauth_token_client/CertificateUtil.java b/src/main/java/com/scalepoint/oauth_token_client/CertificateUtil.java
index 517df0e..e948971 100644
--- a/src/main/java/com/scalepoint/oauth_token_client/CertificateUtil.java
+++ b/src/main/java/com/scalepoint/oauth_token_client/CertificateUtil.java
@@ -1,7 +1,5 @@
package com.scalepoint.oauth_token_client;
-import io.jsonwebtoken.impl.Base64UrlCodec;
-
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -30,7 +28,7 @@ static String getThumbprint(Certificate certificate) {
md.update(der);
byte[] digest = md.digest();
- return new Base64UrlCodec().encode(digest);
+ return java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
}
static Boolean checkIfMatch(PrivateKey privateKey, X509Certificate certificate) {
diff --git a/src/main/java/com/scalepoint/oauth_token_client/ClientAssertionJwtFactory.java b/src/main/java/com/scalepoint/oauth_token_client/ClientAssertionJwtFactory.java
index 4e94011..6628d93 100644
--- a/src/main/java/com/scalepoint/oauth_token_client/ClientAssertionJwtFactory.java
+++ b/src/main/java/com/scalepoint/oauth_token_client/ClientAssertionJwtFactory.java
@@ -1,17 +1,16 @@
package com.scalepoint.oauth_token_client;
-import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwts;
-import io.jsonwebtoken.SignatureAlgorithm;
-import java.security.Key;
+import java.security.PrivateKey;
+import java.time.Instant;
import java.util.Date;
import java.util.UUID;
class ClientAssertionJwtFactory {
private final String tokenEndpointUri;
private final String clientId;
- private final Key key;
+ private final PrivateKey key;
private final String thumbprint;
public ClientAssertionJwtFactory(String tokenEndpointUri, String clientId, CertificateWithPrivateKey keyPair) {
@@ -21,23 +20,27 @@ public ClientAssertionJwtFactory(String tokenEndpointUri, String clientId, Certi
this.key = keyPair.getPrivateKey();
}
- public String CreateAssertionToken() {
- Date now = new Date();
+ public String createAssertionToken() {
+ Instant now = Instant.now();
// no need to have a long-lived token (clock skew should be accounted for on the server-side)
- Date expires = new Date(now.getTime() + 10000 /* 10 seconds */);
+ Instant expires = now.plusSeconds(10);
return Jwts.builder()
- .setHeaderParam("typ", "JWT")
- .setHeaderParam(JwsHeader.X509_CERT_SHA1_THUMBPRINT, thumbprint)
- .setHeaderParam(JwsHeader.KEY_ID, thumbprint)
- .setIssuer(clientId)
- .setSubject(clientId)
- .setAudience(tokenEndpointUri)
- .setId(UUID.randomUUID().toString())
- .setIssuedAt(now)
- .setNotBefore(now)
- .setExpiration(expires)
- .signWith(SignatureAlgorithm.RS256, key)
+ .header()
+ .type("JWT")
+ .add("x5t", thumbprint)
+ .keyId(thumbprint)
+ .and()
+ .claims()
+ .issuer(clientId)
+ .subject(clientId)
+ .audience().add(tokenEndpointUri).and()
+ .id(UUID.randomUUID().toString())
+ .issuedAt(Date.from(now))
+ .notBefore(Date.from(now))
+ .expiration(Date.from(expires))
+ .and()
+ .signWith(key, Jwts.SIG.RS256)
.compact();
}
diff --git a/src/main/java/com/scalepoint/oauth_token_client/ClientCredentialsGrantTokenClient.java b/src/main/java/com/scalepoint/oauth_token_client/ClientCredentialsGrantTokenClient.java
index 291c319..2a3ce64 100644
--- a/src/main/java/com/scalepoint/oauth_token_client/ClientCredentialsGrantTokenClient.java
+++ b/src/main/java/com/scalepoint/oauth_token_client/ClientCredentialsGrantTokenClient.java
@@ -40,7 +40,7 @@ public ClientCredentialsGrantTokenClient(String tokenEndpointUri, ClientCredenti
* @throws IOException Exception during token endpoint communication
*/
@SuppressWarnings("UnusedReturnValue")
- public String getToken(final String... scopes) throws IOException {
+ public String getToken(final String... scopes) throws IOException, InterruptedException {
return getTokenInternal(Collections.emptyList(), scopes);
}
diff --git a/src/main/java/com/scalepoint/oauth_token_client/CustomGrantTokenClient.java b/src/main/java/com/scalepoint/oauth_token_client/CustomGrantTokenClient.java
index f7647ff..9346e99 100644
--- a/src/main/java/com/scalepoint/oauth_token_client/CustomGrantTokenClient.java
+++ b/src/main/java/com/scalepoint/oauth_token_client/CustomGrantTokenClient.java
@@ -4,7 +4,6 @@
import java.util.ArrayList;
import java.util.List;
-@SuppressWarnings("WeakerAccess")
public abstract class CustomGrantTokenClient {
private final ClientCredentials clientCredentials;
private final TokenEndpointHttpClient tokenEndpointHttpClient;
@@ -26,7 +25,7 @@ public CustomGrantTokenClient(String tokenEndpointUri, ClientCredentials clientC
* @return Access token
* @throws IOException Exception during token endpoint communication
*/
- protected String getTokenInternal(final List parameters, final String... scopes) throws IOException {
+ protected String getTokenInternal(final List parameters, final String... scopes) throws IOException, InterruptedException {
final String scopeString =
(scopes == null || scopes.length < 1)
@@ -37,7 +36,7 @@ protected String getTokenInternal(final List parameters, final St
return cache.get(cacheKey, new TokenSource() {
@Override
- public ExpiringToken get() throws IOException {
+ public ExpiringToken get() throws IOException, InterruptedException {
List form = new ArrayList();
diff --git a/src/main/java/com/scalepoint/oauth_token_client/DigestUtil.java b/src/main/java/com/scalepoint/oauth_token_client/DigestUtil.java
index 2250ab1..e0f0314 100644
--- a/src/main/java/com/scalepoint/oauth_token_client/DigestUtil.java
+++ b/src/main/java/com/scalepoint/oauth_token_client/DigestUtil.java
@@ -1,24 +1,18 @@
package com.scalepoint.oauth_token_client;
-import javax.xml.bind.DatatypeConverter;
-import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.HexFormat;
class DigestUtil {
static String sha1Hex(String data) {
- MessageDigest digest = null;
try {
- digest = MessageDigest.getInstance("SHA-1");
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ byte[] digestBytes = digest.digest(data.getBytes(StandardCharsets.UTF_8));
+ return HexFormat.of().formatHex(digestBytes);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
- try {
- digest.update(data.getBytes("utf8"));
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
- byte[] digestBytes = digest.digest();
- return DatatypeConverter.printHexBinary(digestBytes);
}
}
diff --git a/src/main/java/com/scalepoint/oauth_token_client/InMemoryTokenCache.java b/src/main/java/com/scalepoint/oauth_token_client/InMemoryTokenCache.java
index 46f2696..d902689 100644
--- a/src/main/java/com/scalepoint/oauth_token_client/InMemoryTokenCache.java
+++ b/src/main/java/com/scalepoint/oauth_token_client/InMemoryTokenCache.java
@@ -1,27 +1,35 @@
package com.scalepoint.oauth_token_client;
-import net.jodah.expiringmap.ExpirationPolicy;
-import net.jodah.expiringmap.ExpiringMap;
-
import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.ReentrantLock;
+import java.time.Instant;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
/**
- * Simple in-memory token cache implementation
+ * Simple in-memory token cache implementation using ConcurrentHashMap
*/
public class InMemoryTokenCache implements TokenCache {
- private final ExpiringMap cacheMap;
- private final ReentrantLock lock = new ReentrantLock();
+ private final ConcurrentMap cache = new ConcurrentHashMap<>();
/**
- * Create new in-memory cache instance
+ * Wrapper class to store token with its expiration time
*/
- @SuppressWarnings("WeakerAccess")
- public InMemoryTokenCache() {
- this.cacheMap = ExpiringMap.builder()
- .variableExpiration()
- .build();
+ private static class CachedToken {
+ private final String token;
+ private final Instant expirationTime;
+
+ CachedToken(String token, long expiresInSeconds) {
+ this.token = token;
+ this.expirationTime = Instant.now().plusSeconds(expiresInSeconds);
+ }
+
+ String getToken() {
+ return token;
+ }
+
+ boolean isExpired() {
+ return Instant.now().isAfter(expirationTime);
+ }
}
/**
@@ -31,25 +39,29 @@ public InMemoryTokenCache() {
* @throws IOException Exception from underlying source
*/
@Override
- public String get(String cacheKey, TokenSource underlyingSource) throws IOException {
- String value = cacheMap.get(cacheKey);
- if (value == null) {
- lock.lock();
- try {
- value = cacheMap.get(cacheKey);
- if (value == null) {
- ExpiringToken token = underlyingSource.get();
- value = token.getToken();
- if (token.getExpiresInSeconds() > 0) {
- cacheMap.put(cacheKey, value, ExpirationPolicy.CREATED, token.getExpiresInSeconds(), TimeUnit.SECONDS);
- } else {
- throw new IOException("Authorization server does not provide token expiration information. Consider using NoCache or custom cache implementation to avoid performance penalty caused by locking.");
- }
- }
- } finally {
- lock.unlock();
- }
+ public String get(String cacheKey, TokenSource underlyingSource) throws IOException, InterruptedException {
+ CachedToken cachedToken = cache.get(cacheKey);
+
+ // Check if we have a valid (non-expired) token
+ if (cachedToken != null && !cachedToken.isExpired()) {
+ return cachedToken.getToken();
+ }
+
+ // Token is missing or expired - fetch a new one
+ ExpiringToken token = underlyingSource.get();
+ if (token.getExpiresInSeconds() <= 0) {
+ throw new IllegalArgumentException("Authorization server does not provide token expiration information. Consider using NoCache or custom cache implementation.");
}
- return value;
+
+ // Use compute for thread-safe update, even though we already have the token
+ CachedToken newToken = new CachedToken(token.getToken(), token.getExpiresInSeconds());
+ cache.compute(cacheKey, (key, existingToken) -> {
+ if (existingToken != null && !existingToken.isExpired()) {
+ return existingToken;
+ }
+ return newToken;
+ });
+
+ return newToken.getToken();
}
}
diff --git a/src/main/java/com/scalepoint/oauth_token_client/JwtBearerClientAssertionCredentials.java b/src/main/java/com/scalepoint/oauth_token_client/JwtBearerClientAssertionCredentials.java
index 5e45ad2..bb78503 100644
--- a/src/main/java/com/scalepoint/oauth_token_client/JwtBearerClientAssertionCredentials.java
+++ b/src/main/java/com/scalepoint/oauth_token_client/JwtBearerClientAssertionCredentials.java
@@ -32,7 +32,7 @@ public JwtBearerClientAssertionCredentials(String tokenEndpointUri, String clien
@Override
public List getPostParams() {
- String assertionToken = assertionFactory.CreateAssertionToken();
+ String assertionToken = assertionFactory.createAssertionToken();
ArrayList params = new ArrayList();
params.add(new NameValuePair("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"));
params.add(new NameValuePair("client_assertion", assertionToken));
diff --git a/src/main/java/com/scalepoint/oauth_token_client/NoCache.java b/src/main/java/com/scalepoint/oauth_token_client/NoCache.java
index f34ca2b..6c32aad 100644
--- a/src/main/java/com/scalepoint/oauth_token_client/NoCache.java
+++ b/src/main/java/com/scalepoint/oauth_token_client/NoCache.java
@@ -14,7 +14,7 @@ public class NoCache implements TokenCache {
* @throws IOException Exception from underlying source
*/
@Override
- public String get(String cacheKey, TokenSource underlyingSource) throws IOException {
+ public String get(String cacheKey, TokenSource underlyingSource) throws IOException, InterruptedException {
return underlyingSource.get().getToken();
}
}
diff --git a/src/main/java/com/scalepoint/oauth_token_client/ResourceScopedAccessGrantTokenClient.java b/src/main/java/com/scalepoint/oauth_token_client/ResourceScopedAccessGrantTokenClient.java
index 12c143e..ee6221f 100644
--- a/src/main/java/com/scalepoint/oauth_token_client/ResourceScopedAccessGrantTokenClient.java
+++ b/src/main/java/com/scalepoint/oauth_token_client/ResourceScopedAccessGrantTokenClient.java
@@ -23,7 +23,7 @@ public ResourceScopedAccessGrantTokenClient(String tokenEndpointUri, ClientCrede
* @throws IOException Exception during token endpoint communication
*/
@SuppressWarnings("UnusedReturnValue")
- public String getToken(ResourceScopedAccessGrantParameters parameters) throws IOException {
+ public String getToken(ResourceScopedAccessGrantParameters parameters) throws IOException, InterruptedException {
return getTokenInternal(getPostParams(parameters), parameters.getScope());
}
diff --git a/src/main/java/com/scalepoint/oauth_token_client/TokenCache.java b/src/main/java/com/scalepoint/oauth_token_client/TokenCache.java
index b718c4a..176484e 100644
--- a/src/main/java/com/scalepoint/oauth_token_client/TokenCache.java
+++ b/src/main/java/com/scalepoint/oauth_token_client/TokenCache.java
@@ -12,5 +12,5 @@ public interface TokenCache {
* @return Token from either cache or underlying source
* @throws IOException Exception from underlying cache
*/
- String get(String cacheKey, TokenSource underlyingSource) throws IOException;
+ String get(String cacheKey, TokenSource underlyingSource) throws IOException, InterruptedException;
}
diff --git a/src/main/java/com/scalepoint/oauth_token_client/TokenEndpointHttpClient.java b/src/main/java/com/scalepoint/oauth_token_client/TokenEndpointHttpClient.java
index c27f930..9bead75 100644
--- a/src/main/java/com/scalepoint/oauth_token_client/TokenEndpointHttpClient.java
+++ b/src/main/java/com/scalepoint/oauth_token_client/TokenEndpointHttpClient.java
@@ -3,68 +3,50 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import java.io.*;
-import java.net.HttpURLConnection;
-import java.net.URL;
+import java.io.IOException;
+import java.net.URI;
import java.net.URLEncoder;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
import java.util.List;
+import java.util.stream.Collectors;
class TokenEndpointHttpClient {
- private final static String UTF8 = "utf-8";
private final static ObjectMapper MAPPER = new ObjectMapper();
private final String tokenEndpointUri;
+ private final HttpClient httpClient;
public TokenEndpointHttpClient(String tokenEndpointUri) {
this.tokenEndpointUri = tokenEndpointUri;
+ this.httpClient = HttpClient.newBuilder()
+ .connectTimeout(Duration.ofSeconds(30))
+ .build();
}
- ExpiringToken getToken(List params) throws IOException {
-
- String body = formatRequest(params);
- HttpURLConnection c = makeRequest(body);
- String tokenResponse = readResponse(c);
- return parseResponse(tokenResponse);
- }
-
- private String formatRequest(List params) throws UnsupportedEncodingException {
- String body = "";
- for (NameValuePair p: params) {
- body += URLEncoder.encode(p.getName(), UTF8) + "=" + URLEncoder.encode(p.getValue(), UTF8) + "&";
+ ExpiringToken getToken(List params) throws IOException, InterruptedException {
+ String formData = params.stream()
+ .map(p -> URLEncoder.encode(p.getName(), StandardCharsets.UTF_8) + "=" +
+ URLEncoder.encode(p.getValue(), StandardCharsets.UTF_8))
+ .collect(Collectors.joining("&"));
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(tokenEndpointUri))
+ .timeout(Duration.ofSeconds(30))
+ .header("Content-Type", "application/x-www-form-urlencoded")
+ .POST(HttpRequest.BodyPublishers.ofString(formData, StandardCharsets.UTF_8))
+ .build();
+
+ HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
+
+ if (response.statusCode() != 200) {
+ throw new IOException("HTTP Status Code: " + response.statusCode() + ", " + response.body());
}
- body = body.substring(0, body.length()-1);
- return body;
- }
-
- private HttpURLConnection makeRequest(String body) throws IOException {
- URL u = new URL(tokenEndpointUri);
- HttpURLConnection c = (HttpURLConnection)u.openConnection();
- c.setRequestMethod("POST");
- c.setInstanceFollowRedirects(false);
- c.setDoInput(true);
- c.setDoOutput(true);
- c.setReadTimeout(30 * 1000);
- c.setConnectTimeout(30 * 1000);
- c.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
- c.setUseCaches(false);
- OutputStream outputStream = c.getOutputStream();
- OutputStreamWriter writer = new OutputStreamWriter(outputStream);
- writer.write(body);
- writer.flush();
- writer.close();
- return c;
- }
-
- private String readResponse(HttpURLConnection c) throws IOException {
- int statusCode = c.getResponseCode();
- if (statusCode != 200) {
- InputStream errorStream = c.getErrorStream();
- String errorMessage = errorStream != null
- ? ", " + readStream(errorStream)
- : "";
- throw new IOException("HTTP Status Code: "+statusCode+errorMessage);
- }
- return readStream(c.getInputStream());
+
+ return parseResponse(response.body());
}
private ExpiringToken parseResponse(String tokenResponse) throws IOException {
@@ -80,15 +62,4 @@ private ExpiringToken parseResponse(String tokenResponse) throws IOException {
return new ExpiringToken(accessToken, expiresInSeconds);
}
-
- private String readStream(InputStream stream) throws IOException {
- BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
- String content = "";
- String line;
- while ((line = reader.readLine()) != null) {
- content += line;
- }
- reader.close();
- return content;
- }
}
diff --git a/src/main/java/com/scalepoint/oauth_token_client/TokenSource.java b/src/main/java/com/scalepoint/oauth_token_client/TokenSource.java
index eccd3de..1830422 100644
--- a/src/main/java/com/scalepoint/oauth_token_client/TokenSource.java
+++ b/src/main/java/com/scalepoint/oauth_token_client/TokenSource.java
@@ -10,5 +10,5 @@ public interface TokenSource {
* @return Token
* @throws IOException
*/
- ExpiringToken get() throws IOException;
+ ExpiringToken get() throws IOException, InterruptedException;
}
diff --git a/src/test/java/com/scalepoint/oauth_token_client/BadRequestCallback.java b/src/test/java/com/scalepoint/oauth_token_client/BadRequestCallback.java
index e61ea63..ac2e2a8 100644
--- a/src/test/java/com/scalepoint/oauth_token_client/BadRequestCallback.java
+++ b/src/test/java/com/scalepoint/oauth_token_client/BadRequestCallback.java
@@ -1,12 +1,12 @@
package com.scalepoint.oauth_token_client;
-import org.mockserver.mock.action.ExpectationCallback;
+import org.mockserver.mock.action.ExpectationResponseCallback;
import org.mockserver.model.Header;
import org.mockserver.model.HttpRequest;
import org.mockserver.model.HttpResponse;
@SuppressWarnings("WeakerAccess")
-public class BadRequestCallback implements ExpectationCallback {
+public class BadRequestCallback implements ExpectationResponseCallback {
@Override
public HttpResponse handle(HttpRequest httpRequest) {
return new HttpResponse()
diff --git a/src/test/java/com/scalepoint/oauth_token_client/ClientAssertionJwtFactoryTest.java b/src/test/java/com/scalepoint/oauth_token_client/ClientAssertionJwtFactoryTest.java
index 342b73a..82e2f65 100644
--- a/src/test/java/com/scalepoint/oauth_token_client/ClientAssertionJwtFactoryTest.java
+++ b/src/test/java/com/scalepoint/oauth_token_client/ClientAssertionJwtFactoryTest.java
@@ -10,7 +10,6 @@
/**
* Validate that assertion token is generated according to the specification
*/
-@SuppressWarnings("unused")
public class ClientAssertionJwtFactoryTest {
private final static String TOKEN_ENDPOINT_URI = "https://foobar";
@@ -23,8 +22,9 @@ public void init() {
CertificateWithPrivateKey keyPair = TestCertificateHelper.load();
thumbprint = CertificateUtil.getThumbprint(keyPair.getCertificate());
ClientAssertionJwtFactory factory = new ClientAssertionJwtFactory(TOKEN_ENDPOINT_URI, CLIENT_ID, keyPair);
- String tokenString = factory.CreateAssertionToken();
- token = Jwts.parser().setSigningKey(keyPair.getPrivateKey()).parseClaimsJws(tokenString);
+ String tokenString = factory.createAssertionToken();
+ // Use the PUBLIC key from the certificate to verify the JWT signature, not the private key
+ token = Jwts.parser().verifyWith(keyPair.getCertificate().getPublicKey()).build().parseSignedClaims(tokenString);
}
@Test
@@ -44,31 +44,32 @@ public void testContainsX5t() {
@Test
public void testContainsValidIssuer() {
- Assert.assertEquals(token.getBody().getIssuer(), CLIENT_ID);
+ Assert.assertEquals(token.getPayload().getIssuer(), CLIENT_ID);
}
@Test
public void testContainsValidSubject() {
- Assert.assertEquals(token.getBody().getSubject(), CLIENT_ID);
+ Assert.assertEquals(token.getPayload().getSubject(), CLIENT_ID);
}
@Test
public void testContainsValidAudience() {
- Assert.assertEquals(token.getBody().getAudience(), TOKEN_ENDPOINT_URI);
+ // In newer JJWT versions, audience is returned as a Set
+ Assert.assertTrue(token.getPayload().getAudience().contains(TOKEN_ENDPOINT_URI));
}
@Test
public void testContainsJwtId() {
- Assert.assertNotNull(token.getBody().getId());
+ Assert.assertNotNull(token.getPayload().getId());
}
@Test
public void testContainsExpiration() {
- Assert.assertNotNull(token.getBody().getExpiration());
+ Assert.assertNotNull(token.getPayload().getExpiration());
}
@Test
public void testContainsIssuedAt() {
- Assert.assertNotNull(token.getBody().getIssuedAt());
+ Assert.assertNotNull(token.getPayload().getIssuedAt());
}
}
diff --git a/src/test/java/com/scalepoint/oauth_token_client/ClientAssertionTokenClientTest.java b/src/test/java/com/scalepoint/oauth_token_client/ClientAssertionTokenClientTest.java
index 5a8c403..a2d274d 100644
--- a/src/test/java/com/scalepoint/oauth_token_client/ClientAssertionTokenClientTest.java
+++ b/src/test/java/com/scalepoint/oauth_token_client/ClientAssertionTokenClientTest.java
@@ -1,6 +1,6 @@
package com.scalepoint.oauth_token_client;
-import org.mockserver.model.HttpCallback;
+import org.mockserver.model.HttpClassCallback;
import org.mockserver.model.HttpRequest;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -10,7 +10,6 @@
import static org.mockserver.matchers.Times.exactly;
import static org.mockserver.model.HttpRequest.request;
-@SuppressWarnings("unused")
public class ClientAssertionTokenClientTest extends MockServerTestBase {
@Test
@@ -23,7 +22,7 @@ public void testSuccess() throws Exception {
.withPath("/oauth2/token"),
exactly(1)
)
- .callback(HttpCallback.callback().withCallbackClass(SuccessfulExpectationCallback.class.getName()));
+ .respond(HttpClassCallback.callback(SuccessfulExpectationCallback.class));
ClientCredentialsGrantTokenClient tokenClient = new ClientCredentialsGrantTokenClient(
tokenEndpointUri,
@@ -47,7 +46,7 @@ public void testSuccessFromCache() throws Exception {
request,
exactly(1)
)
- .callback(HttpCallback.callback().withCallbackClass(SuccessfulExpectationCallback.class.getName()));
+ .respond(HttpClassCallback.callback(SuccessfulExpectationCallback.class));
ClientCredentialsGrantTokenClient tokenClient = new ClientCredentialsGrantTokenClient(
tokenEndpointUri,
@@ -72,7 +71,7 @@ public void testValidRequest() throws Exception {
.withPath("/oauth2/token"),
exactly(1)
)
- .callback(HttpCallback.callback().withCallbackClass(ValidClientAssertionExpectationCallback.class.getName()));
+ .respond(HttpClassCallback.callback(ValidClientAssertionExpectationCallback.class));
ClientCredentialsGrantTokenClient tokenClient = new ClientCredentialsGrantTokenClient(
tokenEndpointUri,
@@ -95,7 +94,7 @@ public void testFailure() throws Exception {
.withPath("/oauth2/token"),
exactly(1)
)
- .callback(HttpCallback.callback().withCallbackClass(BadRequestCallback.class.getName()));
+ .respond(HttpClassCallback.callback(BadRequestCallback.class));
ClientCredentialsGrantTokenClient tokenClient = new ClientCredentialsGrantTokenClient(
tokenEndpointUri,
@@ -118,7 +117,7 @@ public void testEmptyScopes() throws Exception {
.withPath("/oauth2/token"),
exactly(1)
)
- .callback(HttpCallback.callback().withCallbackClass(SuccessfulExpectationCallback.class.getName()));
+ .respond(HttpClassCallback.callback(SuccessfulExpectationCallback.class));
ClientCredentialsGrantTokenClient tokenClient = new ClientCredentialsGrantTokenClient(
tokenEndpointUri,
diff --git a/src/test/java/com/scalepoint/oauth_token_client/ClientSecretTokenClientTest.java b/src/test/java/com/scalepoint/oauth_token_client/ClientSecretTokenClientTest.java
index b6be83b..1b1c3f7 100644
--- a/src/test/java/com/scalepoint/oauth_token_client/ClientSecretTokenClientTest.java
+++ b/src/test/java/com/scalepoint/oauth_token_client/ClientSecretTokenClientTest.java
@@ -1,6 +1,6 @@
package com.scalepoint.oauth_token_client;
-import org.mockserver.model.HttpCallback;
+import org.mockserver.model.HttpClassCallback;
import org.mockserver.model.HttpRequest;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -10,7 +10,6 @@
import static org.mockserver.matchers.Times.exactly;
import static org.mockserver.model.HttpRequest.request;
-@SuppressWarnings("unused")
public class ClientSecretTokenClientTest extends MockServerTestBase {
@Test
@@ -23,7 +22,7 @@ public void testSuccess() throws Exception {
.withPath("/oauth2/token"),
exactly(1)
)
- .callback(HttpCallback.callback().withCallbackClass(SuccessfulExpectationCallback.class.getName()));
+ .respond(HttpClassCallback.callback().withCallbackClass(SuccessfulExpectationCallback.class.getName()));
ClientCredentialsGrantTokenClient tokenClient = new ClientCredentialsGrantTokenClient(tokenEndpointUri, new ClientSecretCredentials("clientid", "password"));
tokenClient.getToken("success");
@@ -40,7 +39,7 @@ public void testSuccessFromCache() throws Exception {
request,
exactly(1)
)
- .callback(HttpCallback.callback().withCallbackClass(SuccessfulExpectationCallback.class.getName()));
+ .respond(HttpClassCallback.callback().withCallbackClass(SuccessfulExpectationCallback.class.getName()));
ClientCredentialsGrantTokenClient tokenClient = new ClientCredentialsGrantTokenClient(tokenEndpointUri, new ClientSecretCredentials("clientid", "password"));
tokenClient.getToken("cache");
@@ -58,7 +57,7 @@ public void testValidRequest() throws Exception {
.withPath("/oauth2/token"),
exactly(1)
)
- .callback(HttpCallback.callback().withCallbackClass(ValidClientSecretExpectationCallback.class.getName()));
+ .respond(HttpClassCallback.callback().withCallbackClass(ValidClientSecretExpectationCallback.class.getName()));
ClientCredentialsGrantTokenClient tokenClient = new ClientCredentialsGrantTokenClient(tokenEndpointUri, new ClientSecretCredentials("clientid", "password"));
tokenClient.getToken("scope1", "scope2");
@@ -74,7 +73,7 @@ public void testFailure() throws Exception {
.withPath("/oauth2/token"),
exactly(1)
)
- .callback(HttpCallback.callback().withCallbackClass(BadRequestCallback.class.getName()));
+ .respond(HttpClassCallback.callback().withCallbackClass(BadRequestCallback.class.getName()));
ClientCredentialsGrantTokenClient tokenClient = new ClientCredentialsGrantTokenClient(tokenEndpointUri, new ClientSecretCredentials("clientid", "password"));
tokenClient.getToken("badRequest");
@@ -90,7 +89,7 @@ public void testEmptyScopes() throws Exception {
.withPath("/oauth2/token"),
exactly(1)
)
- .callback(HttpCallback.callback().withCallbackClass(SuccessfulExpectationCallback.class.getName()));
+ .respond(HttpClassCallback.callback().withCallbackClass(SuccessfulExpectationCallback.class.getName()));
ClientCredentialsGrantTokenClient tokenClient = new ClientCredentialsGrantTokenClient(tokenEndpointUri, new ClientSecretCredentials("clientid", "password"));
tokenClient.getToken();
diff --git a/src/test/java/com/scalepoint/oauth_token_client/ResourceScopedAccessGrantClientTest.java b/src/test/java/com/scalepoint/oauth_token_client/ResourceScopedAccessGrantClientTest.java
index 79d64f4..a36b73b 100644
--- a/src/test/java/com/scalepoint/oauth_token_client/ResourceScopedAccessGrantClientTest.java
+++ b/src/test/java/com/scalepoint/oauth_token_client/ResourceScopedAccessGrantClientTest.java
@@ -1,6 +1,6 @@
package com.scalepoint.oauth_token_client;
-import org.mockserver.model.HttpCallback;
+import org.mockserver.model.HttpClassCallback;
import org.testng.annotations.Test;
import java.io.IOException;
@@ -8,7 +8,6 @@
import static org.mockserver.matchers.Times.exactly;
import static org.mockserver.model.HttpRequest.request;
-@SuppressWarnings("unused")
public class ResourceScopedAccessGrantClientTest extends MockServerTestBase {
@Test
@@ -21,7 +20,7 @@ public void testSuccess() throws Exception {
.withPath("/oauth2/token"),
exactly(1)
)
- .callback(HttpCallback.callback().withCallbackClass(SuccessfulExpectationCallback.class.getName()));
+ .respond(HttpClassCallback.callback().withCallbackClass(SuccessfulExpectationCallback.class.getName()));
ResourceScopedAccessGrantTokenClient tokenClient = new ResourceScopedAccessGrantTokenClient(
tokenEndpointUri,
@@ -44,7 +43,7 @@ public void testValidRequest() throws Exception {
.withPath("/oauth2/token"),
exactly(1)
)
- .callback(HttpCallback.callback().withCallbackClass(ValidResourceScopedAccessRequestCallback.class.getName()));
+ .respond(HttpClassCallback.callback().withCallbackClass(ValidResourceScopedAccessRequestCallback.class.getName()));
ResourceScopedAccessGrantTokenClient tokenClient = new ResourceScopedAccessGrantTokenClient(
tokenEndpointUri,
@@ -67,7 +66,7 @@ public void testFailure() throws Exception {
.withPath("/oauth2/token"),
exactly(1)
)
- .callback(HttpCallback.callback().withCallbackClass(BadRequestCallback.class.getName()));
+ .respond(HttpClassCallback.callback().withCallbackClass(BadRequestCallback.class.getName()));
ResourceScopedAccessGrantTokenClient tokenClient = new ResourceScopedAccessGrantTokenClient(
tokenEndpointUri,
@@ -90,7 +89,7 @@ public void testEmptyScopes() throws Exception {
.withPath("/oauth2/token"),
exactly(1)
)
- .callback(HttpCallback.callback().withCallbackClass(SuccessfulExpectationCallback.class.getName()));
+ .respond(HttpClassCallback.callback().withCallbackClass(SuccessfulExpectationCallback.class.getName()));
ResourceScopedAccessGrantTokenClient tokenClient = new ResourceScopedAccessGrantTokenClient(
tokenEndpointUri,
diff --git a/src/test/java/com/scalepoint/oauth_token_client/SuccessfulExpectationCallback.java b/src/test/java/com/scalepoint/oauth_token_client/SuccessfulExpectationCallback.java
index 233a58c..bff389e 100644
--- a/src/test/java/com/scalepoint/oauth_token_client/SuccessfulExpectationCallback.java
+++ b/src/test/java/com/scalepoint/oauth_token_client/SuccessfulExpectationCallback.java
@@ -1,12 +1,11 @@
package com.scalepoint.oauth_token_client;
-import org.mockserver.mock.action.ExpectationCallback;
+import org.mockserver.mock.action.ExpectationResponseCallback;
import org.mockserver.model.Header;
import org.mockserver.model.HttpRequest;
import org.mockserver.model.HttpResponse;
-@SuppressWarnings("WeakerAccess")
-public class SuccessfulExpectationCallback implements ExpectationCallback {
+public class SuccessfulExpectationCallback implements ExpectationResponseCallback {
@Override
public HttpResponse handle(HttpRequest httpRequest) {
return new HttpResponse()
diff --git a/src/test/java/com/scalepoint/oauth_token_client/ValidClientAssertionExpectationCallback.java b/src/test/java/com/scalepoint/oauth_token_client/ValidClientAssertionExpectationCallback.java
index 159380a..886190a 100644
--- a/src/test/java/com/scalepoint/oauth_token_client/ValidClientAssertionExpectationCallback.java
+++ b/src/test/java/com/scalepoint/oauth_token_client/ValidClientAssertionExpectationCallback.java
@@ -14,7 +14,7 @@ protected boolean isValid(HashMap params) {
return false;
CertificateWithPrivateKey keyPair = TestCertificateHelper.load();
- Jwts.parser().setSigningKey(keyPair.getPrivateKey()).parseClaimsJws(params.get("client_assertion"));
+ Jwts.parser().verifyWith(keyPair.getCertificate().getPublicKey()).build().parseSignedClaims(params.get("client_assertion"));
return true;
}
diff --git a/src/test/java/com/scalepoint/oauth_token_client/ValidRequestExpectationCallback.java b/src/test/java/com/scalepoint/oauth_token_client/ValidRequestExpectationCallback.java
index 3f6ce2c..6dcf595 100644
--- a/src/test/java/com/scalepoint/oauth_token_client/ValidRequestExpectationCallback.java
+++ b/src/test/java/com/scalepoint/oauth_token_client/ValidRequestExpectationCallback.java
@@ -1,17 +1,16 @@
package com.scalepoint.oauth_token_client;
-import org.apache.commons.lang3.CharEncoding;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
-import org.mockserver.mock.action.ExpectationCallback;
+import org.mockserver.mock.action.ExpectationResponseCallback;
import org.mockserver.model.HttpRequest;
import org.mockserver.model.HttpResponse;
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
-abstract class ValidRequestExpectationCallback implements ExpectationCallback {
+abstract class ValidRequestExpectationCallback implements ExpectationResponseCallback {
@Override
public HttpResponse handle(HttpRequest httpRequest) {
@@ -24,7 +23,7 @@ public HttpResponse handle(HttpRequest httpRequest) {
private boolean isValid(HttpRequest httpRequest) {
String body = (String) httpRequest.getBody().getValue();
- List parsedParams = URLEncodedUtils.parse(body, Charset.forName(CharEncoding.UTF_8));
+ List parsedParams = URLEncodedUtils.parse(body, StandardCharsets.UTF_8);
HashMap params = new HashMap();
for (NameValuePair p : parsedParams) params.put(p.getName(), p.getValue());
return isValid(params);
diff --git a/src/test/java/com/scalepoint/oauth_token_client/ValidResourceScopedAccessRequestCallback.java b/src/test/java/com/scalepoint/oauth_token_client/ValidResourceScopedAccessRequestCallback.java
index e8fd313..e66cd20 100644
--- a/src/test/java/com/scalepoint/oauth_token_client/ValidResourceScopedAccessRequestCallback.java
+++ b/src/test/java/com/scalepoint/oauth_token_client/ValidResourceScopedAccessRequestCallback.java
@@ -5,7 +5,6 @@
import java.util.HashMap;
public class ValidResourceScopedAccessRequestCallback extends ValidRequestExpectationCallback {
- @SuppressWarnings("RedundantIfStatement")
@Override
protected boolean isValid(HashMap params) {
if (!params.get("grant_type").equals("urn:scalepoint:params:oauth:grant-type:resource-scoped-access")) return false;
@@ -14,7 +13,7 @@ protected boolean isValid(HashMap params) {
return false;
CertificateWithPrivateKey keyPair = TestCertificateHelper.load();
- Jwts.parser().setSigningKey(keyPair.getPrivateKey()).parseClaimsJws(params.get("client_assertion"));
+ Jwts.parser().verifyWith(keyPair.getCertificate().getPublicKey()).build().parseSignedClaims(params.get("client_assertion"));
if (!params.get("resource").equals("resource")) return false;
if (!params.get("amr").equals("pwd otp mfa")) return false;