This is an automated email from the ASF dual-hosted git repository.
ndipiazza pushed a commit to branch
TIKA-4237-Add-JWT-authentication-ability-to-the-http-fetcher
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to
refs/heads/TIKA-4237-Add-JWT-authentication-ability-to-the-http-fetcher by this
push:
new eccd23add add jwt fetching
eccd23add is described below
commit eccd23adde8832d83edd72f90b513245963edae1
Author: Nicholas DiPiazza <[email protected]>
AuthorDate: Fri Apr 5 16:17:47 2024 -0500
add jwt fetching
---
.../tika/pipes/fetcher/http/HttpFetcher.java | 82 +++++++++++++++++++++-
.../tika/pipes/fetcher/http/jwt/JwtCreds.java | 25 +++++++
.../pipes/fetcher/http/{ => jwt}/JwtGenerator.java | 50 ++++++-------
.../pipes/fetcher/http/jwt/JwtPrivateKeyCreds.java | 43 ++++++++++++
.../pipes/fetcher/http/jwt/JwtSecretCreds.java | 14 ++++
.../pipes/fetcher/http/jwt/JwtGeneratorTest.java | 41 +++++++++++
6 files changed, 225 insertions(+), 30 deletions(-)
diff --git
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/HttpFetcher.java
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/HttpFetcher.java
index 35e6f3e82..abf24825a 100644
---
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/HttpFetcher.java
+++
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/HttpFetcher.java
@@ -28,6 +28,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
+import java.security.PrivateKey;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -36,6 +37,7 @@ import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;
+import com.nimbusds.jose.JOSEException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
import org.apache.http.ConnectionClosedException;
@@ -69,6 +71,10 @@ import org.apache.tika.metadata.Property;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.pipes.fetcher.AbstractFetcher;
import org.apache.tika.pipes.fetcher.RangeFetcher;
+import org.apache.tika.pipes.fetcher.http.jwt.JwtCreds;
+import org.apache.tika.pipes.fetcher.http.jwt.JwtGenerator;
+import org.apache.tika.pipes.fetcher.http.jwt.JwtPrivateKeyCreds;
+import org.apache.tika.pipes.fetcher.http.jwt.JwtSecretCreds;
import org.apache.tika.utils.StringUtils;
/**
@@ -128,13 +134,20 @@ public class HttpFetcher extends AbstractFetcher
implements Initializable, Range
private int maxErrMsgSize = 10000;
//httpHeaders to capture in the metadata
- private Set<String> httpHeaders = new HashSet<>();
+ private final Set<String> httpHeaders = new HashSet<>();
+
+ private String jwtIssuer;
+ private String jwtSubject;
+ private int jwtExpiresInSeconds;
+ private String jwtSecret;
+ private String jwtPrivateKeyBase64;
+
+ private JwtCreds jwtCreds;
//When making the request, what User-Agent is sent.
//By default httpclient adds e.g. "Apache-HttpClient/4.5.13 (Java/x.y.z)"
private String userAgent = null;
-
@Override
public InputStream fetch(String fetchKey, Metadata metadata) throws
IOException, TikaException {
HttpGet get = new HttpGet(fetchKey);
@@ -143,9 +156,16 @@ public class HttpFetcher extends AbstractFetcher
implements Initializable, Range
.setMaxRedirects(maxRedirects)
.setRedirectsEnabled(true).build();
get.setConfig(requestConfig);
- if (! StringUtils.isBlank(userAgent)) {
+ if (!StringUtils.isBlank(userAgent)) {
get.setHeader(USER_AGENT, userAgent);
}
+ if (jwtCreds != null) {
+ try {
+ JwtGenerator.jwt(jwtCreds);
+ } catch (JOSEException e) {
+ throw new TikaException("Could not generate JWT", e);
+ }
+ }
return execute(get, metadata, httpClient, true);
}
@@ -437,17 +457,73 @@ public class HttpFetcher extends AbstractFetcher
implements Initializable, Range
this.userAgent = userAgent;
}
+ public String getJwtIssuer() {
+ return jwtIssuer;
+ }
+
+ @Field
+ public void setJwtIssuer(String jwtIssuer) {
+ this.jwtIssuer = jwtIssuer;
+ }
+
+ public String getJwtSubject() {
+ return jwtSubject;
+ }
+
+ @Field
+ public void setJwtSubject(String jwtSubject) {
+ this.jwtSubject = jwtSubject;
+ }
+
+ public int getJwtExpiresInSeconds() {
+ return jwtExpiresInSeconds;
+ }
+
+ @Field
+ public void setJwtExpiresInSeconds(int jwtExpiresInSeconds) {
+ this.jwtExpiresInSeconds = jwtExpiresInSeconds;
+ }
+
+ public String getJwtSecret() {
+ return jwtSecret;
+ }
+
+ @Field
+ public void setJwtSecret(String jwtSecret) {
+ this.jwtSecret = jwtSecret;
+ }
+
+ public String getJwtPrivateKeyBase64() {
+ return jwtPrivateKeyBase64;
+ }
+
+ @Field
+ public void setJwtPrivateKeyBase64(String jwtPrivateKeyBase64) {
+ this.jwtPrivateKeyBase64 = jwtPrivateKeyBase64;
+ }
+
@Override
public void initialize(Map<String, Param> params) throws
TikaConfigException {
httpClient = httpClientFactory.build();
HttpClientFactory cp = httpClientFactory.copy();
cp.setDisableContentCompression(true);
noCompressHttpClient = cp.build();
+ if (!StringUtils.isBlank(jwtPrivateKeyBase64)) {
+ PrivateKey key =
JwtPrivateKeyCreds.convertBase64ToPrivateKey(jwtPrivateKeyBase64);
+ jwtCreds = new JwtPrivateKeyCreds(key, jwtIssuer, jwtSubject,
jwtExpiresInSeconds);
+ } else if (!StringUtils.isBlank(jwtSecret)) {
+ jwtCreds = new
JwtSecretCreds(jwtSecret.getBytes(StandardCharsets.UTF_8), jwtIssuer,
+ jwtSubject, jwtExpiresInSeconds);
+ }
}
@Override
public void checkInitialization(InitializableProblemHandler problemHandler)
throws TikaConfigException {
+ if (!StringUtils.isBlank(jwtSecret) &&
!StringUtils.isBlank(jwtPrivateKeyBase64)) {
+ throw new TikaConfigException("Both JWT secret and JWT private key
base 64 were " +
+ "specified. Only one or the other is supported");
+ }
}
// For test purposes
diff --git
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtCreds.java
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtCreds.java
new file mode 100644
index 000000000..6ff445dfc
--- /dev/null
+++
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtCreds.java
@@ -0,0 +1,25 @@
+package org.apache.tika.pipes.fetcher.http.jwt;
+
+public abstract class JwtCreds {
+ private final String issuer;
+ private final String subject;
+ private final int expiresInSeconds;
+
+ public JwtCreds(String issuer, String subject, int expiresInSeconds) {
+ this.issuer = issuer;
+ this.subject = subject;
+ this.expiresInSeconds = expiresInSeconds;
+ }
+
+ public String getIssuer() {
+ return issuer;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public int getExpiresInSeconds() {
+ return expiresInSeconds;
+ }
+}
diff --git
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/JwtGenerator.java
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtGenerator.java
similarity index 51%
rename from
tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/JwtGenerator.java
rename to
tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtGenerator.java
index 13e936270..91b3e3295 100644
---
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/JwtGenerator.java
+++
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtGenerator.java
@@ -1,8 +1,5 @@
-package org.apache.tika.pipes.fetcher.http;
+package org.apache.tika.pipes.fetcher.http.jwt;
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.SecureRandom;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
@@ -17,21 +14,20 @@ import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
public class JwtGenerator {
- public static void main(String[] args) throws Exception {
- KeyPairGenerator keyPairGenerator =
KeyPairGenerator.getInstance("RSA");
- keyPairGenerator.initialize(2048);
- byte[] randomBytes = new byte[32];
- new SecureRandom().nextBytes(randomBytes);
- System.out.println(jwt(randomBytes, "nick", "subject", 120));
-
System.out.println(jwt(keyPairGenerator.generateKeyPair().getPrivate(), "nick",
"subject", 120));
+ public static String jwt(JwtCreds jwtCreds) throws JOSEException {
+ if (jwtCreds instanceof JwtSecretCreds) {
+ return jwtHS256((JwtSecretCreds) jwtCreds);
+ } else {
+ return jwtRS256((JwtPrivateKeyCreds) jwtCreds);
+ }
}
- public static String jwt(byte[] secret, String issuer, String subject,
- int expiresInSeconds)
+ public static String jwtHS256(JwtSecretCreds jwtSecretCreds)
throws JOSEException {
- JWSSigner signer = new MACSigner(secret);
+ JWSSigner signer = new MACSigner(jwtSecretCreds.getSecret());
- JWTClaimsSet claimsSet = getJwtClaimsSet(issuer, subject,
expiresInSeconds);
+ JWTClaimsSet claimsSet = getJwtClaimsSet(jwtSecretCreds.getIssuer(),
+ jwtSecretCreds.getSubject(),
jwtSecretCreds.getExpiresInSeconds());
SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256),
claimsSet);
signedJWT.sign(signer);
@@ -39,20 +35,12 @@ public class JwtGenerator {
return signedJWT.serialize();
}
- private static JWTClaimsSet getJwtClaimsSet(String issuer, String subject,
int expiresInSeconds) {
- return new JWTClaimsSet.Builder()
- .subject(subject)
- .issuer(issuer)
- .expirationTime(Date.from(Instant.now().plus(expiresInSeconds,
ChronoUnit.SECONDS)))
- .build();
- }
-
- public static String jwt(PrivateKey privateKey, String issuer, String
subject,
- int expiresInSeconds)
+ public static String jwtRS256(JwtPrivateKeyCreds jwtPrivateKeyCreds)
throws JOSEException {
- JWSSigner signer = new RSASSASigner(privateKey);
+ JWSSigner signer = new
RSASSASigner(jwtPrivateKeyCreds.getPrivateKey());
- JWTClaimsSet claimsSet = getJwtClaimsSet(issuer, subject,
expiresInSeconds);
+ JWTClaimsSet claimsSet =
getJwtClaimsSet(jwtPrivateKeyCreds.getIssuer(),
+ jwtPrivateKeyCreds.getSubject(),
jwtPrivateKeyCreds.getExpiresInSeconds());
SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256),
claimsSet);
@@ -60,4 +48,12 @@ public class JwtGenerator {
return signedJWT.serialize();
}
+
+ private static JWTClaimsSet getJwtClaimsSet(String issuer, String subject,
int expiresInSeconds) {
+ return new JWTClaimsSet.Builder()
+ .subject(subject)
+ .issuer(issuer)
+ .expirationTime(Date.from(Instant.now().plus(expiresInSeconds,
ChronoUnit.SECONDS)))
+ .build();
+ }
}
diff --git
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtPrivateKeyCreds.java
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtPrivateKeyCreds.java
new file mode 100644
index 000000000..aac7f155d
--- /dev/null
+++
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtPrivateKeyCreds.java
@@ -0,0 +1,43 @@
+package org.apache.tika.pipes.fetcher.http.jwt;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
+
+import org.apache.tika.exception.TikaConfigException;
+
+public class JwtPrivateKeyCreds extends JwtCreds {
+ private final PrivateKey privateKey;
+ public JwtPrivateKeyCreds(PrivateKey privateKey, String issuer, String
subject,
+ int expiresInSeconds) {
+ super(issuer, subject, expiresInSeconds);
+ this.privateKey = privateKey;
+ }
+
+ public PrivateKey getPrivateKey() {
+ return privateKey;
+ }
+
+ public static String convertPrivateKeyToBase64(PrivateKey privateKey) {
+ // Get the encoded form of the private key
+ byte[] privateKeyEncoded = privateKey.getEncoded();
+ // Encode the byte array using Base64
+ return Base64.getEncoder().encodeToString(privateKeyEncoded);
+ }
+
+ public static PrivateKey convertBase64ToPrivateKey(String privateKeyBase64)
+ throws TikaConfigException {
+ try {
+ byte[] privateKeyEncoded =
Base64.getDecoder().decode(privateKeyBase64);
+
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ PKCS8EncodedKeySpec keySpec = new
PKCS8EncodedKeySpec(privateKeyEncoded);
+ return keyFactory.generatePrivate(keySpec);
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new TikaConfigException("Could not convert private key
base64 to PrivateKey", e);
+ }
+ }
+}
diff --git
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtSecretCreds.java
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtSecretCreds.java
new file mode 100644
index 000000000..a8c121b23
--- /dev/null
+++
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtSecretCreds.java
@@ -0,0 +1,14 @@
+package org.apache.tika.pipes.fetcher.http.jwt;
+
+public class JwtSecretCreds extends JwtCreds {
+ private final byte[] secret;
+ public JwtSecretCreds(byte[] secret, String issuer, String subject, int
expiresInSeconds) {
+ super(issuer, subject, expiresInSeconds);
+ this.secret = secret;
+ }
+
+ public byte[] getSecret() {
+ return secret;
+ }
+
+}
diff --git
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/test/java/org/apache/tika/pipes/fetcher/http/jwt/JwtGeneratorTest.java
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/test/java/org/apache/tika/pipes/fetcher/http/jwt/JwtGeneratorTest.java
new file mode 100644
index 000000000..cb5b1ada5
--- /dev/null
+++
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/test/java/org/apache/tika/pipes/fetcher/http/jwt/JwtGeneratorTest.java
@@ -0,0 +1,41 @@
+package org.apache.tika.pipes.fetcher.http.jwt;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.interfaces.RSAPublicKey;
+
+import com.nimbusds.jose.JWSVerifier;
+import com.nimbusds.jose.crypto.MACVerifier;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
+import com.nimbusds.jwt.SignedJWT;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class JwtGeneratorTest {
+ @Test
+ void jwtSecret() throws Exception {
+ byte[] randomBytes = new byte[32];
+ new SecureRandom().nextBytes(randomBytes);
+ String jwt = JwtGenerator.jwtHS256(new JwtSecretCreds(randomBytes,
"nick", "subject",
+ 120));
+ SignedJWT signedJWT = SignedJWT.parse(jwt);
+ JWSVerifier verifier = new MACVerifier(randomBytes);
+ Assertions.assertTrue(signedJWT.verify(verifier));
+ }
+
+ @Test
+ void jwtPrivateKey() throws Exception {
+ KeyPairGenerator keyPairGenerator =
KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(2048);
+ byte[] randomBytes = new byte[32];
+ new SecureRandom().nextBytes(randomBytes);
+ KeyPair keyPair = keyPairGenerator.generateKeyPair();
+ String jwt = JwtGenerator.jwtRS256(
+ new JwtPrivateKeyCreds(keyPair.getPrivate(), "nick",
+ "subject", 120));
+ JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey)
keyPair.getPublic());
+ SignedJWT signedJWT = SignedJWT.parse(jwt);
+ Assertions.assertTrue(signedJWT.verify(verifier));
+ }
+}