diff --git a/README.md b/README.md index 7be4ea1..e3c5cb2 100644 --- a/README.md +++ b/README.md @@ -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> payload = quintoAndarJwt.getPayload(jwtString); +``` diff --git a/build.gradle b/build.gradle index 20652d1..4c4adf7 100644 --- a/build.gradle +++ b/build.gradle @@ -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 @@ -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') @@ -63,7 +61,7 @@ publishing { mavenJava(MavenPublication) { groupId = 'br.com.quintoandar' artifactId = 'java-jwt' - version = '1.6.2' + version = '2.0.0' from components.java artifact sourcesJar artifact javadocJar diff --git a/src/main/java/br/com/quintoandar/javajwt/QuintoAndarJwt.java b/src/main/java/br/com/quintoandar/javajwt/QuintoAndarJwt.java index 9900caa..3a593fc 100644 --- a/src/main/java/br/com/quintoandar/javajwt/QuintoAndarJwt.java +++ b/src/main/java/br/com/quintoandar/javajwt/QuintoAndarJwt.java @@ -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> getPayload(String jwt) throws InvalidJwtException; + Optional> getPayload(String jwt); } diff --git a/src/main/java/br/com/quintoandar/javajwt/QuintoAndarJwtBean.java b/src/main/java/br/com/quintoandar/javajwt/QuintoAndarJwtBean.java index 2e8824f..a3ee9c7 100644 --- a/src/main/java/br/com/quintoandar/javajwt/QuintoAndarJwtBean.java +++ b/src/main/java/br/com/quintoandar/javajwt/QuintoAndarJwtBean.java @@ -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. + * + *

This class performs no validation (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). + * + *

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> getPayload(final String jwt) throws InvalidJwtException { + public Optional> 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 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 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; - } - } diff --git a/src/main/java/br/com/quintoandar/javajwt/QuintoAndarKeycloakJwt.java b/src/main/java/br/com/quintoandar/javajwt/QuintoAndarKeycloakJwt.java index 4521c10..2b3059b 100644 --- a/src/main/java/br/com/quintoandar/javajwt/QuintoAndarKeycloakJwt.java +++ b/src/main/java/br/com/quintoandar/javajwt/QuintoAndarKeycloakJwt.java @@ -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> getPayload(String jwt) throws InvalidJwtException; + Optional> getPayload(String jwt); } diff --git a/src/main/java/br/com/quintoandar/javajwt/QuintoAndarKeycloakJwtBean.java b/src/main/java/br/com/quintoandar/javajwt/QuintoAndarKeycloakJwtBean.java index 6cf420a..800b3e2 100644 --- a/src/main/java/br/com/quintoandar/javajwt/QuintoAndarKeycloakJwtBean.java +++ b/src/main/java/br/com/quintoandar/javajwt/QuintoAndarKeycloakJwtBean.java @@ -1,94 +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 Keycloak (QuintoAndar realm). + * + *

This class performs no validation (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). + * + *

This class is intentionally kept separate from {@link QuintoAndarJwtBean} for + * backward compatibility: consumers of this library may depend on either interface + * ({@link QuintoAndarKeycloakJwt} or {@link QuintoAndarJwt}) and both must remain injectable + * as distinct Spring beans. + */ public class QuintoAndarKeycloakJwtBean implements QuintoAndarKeycloakJwt { - private static final Logger LOGGER = LoggerFactory.getLogger(QuintoAndarKeycloakJwtBean.class); - - private static final String KEY_ALGORITHM = "RSA"; - - private QuintoAndarKeycloakPublicKeyService quintoAndarKeycloakPublicKeyService; - - private JwtConsumer jwtConsumer; - - @Autowired - public QuintoAndarKeycloakJwtBean(final QuintoAndarKeycloakPublicKeyService quintoAndarKeycloakPublicKeyService) { - this.quintoAndarKeycloakPublicKeyService = quintoAndarKeycloakPublicKeyService; - } + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @Override - public Optional> getPayload(final String jwt) throws InvalidJwtException { + public Optional> getPayload(final String jwt) { if (jwt == null) { return Optional.empty(); } - if (!isReady()) { - try { - setup(); - } catch (SetupException e) { - LOGGER.error("Failed to setup QuintoAndarKeycloakJwtBean", e); - return Optional.empty(); - } - } - - final Map payload = jwtConsumer.processToClaims(jwt).getClaimsMap(); - return Optional.ofNullable(payload); - } - - private void setup() throws SetupException { try { - LOGGER.info("Setting up QuintoAndarKeycloakJwtBean"); - 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() - .setRequireExpirationTime() - .setSkipDefaultAudienceValidation() - .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 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(quintoAndarKeycloakPublicKeyService.fetchKeycloakPublicKey()); - - final byte[] decodedKey = Base64.getDecoder().decode(encodedKey); - - return new X509EncodedKeySpec(decodedKey); - } - - private String strippedKey(final String publicKey) { - return publicKey.replaceAll("\\n", ""); - } - - // visible for testing - protected void setQuintoAndarKeycloakPublicKeyService( - final QuintoAndarKeycloakPublicKeyService quintoAndarKeycloakPublicKeyService - ) { - this.quintoAndarKeycloakPublicKeyService = quintoAndarKeycloakPublicKeyService; - } - } diff --git a/src/main/java/br/com/quintoandar/javajwt/QuintoAndarKeycloakPublicKeyService.java b/src/main/java/br/com/quintoandar/javajwt/QuintoAndarKeycloakPublicKeyService.java deleted file mode 100644 index ba2ff39..0000000 --- a/src/main/java/br/com/quintoandar/javajwt/QuintoAndarKeycloakPublicKeyService.java +++ /dev/null @@ -1,48 +0,0 @@ -package br.com.quintoandar.javajwt; - -import br.com.quintoandar.javajwt.config.QuintoandarProperties; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.io.IOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.nio.charset.StandardCharsets; -import java.util.Map; - -public class QuintoAndarKeycloakPublicKeyService { - - private static final Logger LOGGER = LoggerFactory.getLogger(QuintoAndarKeycloakPublicKeyService.class); - - private static final int BUFFER_SIZE = 8192; - - private final QuintoandarProperties quintoandarProperties; - - @Autowired - public QuintoAndarKeycloakPublicKeyService(final QuintoandarProperties quintoandarProperties) { - this.quintoandarProperties = quintoandarProperties; - } - - protected String fetchKeycloakPublicKey() throws IOException { - final String JWT_KC_PATH = quintoandarProperties.getKeycloakUrl(); - - LOGGER.info("Opening connection to {}", JWT_KC_PATH); - final URL url = new URL(JWT_KC_PATH); - final URLConnection connection = url.openConnection(); - connection.connect(); - - LOGGER.info("Connection opened, fetching public key"); - final InputStream stream = new BufferedInputStream(url.openStream(), BUFFER_SIZE); - - // Keycloak's realm info endpoint returns a payload with a public_key property - final ObjectMapper om = new ObjectMapper(); - final Map keycloakRealmInfo = om.readValue(stream, Map.class); - - return (String) keycloakRealmInfo.get("public_key"); - } -} diff --git a/src/main/java/br/com/quintoandar/javajwt/QuintoAndarPublicKeyService.java b/src/main/java/br/com/quintoandar/javajwt/QuintoAndarPublicKeyService.java deleted file mode 100644 index 9059eef..0000000 --- a/src/main/java/br/com/quintoandar/javajwt/QuintoAndarPublicKeyService.java +++ /dev/null @@ -1,45 +0,0 @@ -package br.com.quintoandar.javajwt; - -import br.com.quintoandar.javajwt.config.QuintoandarProperties; -import org.apache.commons.io.IOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.nio.charset.StandardCharsets; - -public class QuintoAndarPublicKeyService { - - private static final Logger LOGGER = LoggerFactory.getLogger(QuintoAndarPublicKeyService.class); - - private static final int BUFFER_SIZE = 8192; - - private static final String AUTH_ENDPOINT = "/auth/key"; - - private final QuintoandarProperties quintoandarProperties; - - @Autowired - public QuintoAndarPublicKeyService(final QuintoandarProperties quintoandarProperties) { - this.quintoandarProperties = quintoandarProperties; - } - - // visible for testing - protected String fetchMainPublicKey() throws IOException { - final String JWT_MAIN_PATH = quintoandarProperties.getMainUrl() + AUTH_ENDPOINT; - LOGGER.info("Opening connection to {}", JWT_MAIN_PATH); - final URL url = new URL(JWT_MAIN_PATH); - final URLConnection connection = url.openConnection(); - connection.connect(); - - LOGGER.info("Connection opened, fetching public key"); - - final InputStream stream = new BufferedInputStream(url.openStream(), BUFFER_SIZE); - return IOUtils.toString(stream, StandardCharsets.UTF_8); - } - -} diff --git a/src/main/java/br/com/quintoandar/javajwt/QuintoandarJwtConfiguration.java b/src/main/java/br/com/quintoandar/javajwt/QuintoandarJwtConfiguration.java index c5df05b..5c38a30 100644 --- a/src/main/java/br/com/quintoandar/javajwt/QuintoandarJwtConfiguration.java +++ b/src/main/java/br/com/quintoandar/javajwt/QuintoandarJwtConfiguration.java @@ -1,38 +1,19 @@ package br.com.quintoandar.javajwt; -import br.com.quintoandar.javajwt.config.QuintoandarProperties; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class QuintoandarJwtConfiguration { - private final QuintoandarProperties quintoandarProperties; - - @Autowired - public QuintoandarJwtConfiguration(final QuintoandarProperties quintoandarProperties) { - this.quintoandarProperties = quintoandarProperties; - } - @Bean public QuintoAndarJwt quintoAndarJwt() { - return new QuintoAndarJwtBean(quintoAndarPublicKeyService()); + return new QuintoAndarJwtBean(); } @Bean public QuintoAndarKeycloakJwt quintoAndarKeycloakJwt() { - return new QuintoAndarKeycloakJwtBean(quintoAndarKeycloakPublicKeyService()); - } - - @Bean - public QuintoAndarPublicKeyService quintoAndarPublicKeyService() { - return new QuintoAndarPublicKeyService(quintoandarProperties); - } - - @Bean - public QuintoAndarKeycloakPublicKeyService quintoAndarKeycloakPublicKeyService() { - return new QuintoAndarKeycloakPublicKeyService(quintoandarProperties); + return new QuintoAndarKeycloakJwtBean(); } } diff --git a/src/main/java/br/com/quintoandar/javajwt/SetupException.java b/src/main/java/br/com/quintoandar/javajwt/SetupException.java deleted file mode 100644 index 340f1b1..0000000 --- a/src/main/java/br/com/quintoandar/javajwt/SetupException.java +++ /dev/null @@ -1,7 +0,0 @@ -package br.com.quintoandar.javajwt; - -public class SetupException extends Exception { - public SetupException(final Throwable t) { - super(t); - } -} diff --git a/src/main/java/br/com/quintoandar/javajwt/config/QuintoandarProperties.java b/src/main/java/br/com/quintoandar/javajwt/config/QuintoandarProperties.java deleted file mode 100644 index 67d7d04..0000000 --- a/src/main/java/br/com/quintoandar/javajwt/config/QuintoandarProperties.java +++ /dev/null @@ -1,22 +0,0 @@ -package br.com.quintoandar.javajwt.config; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class QuintoandarProperties { - - @Value("${main.url}") - private String mainUrl; - - @Value("${keycloak.url}") - private String keycloakUrl; - - public String getMainUrl() { - return mainUrl; - } - - public String getKeycloakUrl() { - return keycloakUrl; - } -} diff --git a/src/test/groovy/br/com/quintoandar/javajwt/QuintoAndarJwtSpec.groovy b/src/test/groovy/br/com/quintoandar/javajwt/QuintoAndarJwtSpec.groovy index 3a5882b..111b614 100644 --- a/src/test/groovy/br/com/quintoandar/javajwt/QuintoAndarJwtSpec.groovy +++ b/src/test/groovy/br/com/quintoandar/javajwt/QuintoAndarJwtSpec.groovy @@ -1,57 +1,12 @@ package br.com.quintoandar.javajwt -import org.jose4j.jwt.consumer.InvalidJwtException import spock.lang.Shared import spock.lang.Specification class QuintoAndarJwtSpec extends Specification { @Shared - def publicKeyServiceMock = Mock(QuintoAndarPublicKeyService) - - @Shared - def subject = GroovySpy(QuintoAndarJwtBean) - - @Shared - def fakePublicKey2048bit = "-----BEGIN PUBLIC KEY-----\n" + - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6A+CGRKw/xpaAqy/1eJl\n" + - "f5urRzmUWhgGVNedUhmgMudMBQIXPYVOWQQydWq/e+SaxdT7fRQE/Ae0eOCPgwty\n" + - "FmLoA/ugGT0AGF5juXBzCwXY9iLdIejQPDbbVVMICFteRCDkLaLZy4U3qj895TzZ\n" + - "Hg7gXwo4l3oi4EUT6MchMANGw4D4/SIZcdzm9R/pV9+AHYaA1k8YP612YXFe4W39\n" + - "ZPXuQrz/Lnys7MgPo/NSpKmxMp82el/vVW9wlQKA4fY/zz7GgfsUf25hVpnCC8V6\n" + - "b1kIU+5TfYSjTThHfSqI8aDIXohIiQu3KISNXqJV4Tx+PMqeQkKT0GIaqKM+b4jP\n" + - "WQIDAQAB\n" + - "-----END PUBLIC KEY-----" - - @Shared - def fakePrivateKey2048bit = "-----BEGIN PRIVATE KEY-----\n" + - "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDoD4IZErD/GloC\n" + - "rL/V4mV/m6tHOZRaGAZU151SGaAy50wFAhc9hU5ZBDJ1ar975JrF1Pt9FAT8B7R4\n" + - "4I+DC3IWYugD+6AZPQAYXmO5cHMLBdj2It0h6NA8NttVUwgIW15EIOQtotnLhTeq\n" + - "Pz3lPNkeDuBfCjiXeiLgRRPoxyEwA0bDgPj9Ihlx3Ob1H+lX34AdhoDWTxg/rXZh\n" + - "cV7hbf1k9e5CvP8ufKzsyA+j81KkqbEynzZ6X+9Vb3CVAoDh9j/PPsaB+xR/bmFW\n" + - "mcILxXpvWQhT7lN9hKNNOEd9KojxoMheiEiJC7cohI1eolXhPH48yp5CQpPQYhqo\n" + - "oz5viM9ZAgMBAAECggEBAMteW+tRQCAwndVeQzhUEhNE/1OKGILkLxhHZS3AG27A\n" + - "2RRCgs99de35CadxB6Kx8xmQz10MIFom/ng4hEyZyT/pKd/jsqirltvETK0E6S0t\n" + - "0LfUUesXtvYuNQWPoKiCOhiGorGD2E7Nzry6c6nkK3p2GxfvQy0s8keNAier61/A\n" + - "tAuY+vCQUdCV8v3KFy6edKSj7T0e94kNNYi+lTPLq28f0LfhjFT0DhfWI49wh1Vq\n" + - "Tn61bAX0wI0pyc5OIdMMoLQ37aez9iwHCRvYFCuYpaHleNaQ3FpxLaoPrSCQxGyT\n" + - "R1MQq51Bbh+MJI7DXO8TJw5ZVoX86tCStR0XdqcGnCkCgYEA9/rO8u2cCNYAxRPk\n" + - "n3VHZpmYq9Gvrbq+TbfJxgODCS9kJYAgwN1aeSzhO4nustrHT/B4jLSlR82Qi66Z\n" + - "+oaVXWygk5XVdLPMmO2rabDWsQWSWxBli1ez8DAflM+CFm9y1hKxMJiPbC8alZea\n" + - "fheiLjOMKElf94mBfobkeUEEmOMCgYEA75Dk/GY2Xel67f08g7X6cI+z4QMtedWd\n" + - "5BL63AujBEdZNobazhSDQmoiKqoBhwGertiskNmYpJ21uzA0FrSaO6AUx/5jTgNc\n" + - "TCwg2J6gXl+XOKJZ4xTu4ALImFyG4SZ5/QBKBrLs+dacOAnj7WvWvnaWph0rhzR/\n" + - "ZdWBUHO295MCgYBEJ26RXbSwyQBVKe5/1N/W1wga0PqTqOt8uLJ/9Z8h+yBvHhPi\n" + - "bfPbsfYFQxeTmIWG9vRq14tFfL3pZgdzz2Fl1+EaLugHtxLYRRoDZlLbPEjJNmxy\n" + - "K5yMuu0zHQUH3YGWTHTegk+I0DliO9R+K0irogc3W1NA2U351GEe4ju9OQKBgQC6\n" + - "27ugG2GgkrKt2u5OlazIC250vfPEqhhDg4JkDDeU6MnvO/SC9YEEVqBbwsr6MQtC\n" + - "ugKv4OmszM6pOQoIA8qhY1WSQRvYB8sAJxNfoyrXMZxUMl4GP5eq5sDsBo+2IjrY\n" + - "WldjLkClBv5Gv4Am+gw/92O+IdaH2Szdk1EQHZHDPQKBgGx3aAvS68WwA1smzI0Y\n" + - "i9zSBlBtqWX3ISAD20c/Oy5jK7YJEn307KqOXYpSNj6D544MffaZvMPafc/6zXmc\n" + - "OhyX9yMqdsl3SpJ8HsCD70eBIbS3kIttPb0rMXP/hyX/v/DL3GW0jr4vH9ThkYKQ\n" + - "Q23FiefzA2cYDohWY25jrl4f\n" + - "-----END PRIVATE KEY-----" + def subject = new QuintoAndarJwtBean() /** * header: { @@ -70,11 +25,6 @@ class QuintoAndarJwtSpec extends Specification { @Shared def fakeJwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NywibmFtZSI6IkphbWVzIEJvbmQiLCJlbWFpbCI6ImphbWVzLmJvbmRAcXVpbnRvYW5kYXIuY29tLmJyIiwicm9sZSI6ImFkbWluIn0.piI9eOSM06yfh9Lzy8O0iUW18mIGql7vSIEjWYrm6pbZCQNV-7LxU12PXO2ZdH-zqbv7VVtkJvuO3pseACKYzsukXbswb9Rc_v77UaqMj4QGXHTJfoniL0mFAMxcqJcJVgmS2mE03gKh62y_BA8Emh2Gbal-44grYPn2HOG7nNjGa799SKtvqSG2beilEnXXURidH5QNA7kWXIkLfVbRgMjTmu8axQUHa98dP95Hopnn2-6CJGtpPyZUqT9df76_r8B2oMY4N6pJJWo-ik8y7DAV8WyJVsLQTj_1rzHLGxtMT7aXEEtAufGovgBc7qV1Vgu4VhsoIpqQEqnby5m7jQ" - def setupSpec() { - publicKeyServiceMock.fetchMainPublicKey() >> fakePublicKey2048bit - subject.quintoAndarPublicKeyService = publicKeyServiceMock - } - def "get payload from valid Main jwt"() { when: def result = subject.getPayload(fakeJwt) @@ -90,19 +40,12 @@ class QuintoAndarJwtSpec extends Specification { claims.get("role") == "admin" } - def "get payload from invalid Main jwt throws exception"() { - given: - def jwtForOtherKeys = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NywibmFtZSI6IkphbWVzIEJvbmQiLCJlbWFpbCI6Imphb" + - "WVzLmJvbmRAcXVpbnRvYW5kYXIuY29tLmJyIiwicm9sZSI6ImFkbWluIn0.fwuOMtnmdKV7QeesDPWIsK-5Ipanv9PHPenzUWg5EOXG639z9" + - "9EzEFn3Fcy-cBTqEPrzm1JorhfVvfABOEZaz7Kocv058lcUSnTQhVgAhu9GVo5CaQmPg6gDKBkndQ0n4wu-pW_c-q3AKr5auJW8gxs9AoJRH" + - "1c5gQ-ablFJrOX4m2z17j81Aa-t8wM0jfbASJcu6omHaX5C4BLL7IzbYRUhE47lje3_D7SeXpciGPomI8Hl8hOo4dQpO8bZgO_uSTHDfTsoE" + - "1ypOaXe8A5VVr1YDRpxlcBHvbxymCR0uJmeq2HCuAPyoN97KQHLrw5ehJ5TyLKTCDTm1aSjnl1O0g" - + def "get payload returns empty for null jwt"() { when: - def result = subject.getPayload(jwtForOtherKeys) + def result = subject.getPayload(null) then: - thrown(InvalidJwtException) + !result.isPresent() } } diff --git a/src/test/groovy/br/com/quintoandar/javajwt/QuintoAndarKeycloakJwtSpec.groovy b/src/test/groovy/br/com/quintoandar/javajwt/QuintoAndarKeycloakJwtSpec.groovy index 1c2fc3c..66d540f 100644 --- a/src/test/groovy/br/com/quintoandar/javajwt/QuintoAndarKeycloakJwtSpec.groovy +++ b/src/test/groovy/br/com/quintoandar/javajwt/QuintoAndarKeycloakJwtSpec.groovy @@ -1,54 +1,12 @@ package br.com.quintoandar.javajwt -import org.jose4j.jwt.consumer.InvalidJwtException import spock.lang.Shared import spock.lang.Specification class QuintoAndarKeycloakJwtSpec extends Specification { @Shared - def keycloakPublicKeyServiceMock = Mock(QuintoAndarKeycloakPublicKeyService) - - @Shared - def keycloakSubject = GroovySpy(QuintoAndarKeycloakJwtBean) - - @Shared - def fakePublicKey2048bit = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AM\n" + - "IIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVh\n" + - "a4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/Ytj\n" + - "CZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzG\n" + - "TU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1F\n" + - "tGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68M\n" + - "b59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB" - - @Shared - def fakePrivateKey2048bit = "-----BEGIN PRIVATE KEY-----\n" + - "MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw\n" + - "kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr\n" + - "m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi\n" + - "NQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV\n" + - "3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2\n" + - "QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQABAoIBACiARq2wkltjtcjs\n" + - "kFvZ7w1JAORHbEufEO1Eu27zOIlqbgyAcAl7q+/1bip4Z/x1IVES84/yTaM8p0go\n" + - "amMhvgry/mS8vNi1BN2SAZEnb/7xSxbflb70bX9RHLJqKnp5GZe2jexw+wyXlwaM\n" + - "+bclUCrh9e1ltH7IvUrRrQnFJfh+is1fRon9Co9Li0GwoN0x0byrrngU8Ak3Y6D9\n" + - "D8GjQA4Elm94ST3izJv8iCOLSDBmzsPsXfcCUZfmTfZ5DbUDMbMxRnSo3nQeoKGC\n" + - "0Lj9FkWcfmLcpGlSXTO+Ww1L7EGq+PT3NtRae1FZPwjddQ1/4V905kyQFLamAA5Y\n" + - "lSpE2wkCgYEAy1OPLQcZt4NQnQzPz2SBJqQN2P5u3vXl+zNVKP8w4eBv0vWuJJF+\n" + - "hkGNnSxXQrTkvDOIUddSKOzHHgSg4nY6K02ecyT0PPm/UZvtRpWrnBjcEVtHEJNp\n" + - "bU9pLD5iZ0J9sbzPU/LxPmuAP2Bs8JmTn6aFRspFrP7W0s1Nmk2jsm0CgYEAyH0X\n" + - "+jpoqxj4efZfkUrg5GbSEhf+dZglf0tTOA5bVg8IYwtmNk/pniLG/zI7c+GlTc9B\n" + - "BwfMr59EzBq/eFMI7+LgXaVUsM/sS4Ry+yeK6SJx/otIMWtDfqxsLD8CPMCRvecC\n" + - "2Pip4uSgrl0MOebl9XKp57GoaUWRWRHqwV4Y6h8CgYAZhI4mh4qZtnhKjY4TKDjx\n" + - "QYufXSdLAi9v3FxmvchDwOgn4L+PRVdMwDNms2bsL0m5uPn104EzM6w1vzz1zwKz\n" + - "5pTpPI0OjgWN13Tq8+PKvm/4Ga2MjgOgPWQkslulO/oMcXbPwWC3hcRdr9tcQtn9\n" + - "Imf9n2spL/6EDFId+Hp/7QKBgAqlWdiXsWckdE1Fn91/NGHsc8syKvjjk1onDcw0\n" + - "NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j\n" + - "8mRex97UVokJQRRA452V2vCO6S5ETgpnad36de3MUxHgCOX3qL382Qx9/THVmbma\n" + - "3YfRAoGAUxL/Eu5yvMK8SAt/dJK6FedngcM3JEFNplmtLYVLWhkIlNRGDwkg3I5K\n" + - "y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB\n" + - "jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE=\n" + - "-----END PRIVATE KEY-----" + def subject = new QuintoAndarKeycloakJwtBean() /** * header: { @@ -68,14 +26,9 @@ class QuintoAndarKeycloakJwtSpec extends Specification { @Shared def fakeJwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ODYsIm5hbWUiOiJNYXh3ZWxsIFNtYXJ0IiwiZW1haWwiOiJtYXh3ZWxsLnNtYXJ0QHF1aW50b2FuZGFyLmNvbS5iciIsInJvbGUiOiJhZG1pbiIsImV4cCI6NDEwMjQ1NTYwMDAwMH0.TMNdNzFKjrRqt72tdW5PMti1tee_13787UWkqP_sPsmWXkmtlChFgIzEKiWj0aR9NqcUrVWn0U4jGMpnD45BZ7k7CoRvROR06nrUkvJStptEU_H4dGbQId5oJcGzSz92sEJ7GtNmVfW2uiHK71ipY5jy7CosviHwDKAwEcMTJWJp58rbNBoO-esQb-CRztMvHZ5WSl0gxB6pXmglV95um6GAa_Qfve4lAEocTRci9WowHfVdhvPQDKsl7oMTod6Iz3OpPnPlPPH6XMAFs709juuHy3kCUxEv48rQVypUN-Cpn6a53wW1tq7RDJJnXv5bhvTm3421bMJV7kav7bdCmg" - def setupSpec() { - keycloakPublicKeyServiceMock.fetchKeycloakPublicKey() >> fakePublicKey2048bit - keycloakSubject.quintoAndarKeycloakPublicKeyService = keycloakPublicKeyServiceMock - } - def "get payload from valid Keycloak jwt"() { when: - def result = keycloakSubject.getPayload(fakeJwt) + def result = subject.getPayload(fakeJwt) then: result.isPresent() @@ -89,19 +42,12 @@ class QuintoAndarKeycloakJwtSpec extends Specification { claims.get("exp") == 4102455600000 } - def "get payload from invalid Keycloak jwt throws exception"() { - given: - def jwtForOtherKeys = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NywibmFtZSI6IkphbWVzIEJvbmQiLCJlbWFpbCI6Imphb" + - "WVzLmJvbmRAcXVpbnRvYW5kYXIuY29tLmJyIiwicm9sZSI6ImFkbWluIn0.fwuOMtnmdKV7QeesDPWIsK-5Ipanv9PHPenzUWg5EOXG639z9" + - "9EzEFn3Fcy-cBTqEPrzm1JorhfVvfABOEZaz7Kocv058lcUSnTQhVgAhu9GVo5CaQmPg6gDKBkndQ0n4wu-pW_c-q3AKr5auJW8gxs9AoJRH" + - "1c5gQ-ablFJrOX4m2z17j81Aa-t8wM0jfbASJcu6omHaX5C4BLL7IzbYRUhE47lje3_D7SeXpciGPomI8Hl8hOo4dQpO8bZgO_uSTHDfTsoE" + - "1ypOaXe8A5VVr1YDRpxlcBHvbxymCR0uJmeq2HCuAPyoN97KQHLrw5ehJ5TyLKTCDTm1aSjnl1O0g" - + def "get payload returns empty for null jwt"() { when: - def result = keycloakSubject.getPayload(jwtForOtherKeys) + def result = subject.getPayload(null) then: - thrown(InvalidJwtException) + !result.isPresent() } }