Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .woodpecker/publish-historical.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
steps:
publish-historical:
image: eclipse-temurin:11
environment:
GITHUB_PACKAGES_TOKEN:
from_secret: github_packages_token
GITHUB_ACTOR: quintoandar
commands:
- |
GROUP_PATH="mvn-repo/br/com/quintoandar/java-jwt"
for VERSION_DIR in "$GROUP_PATH"/*/; do
VERSION=$(basename "$VERSION_DIR")
BASE="$VERSION_DIR/java-jwt-$VERSION"
BASE_URL="https://maven.pkg.github.com/quintoandar/repository/br/com/quintoandar/java-jwt/$VERSION"
echo "Publishing $VERSION..."
curl -s -o /dev/null -w "%{http_code}" -u "quintoandar:$GITHUB_PACKAGES_TOKEN" \
--upload-file "${BASE}.pom" "$BASE_URL/java-jwt-$VERSION.pom" || true
curl -s -o /dev/null -w "%{http_code}" -u "quintoandar:$GITHUB_PACKAGES_TOKEN" \
--upload-file "${BASE}.jar" "$BASE_URL/java-jwt-$VERSION.jar" || true
curl -s -o /dev/null -w "%{http_code}" -u "quintoandar:$GITHUB_PACKAGES_TOKEN" \
--upload-file "${BASE}-sources.jar" "$BASE_URL/java-jwt-$VERSION-sources.jar" || true
curl -s -o /dev/null -w "%{http_code}" -u "quintoandar:$GITHUB_PACKAGES_TOKEN" \
--upload-file "${BASE}-javadoc.jar" "$BASE_URL/java-jwt-$VERSION-javadoc.jar" || true
echo " done"
done
when:
event: manual
12 changes: 12 additions & 0 deletions .woodpecker/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
steps:
java-jwt-publish:
image: eclipse-temurin:11
environment:
GITHUB_PACKAGES_TOKEN:
from_secret: github_packages_token
GITHUB_ACTOR: quintoandar
commands:
- chmod +x ./gradlew
- unset CI && ./gradlew -Pversion=${CI_COMMIT_TAG##java-jwt@} publish
when:
ref: refs/tags/java-jwt@*
47 changes: 42 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,48 @@
# java-jwt

JWT verifier using QuintoAndar public key.
> **Deprecated:** This library is being phased out. New services should use
> [auth-java](https://github.com/quintoandar/backend-services/tree/main/libraries/auth-java) instead.

## publishing
JWT parser for QuintoAndar services.

Run a `./gradlew publish` to generate the artifacts and commit all files in `mvn-repo` directory.
## Versioning

## usage
### v1.6.x and earlier

TODO
The library validated JWTs at parse time:

- Fetched the public RSA key from an external endpoint at runtime (QuintoAndar auth service or Keycloak realm info).
- Verified the JWT signature against that key.
- For Keycloak tokens specifically, also required the `exp` claim to be present.
- Threw `InvalidJwtException` (jose4j) when validation failed.

### v2.0.0+

This version was introduced as a transitional step to remove internal JWT validation from services,
making it possible to rotate signing keys without requiring coordinated secret updates across all consumers.

The library now only parses the JWT payload: it base64url-decodes the payload segment and returns the claims map.
No signature verification, no expiry check. JWT validation is handled at the infrastructure level by Istio,
so by the time the token reaches the application it has already been validated.

Changes from v1.6.x:
- `getPayload()` no longer throws `InvalidJwtException`.
- `QuintoAndarPublicKeyService` and `QuintoAndarKeycloakPublicKeyService` were removed.
- `main.url` and `keycloak.url` properties are no longer required.
- jose4j and commons-io dependencies were removed.

## Publishing

Run `./gradlew publish` to generate the artifacts and commit all files in the `mvn-repo` directory.

## Usage

Inject `QuintoAndarJwt` (for QuintoAndar tokens) or `QuintoAndarKeycloakJwt` (for Keycloak tokens).
Both beans are auto-configured via Spring Boot autoconfiguration.

```java
@Autowired
QuintoAndarJwt quintoAndarJwt;

Optional<Map<String, Object>> payload = quintoAndarJwt.getPayload(jwtString);
```
17 changes: 7 additions & 10 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
}

group 'br.com.quintoandar'
version '1.6.2'
version '2.0.0'

sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
Expand All @@ -22,8 +22,6 @@ dependencies {
compile('org.springframework:spring-context:5.0.8.RELEASE')
compile('org.springframework:spring-beans:5.0.8.RELEASE')
compile('org.codehaus.groovy:groovy-all:2.4.15')
compile('commons-io:commons-io:2.6')
compile('org.bitbucket.b_c:jose4j:0.9.3')
compile('net.bytebuddy:byte-buddy:1.8.22')
compile('com.fasterxml.jackson.core:jackson-databind:2.11.1')

Expand All @@ -42,11 +40,6 @@ test {
}
}

javadoc {
if(JavaVersion.current().isJava9Compatible()) {
options.addBooleanOption('html4', true)
}
}

task sourcesJar(type: Jar) {
from sourceSets.main.allJava
Expand All @@ -63,7 +56,6 @@ publishing {
mavenJava(MavenPublication) {
groupId = 'br.com.quintoandar'
artifactId = 'java-jwt'
version = '1.6.2'
from components.java
artifact sourcesJar
artifact javadocJar
Expand All @@ -83,7 +75,12 @@ publishing {

repositories {
maven {
url = "$projectDir/mvn-repo"
name = "GitHubPackages"
url = "https://maven.pkg.github.com/quintoandar/repository"
credentials {
username = System.getenv("GITHUB_ACTOR") ?: ""
password = System.getenv("GITHUB_PACKAGES_TOKEN") ?: ""
}
}
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
f6799e39c73167ef7d76c90bd6488b7b
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a0bdbe3bb10e5ccd50423627edf9bc5a6c5be897
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
09404840c93b48fd07353c6aa7ecd26f
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ade26b27ec539f38b17720bc1d3a4926eefd8d9a
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1bbbbbf6f023ece10087dd9828c3b61e
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
898269e2739fd89c9eb102c3e998d8ea4a354793
53 changes: 53 additions & 0 deletions mvn-repo/br/com/quintoandar/java-jwt/2.0.0/java-jwt-2.0.0.pom
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>br.com.quintoandar</groupId>
<artifactId>java-jwt</artifactId>
<version>2.0.0</version>
<name>5A Java JWT</name>
<description>Java JWT parser using QuintoAndar public key or Keycloak Quintoandar</description>
<scm>
<connection>scm:git:git@github.com:quintoandar/java-jwt.git</connection>
<developerConnection>scm:git:git@github.com:quintoandar/java-jwt.git</developerConnection>
<url>https://github.com/quintoandar/java-jwt</url>
</scm>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.0.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.0.8.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.15</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.8.22</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
01ff46e836d635ced5c34cd383f53948
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
42029f176f8ff8bbce2e72863ae184fcf4536721
5 changes: 3 additions & 2 deletions mvn-repo/br/com/quintoandar/java-jwt/maven-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<groupId>br.com.quintoandar</groupId>
<artifactId>java-jwt</artifactId>
<versioning>
<release>1.6.2</release>
<release>2.0.0</release>
<versions>
<version>1.0</version>
<version>1.1</version>
Expand All @@ -14,7 +14,8 @@
<version>1.6</version>
<version>1.6.1</version>
<version>1.6.2</version>
<version>2.0.0</version>
</versions>
<lastUpdated>20231130193215</lastUpdated>
<lastUpdated>20260618193458</lastUpdated>
</versioning>
</metadata>
Original file line number Diff line number Diff line change
@@ -1 +1 @@
91fd2946e976fe87b31d5218e3976841
ea4e026f79573d490232c71e6076ed8d
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1decf6f5102929d1c100215ee3f5fb538730dfae
134c3f29bd56206955f382ebeb33cf0505c2583b
4 changes: 1 addition & 3 deletions src/main/java/br/com/quintoandar/javajwt/QuintoAndarJwt.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package br.com.quintoandar.javajwt;

import org.jose4j.jwt.consumer.InvalidJwtException;

import java.util.Map;
import java.util.Optional;

public interface QuintoAndarJwt {

Optional<Map<String, Object>> getPayload(String jwt) throws InvalidJwtException;
Optional<Map<String, Object>> getPayload(String jwt);

}
91 changes: 21 additions & 70 deletions src/main/java/br/com/quintoandar/javajwt/QuintoAndarJwtBean.java
Original file line number Diff line number Diff line change
@@ -1,90 +1,41 @@
package br.com.quintoandar.javajwt;

import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Map;
import java.util.Optional;

/**
* Parses JWTs issued by QuintoAndar's main auth service.
*
* <p>This class performs <strong>no validation</strong> (no signature verification, no expiry check).
* It simply decodes the JWT payload and returns the claims map. Validation is expected to be
* handled externally (e.g. by the identity provider or API gateway).
*
* <p>This class is intentionally kept separate from {@link QuintoAndarKeycloakJwtBean} for
* backward compatibility: consumers of this library may depend on either interface
* ({@link QuintoAndarJwt} or {@link QuintoAndarKeycloakJwt}) and both must remain injectable
* as distinct Spring beans.
*/
public class QuintoAndarJwtBean implements QuintoAndarJwt {

private static final Logger LOGGER = LoggerFactory.getLogger(QuintoAndarJwtBean.class);

private static final String KEY_ALGORITHM = "RSA";

private QuintoAndarPublicKeyService quintoAndarPublicKeyService;

private JwtConsumer jwtConsumer;

@Autowired
public QuintoAndarJwtBean(final QuintoAndarPublicKeyService quintoAndarPublicKeyService) {
this.quintoAndarPublicKeyService = quintoAndarPublicKeyService;
}
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

@Override
public Optional<Map<String, Object>> getPayload(final String jwt) throws InvalidJwtException {
public Optional<Map<String, Object>> getPayload(final String jwt) {
if (jwt == null) {
return Optional.empty();
}

if (!isReady()) {
try {
setup();
} catch (SetupException e) {
LOGGER.error("Failed to setup QuintoAndarJwtBean", e);
return Optional.empty();
}
}

final Map<String, Object> payload = jwtConsumer.processToClaims(jwt).getClaimsMap();
return Optional.ofNullable(payload);
}

private void setup() throws SetupException {
try {
LOGGER.info("Setting up QuintoAndarJwtBean");
final KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
final X509EncodedKeySpec keySpec = getPublicKeySpec();
final RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
final RsaJsonWebKey webKey = new RsaJsonWebKey(publicKey);
jwtConsumer = new JwtConsumerBuilder().setVerificationKey(webKey.getKey()).build();
} catch (NoSuchAlgorithmException | IOException | InvalidKeySpecException e) {
throw new SetupException(e);
final String[] parts = jwt.split("\\.");
final byte[] decoded = Base64.getUrlDecoder().decode(parts[1]);
final Map<String, Object> payload = OBJECT_MAPPER.readValue(decoded, Map.class);
return Optional.ofNullable(payload);
} catch (Exception e) {
return Optional.empty();
}
}

private boolean isReady() {
return jwtConsumer != null;
}

private X509EncodedKeySpec getPublicKeySpec() throws IOException {
final String encodedKey = strippedKey(quintoAndarPublicKeyService.fetchMainPublicKey());

final byte[] decodedKey = Base64.getDecoder().decode(encodedKey);

return new X509EncodedKeySpec(decodedKey);
}

private String strippedKey(final String publicKey) {
return publicKey.replaceAll("-----(BEGIN|END) PUBLIC KEY-----", "")
.replaceAll("\\n", "");
}

// visible for testing
protected void setQuintoAndarPublicKeyService(final QuintoAndarPublicKeyService quintoAndarPublicKeyService) {
this.quintoAndarPublicKeyService = quintoAndarPublicKeyService;
}

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package br.com.quintoandar.javajwt;

import org.jose4j.jwt.consumer.InvalidJwtException;

import java.util.Map;
import java.util.Optional;

public interface QuintoAndarKeycloakJwt {

Optional<Map<String, Object>> getPayload(String jwt) throws InvalidJwtException;
Optional<Map<String, Object>> getPayload(String jwt);

}
Loading