From f29be807736b8287e1c52d388bc54f24cdf0a1bb Mon Sep 17 00:00:00 2001 From: zihluwang Date: Thu, 25 Jul 2024 21:56:15 +0800 Subject: [PATCH 1/5] feat: added key pair loader --- key-pair-loader/build.gradle.kts | 99 +++++++++++++++++++ .../com/onixbyte/keypairloader/KeyLoader.java | 74 ++++++++++++++ .../exception/KeyLoadingException.java | 40 ++++++++ .../src/main/resources/logback.xml | 32 ++++++ .../keypairloader/KeyPairLoaderTest.java | 29 ++++++ map-util-unsafe/build.gradle.kts | 99 +++++++++++++++++++ settings.gradle.kts | 1 + .../config/AuthzeroTokenResolverConfig.java | 91 +++++++++-------- 8 files changed, 424 insertions(+), 41 deletions(-) create mode 100644 key-pair-loader/build.gradle.kts create mode 100644 key-pair-loader/src/main/java/com/onixbyte/keypairloader/KeyLoader.java create mode 100644 key-pair-loader/src/main/java/com/onixbyte/keypairloader/exception/KeyLoadingException.java create mode 100644 key-pair-loader/src/main/resources/logback.xml create mode 100644 key-pair-loader/src/test/java/com/onixbyte/keypairloader/KeyPairLoaderTest.java create mode 100644 map-util-unsafe/build.gradle.kts diff --git a/key-pair-loader/build.gradle.kts b/key-pair-loader/build.gradle.kts new file mode 100644 index 0000000..17c4fd3 --- /dev/null +++ b/key-pair-loader/build.gradle.kts @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2024-2024 OnixByte. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.URI + +val buildGroupId: String by project +val buildVersion: String by project +val projectUrl: String by project +val projectGithubUrl: String by project +val licenseName: String by project +val licenseUrl: String by project + +group = buildGroupId +version = buildVersion + +dependencies { + implementation(project(":devkit-core")) +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + withSourcesJar() + withJavadocJar() +} + +tasks.test { + useJUnitPlatform() +} + +publishing { + publications { + create("keyPairLoader") { + groupId = buildGroupId + artifactId = "key-pair-loader" + version = buildVersion + + pom { + name = "Key Pair Loader" + description = + "This module can easily load key pairs from a PEM content." + url = projectUrl + + licenses { + license { + name = licenseName + url = licenseUrl + } + } + + scm { + connection = "scm:git:git://github.com:OnixByte/JDevKit.git" + developerConnection = "scm:git:git://github.com:OnixByte/JDevKit.git" + url = projectGithubUrl + } + + developers { + developer { + id = "zihluwang" + name = "Zihlu Wang" + email = "really@zihlu.wang" + timezone = "Asia/Hong_Kong" + } + } + } + + from(components["java"]) + + signing { + sign(publishing.publications["keyPairLoader"]) + } + } + + repositories { + maven { + name = "sonatypeNexus" + url = URI(providers.gradleProperty("repo.maven-central.host").get()) + credentials { + username = providers.gradleProperty("repo.maven-central.username").get() + password = providers.gradleProperty("repo.maven-central.password").get() + } + } + } + } +} \ No newline at end of file diff --git a/key-pair-loader/src/main/java/com/onixbyte/keypairloader/KeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/keypairloader/KeyLoader.java new file mode 100644 index 0000000..a2bdcf3 --- /dev/null +++ b/key-pair-loader/src/main/java/com/onixbyte/keypairloader/KeyLoader.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024-2024 OnixByte. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.keypairloader; + +import com.onixbyte.keypairloader.exception.KeyLoadingException; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.Optional; + +/** + * KeyLoader can load key pairs from PEM formated content. + * + * @author zihluwang + * @version 1.6.0 + * @since 1.6.0 + */ +@Slf4j +public class KeyLoader { + + /** + * Private constructor prevents from being initialised. + */ + private KeyLoader() { + } + + public ECPrivateKey loadEcdsaPrivateKey(String pemKeyText) { + return Optional.ofNullable(pemKeyText) + .map(Base64.getDecoder()::decode) + .map(PKCS8EncodedKeySpec::new) + .map((keySpec) -> { + try { + var kf = KeyFactory.getInstance("EC"); + return kf.generatePrivate(keySpec); + } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { + throw new KeyLoadingException("Unable to load key from text.", e); + } + }).map((publicKey) -> { + if (publicKey instanceof ECPrivateKey privateKey) { + return privateKey; + } + return null; + }) + .orElse(null); + } + + // public ECPublicKey loadEcdsaPublicKey(String pemKeyText) { + // + // } + +} diff --git a/key-pair-loader/src/main/java/com/onixbyte/keypairloader/exception/KeyLoadingException.java b/key-pair-loader/src/main/java/com/onixbyte/keypairloader/exception/KeyLoadingException.java new file mode 100644 index 0000000..3a72811 --- /dev/null +++ b/key-pair-loader/src/main/java/com/onixbyte/keypairloader/exception/KeyLoadingException.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024-2024 OnixByte. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.keypairloader.exception; + +public class KeyLoadingException extends RuntimeException { + + public KeyLoadingException() { + } + + public KeyLoadingException(String message) { + super(message); + } + + public KeyLoadingException(String message, Throwable cause) { + super(message, cause); + } + + public KeyLoadingException(Throwable cause) { + super(cause); + } + + public KeyLoadingException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/key-pair-loader/src/main/resources/logback.xml b/key-pair-loader/src/main/resources/logback.xml new file mode 100644 index 0000000..1cf5a50 --- /dev/null +++ b/key-pair-loader/src/main/resources/logback.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + ${COLOURFUL_OUTPUT} + + + + + + \ No newline at end of file diff --git a/key-pair-loader/src/test/java/com/onixbyte/keypairloader/KeyPairLoaderTest.java b/key-pair-loader/src/test/java/com/onixbyte/keypairloader/KeyPairLoaderTest.java new file mode 100644 index 0000000..5a5abdd --- /dev/null +++ b/key-pair-loader/src/test/java/com/onixbyte/keypairloader/KeyPairLoaderTest.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024-2024 OnixByte. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.keypairloader; + +import org.junit.jupiter.api.Test; + +public class KeyPairLoaderTest { + + @Test + public void test() { + + } + +} diff --git a/map-util-unsafe/build.gradle.kts b/map-util-unsafe/build.gradle.kts new file mode 100644 index 0000000..3376b22 --- /dev/null +++ b/map-util-unsafe/build.gradle.kts @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2024-2024 OnixByte. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.URI + +val buildGroupId: String by project +val buildVersion: String by project +val projectUrl: String by project +val projectGithubUrl: String by project +val licenseName: String by project +val licenseUrl: String by project + +group = buildGroupId +version = buildVersion + +dependencies { + implementation(project(":devkit-core")) +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + withSourcesJar() + withJavadocJar() +} + +tasks.test { + useJUnitPlatform() +} + +publishing { + publications { + create("mapUtilUnsafe") { + groupId = buildGroupId + artifactId = "map-util-unsafe" + version = buildVersion + + pom { + name = "Unsafe Map Util" + description = + "This module is a converter that can convert an object to a map with unsafe methods." + url = projectUrl + + licenses { + license { + name = licenseName + url = licenseUrl + } + } + + scm { + connection = "scm:git:git://github.com:OnixByte/JDevKit.git" + developerConnection = "scm:git:git://github.com:OnixByte/JDevKit.git" + url = projectGithubUrl + } + + developers { + developer { + id = "zihluwang" + name = "Zihlu Wang" + email = "really@zihlu.wang" + timezone = "Asia/Hong_Kong" + } + } + } + + from(components["java"]) + + signing { + sign(publishing.publications["mapUtilUnsafe"]) + } + } + + repositories { + maven { + name = "sonatypeNexus" + url = URI(providers.gradleProperty("repo.maven-central.host").get()) + credentials { + username = providers.gradleProperty("repo.maven-central.username").get() + password = providers.gradleProperty("repo.maven-central.password").get() + } + } + } + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 1e3dd3e..3496393 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -29,3 +29,4 @@ include( "property-guard-spring-boot-starter" ) include("map-util-unsafe") +include("key-pair-loader") diff --git a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java b/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java index b153c23..768b8ce 100644 --- a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java +++ b/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java @@ -24,51 +24,48 @@ import com.onixbyte.simplejwt.exceptions.UnsupportedAlgorithmException; import com.auth0.jwt.algorithms.Algorithm; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.*; import java.util.function.Function; /** * The {@code AuthzeroTokenResolverConfig} class provides the configuration for * the {@link AuthzeroTokenResolver}. *

- * This configuration is used to establish the mapping between the standard - * {@link TokenAlgorithm} defined in the - * {@code cn.org.codecrafters:simple-jwt-facade} and the specific algorithms - * used by the {@code com.auth0:java-jwt} library, which is the underlying - * library used by {@link AuthzeroTokenResolver} to handle JSON Web Tokens - * (JWTs). + * This configuration is used to establish the mapping between the standard {@link TokenAlgorithm} + * defined in the {@code cn.org.codecrafters:simple-jwt-facade} and the specific algorithms used + * by the {@code com.auth0:java-jwt} library, which is the underlying library used by + * {@link AuthzeroTokenResolver} to handle JSON Web Tokens (JWTs). *

* Algorithm Mapping: - * The {@code AuthzeroTokenResolverConfig} allows specifying the relationships - * between the standard {@link TokenAlgorithm} instances supported by - * {@link AuthzeroTokenResolver} and the corresponding algorithms used by the - * {@code com.auth0:java-jwt} library. The mapping is achieved using a Map, - * where the keys are the standard {@link TokenAlgorithm} instances, and the - * values represent the algorithm functions used by {@code com.auth0:java-jwt} - * library for each corresponding key. + * The {@code AuthzeroTokenResolverConfig} allows specifying the relationships between the standard + * {@link TokenAlgorithm} instances supported by {@link AuthzeroTokenResolver} and the corresponding + * algorithms used by the {@code com.auth0:java-jwt} library. The mapping is achieved using a Map, + * where the keys are the standard {@link TokenAlgorithm} instances, and the values represent the + * algorithm functions used by {@code com.auth0:java-jwt} library for each corresponding key. *

* Note: - * The provided algorithm mapping should be consistent with the actual - * algorithms supported and used by the {@code com.auth0:java-jwt} library. It - * is crucial to ensure that the mapping is accurate to enable proper token - * validation and processing within the {@link AuthzeroTokenResolver}. + * The provided algorithm mapping should be consistent with the actual algorithms supported and used + * by the {@code com.auth0:java-jwt} library. It is crucial to ensure that the mapping is accurate + * to enable proper token validation and processing within the {@link AuthzeroTokenResolver}. * * @author Zihlu Wang * @version 1.1.1 * @since 1.0.0 */ -public final class AuthzeroTokenResolverConfig implements TokenResolverConfig> { +public final class AuthzeroTokenResolverConfig + implements TokenResolverConfig> { /** * Gets the instance of {@code AuthzeroTokenResolverConfig}. *

- * This method returns the singleton instance of - * {@code AuthzeroTokenResolverConfig}. If the instance is not yet created, - * it will create a new instance and return it. Otherwise, it returns the - * existing instance. + * This method returns the singleton instance of {@code AuthzeroTokenResolverConfig}. If the + * instance is not yet created, it will create a new instance and return it. Otherwise, it + * returns the existing instance. * * @return the instance of {@code AuthzeroTokenResolverConfig} */ @@ -81,23 +78,18 @@ public static AuthzeroTokenResolverConfig getInstance() { } /** - * Gets the algorithm function corresponding to the specified - * {@link TokenAlgorithm}. + * Gets the algorithm function corresponding to the specified {@link TokenAlgorithm}. *

- * This method returns the algorithm function associated with the given - * {@link TokenAlgorithm}. The provided {@link TokenAlgorithm} represents - * the specific algorithm for which the corresponding algorithm function - * is required. The returned Algorithm Function represents the function - * implementation that can be used by the {@link TokenResolver} to handle - * the specific algorithm. + * This method returns the algorithm function associated with the given {@link TokenAlgorithm}. + * The provided {@link TokenAlgorithm} represents the specific algorithm for which the + * corresponding algorithm function is required. The returned Algorithm Function represents the + * function implementation that can be used by the {@link TokenResolver} to handle the + * specific algorithm. * - * @param algorithm the {@link TokenAlgorithm} for which the algorithm - * function isrequired - * @return the algorithm function associated with the given {@link - * TokenAlgorithm} - * @throws UnsupportedAlgorithmException if the given {@code algorithm} is - * not supported by this - * implementation + * @param algorithm the {@link TokenAlgorithm} for which the algorithm function is required + * @return the algorithm function associated with the given {@link TokenAlgorithm} + * @throws UnsupportedAlgorithmException if the given {@code algorithm} is not supported by + * this implementation */ @Override public Function getAlgorithm(TokenAlgorithm algorithm) { @@ -139,5 +131,22 @@ private AuthzeroTokenResolverConfig() { put(TokenAlgorithm.HS256, Algorithm::HMAC256); put(TokenAlgorithm.HS384, Algorithm::HMAC384); put(TokenAlgorithm.HS512, Algorithm::HMAC512); + put(TokenAlgorithm.ES256, (String privateKey) -> { + try { + var keyBytes = Base64.getDecoder().decode(privateKey); + var spec = new PKCS8EncodedKeySpec(keyBytes); + var kf = KeyFactory.getInstance("EC"); + var key = kf.generatePrivate(spec); + if (key instanceof ECPrivateKey pk) { + return Algorithm.ECDSA256(pk); + } else { + throw new RuntimeException("Type error!"); + } + } catch (NoSuchAlgorithmException ignored) { + } catch (InvalidKeySpecException e) { + throw new RuntimeException(e); + } + return null; + }); }}; } From 4eb7638dbd120011f6d466a5e8740fce82bba586 Mon Sep 17 00:00:00 2001 From: zihluwang Date: Sat, 27 Jul 2024 09:32:02 +0800 Subject: [PATCH 2/5] chore: removed repositories settings --- build.gradle.kts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index fb67a8f..ed34796 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -51,12 +51,6 @@ subprojects { testAnnotationProcessor("org.projectlombok:lombok:$lombokVersion") } - repositories { - mavenLocal() - maven(url = "https://maven.proxy.ustclug.org/maven2/") - mavenCentral() - } - tasks.withType { options.encoding = "UTF-8" } From 7e4fdd54046e1e550c5cdf3abf757f0932090731 Mon Sep 17 00:00:00 2001 From: zihluwang Date: Sat, 27 Jul 2024 10:10:44 +0800 Subject: [PATCH 3/5] feat: load ECDSA key pairs with pem-formatted text --- .../com/onixbyte/keypairloader/KeyLoader.java | 74 ------------- .../java/com/onixbyte/security/KeyLoader.java | 100 ++++++++++++++++++ .../exception/KeyLoadingException.java | 2 +- .../KeyPairLoaderTest.java | 2 +- 4 files changed, 102 insertions(+), 76 deletions(-) delete mode 100644 key-pair-loader/src/main/java/com/onixbyte/keypairloader/KeyLoader.java create mode 100644 key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java rename key-pair-loader/src/main/java/com/onixbyte/{keypairloader => security}/exception/KeyLoadingException.java (96%) rename key-pair-loader/src/test/java/com/onixbyte/{keypairloader => security}/KeyPairLoaderTest.java (95%) diff --git a/key-pair-loader/src/main/java/com/onixbyte/keypairloader/KeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/keypairloader/KeyLoader.java deleted file mode 100644 index a2bdcf3..0000000 --- a/key-pair-loader/src/main/java/com/onixbyte/keypairloader/KeyLoader.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2024-2024 OnixByte. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.onixbyte.keypairloader; - -import com.onixbyte.keypairloader.exception.KeyLoadingException; -import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Base64; -import java.util.Optional; - -/** - * KeyLoader can load key pairs from PEM formated content. - * - * @author zihluwang - * @version 1.6.0 - * @since 1.6.0 - */ -@Slf4j -public class KeyLoader { - - /** - * Private constructor prevents from being initialised. - */ - private KeyLoader() { - } - - public ECPrivateKey loadEcdsaPrivateKey(String pemKeyText) { - return Optional.ofNullable(pemKeyText) - .map(Base64.getDecoder()::decode) - .map(PKCS8EncodedKeySpec::new) - .map((keySpec) -> { - try { - var kf = KeyFactory.getInstance("EC"); - return kf.generatePrivate(keySpec); - } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { - throw new KeyLoadingException("Unable to load key from text.", e); - } - }).map((publicKey) -> { - if (publicKey instanceof ECPrivateKey privateKey) { - return privateKey; - } - return null; - }) - .orElse(null); - } - - // public ECPublicKey loadEcdsaPublicKey(String pemKeyText) { - // - // } - -} diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java new file mode 100644 index 0000000..0e3c8ac --- /dev/null +++ b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024-2024 OnixByte. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.security; + +import com.onixbyte.security.exception.KeyLoadingException; +import lombok.extern.slf4j.Slf4j; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +/** + * KeyLoader can load key pairs from PEM formated content. + * + * @author zihluwang + * @version 1.6.0 + * @since 1.6.0 + */ +@Slf4j +public class KeyLoader { + + /** + * Private constructor prevents from being initialised. + */ + private KeyLoader() { + } + + /** + * Load ECDSA private key from pem-formatted key text. + * + * @param pemKeyText pem-formatted key text + * @return loaded private key + * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, or EC Key Factory is + * not loaded, or key spec is invalid + */ + public ECPrivateKey loadEcdsaPrivateKey(String pemKeyText) { + try { + var decodedKeyString = Base64.getDecoder().decode(pemKeyText); + var keySpec = new PKCS8EncodedKeySpec(decodedKeyString); + var keyFactory = KeyFactory.getInstance("EC"); + var _key = keyFactory.generatePrivate(keySpec); + if (_key instanceof ECPrivateKey privateKey) { + return privateKey; + } else { + throw new KeyLoadingException("Unable to load private key from pem-formatted key text."); + } + } catch (NoSuchAlgorithmException e) { + throw new KeyLoadingException("Cannot get EC Key Factory.", e); + } catch (InvalidKeySpecException e) { + throw new KeyLoadingException("Key spec is invalid.", e); + } + } + + /** + * Load ECDSA public key from pem-formatted key text. + * + * @param pemKeyText pem-formatted key text + * @return loaded private key + * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, or EC Key Factory is + * not loaded, or key spec is invalid + */ + public ECPublicKey loadEcdsaPublicKey(String pemKeyText) { + try { + var keyBytes = Base64.getDecoder().decode(pemKeyText); + var spec = new X509EncodedKeySpec(keyBytes); + var keyFactory = KeyFactory.getInstance("EC"); + var key = keyFactory.generatePublic(spec); + if (key instanceof ECPublicKey publicKey) { + return publicKey; + } else { + throw new KeyLoadingException("Unable to load private key from pem-formatted key text."); + } + } catch (NoSuchAlgorithmException e) { + throw new KeyLoadingException("Cannot get EC Key Factory.", e); + } catch (InvalidKeySpecException e) { + throw new KeyLoadingException("Key spec is invalid.", e); + } + } + +} diff --git a/key-pair-loader/src/main/java/com/onixbyte/keypairloader/exception/KeyLoadingException.java b/key-pair-loader/src/main/java/com/onixbyte/security/exception/KeyLoadingException.java similarity index 96% rename from key-pair-loader/src/main/java/com/onixbyte/keypairloader/exception/KeyLoadingException.java rename to key-pair-loader/src/main/java/com/onixbyte/security/exception/KeyLoadingException.java index 3a72811..4682d67 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/keypairloader/exception/KeyLoadingException.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/exception/KeyLoadingException.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package com.onixbyte.keypairloader.exception; +package com.onixbyte.security.exception; public class KeyLoadingException extends RuntimeException { diff --git a/key-pair-loader/src/test/java/com/onixbyte/keypairloader/KeyPairLoaderTest.java b/key-pair-loader/src/test/java/com/onixbyte/security/KeyPairLoaderTest.java similarity index 95% rename from key-pair-loader/src/test/java/com/onixbyte/keypairloader/KeyPairLoaderTest.java rename to key-pair-loader/src/test/java/com/onixbyte/security/KeyPairLoaderTest.java index 5a5abdd..edfc670 100644 --- a/key-pair-loader/src/test/java/com/onixbyte/keypairloader/KeyPairLoaderTest.java +++ b/key-pair-loader/src/test/java/com/onixbyte/security/KeyPairLoaderTest.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package com.onixbyte.keypairloader; +package com.onixbyte.security; import org.junit.jupiter.api.Test; From 62b8cb8118c075598d303cd3b73fe6a8d3cbae42 Mon Sep 17 00:00:00 2001 From: zihluwang Date: Mon, 5 Aug 2024 19:04:51 +0800 Subject: [PATCH 4/5] refactor: moved MapUtil implemented by reflect API to another package --- .../com/onixbyte/devkit/utils/AesUtil.java | 11 +- .../com/onixbyte/devkit/utils/Base64Util.java | 2 +- .../devkit/utils/ChainedCalcUtil.java | 7 +- .../com/onixbyte/devkit/utils/HashUtil.java | 2 +- .../com/onixbyte/devkit/utils/MapUtil.java | 3 +- .../java/com/onixbyte/security/KeyLoader.java | 12 +-- .../exception/KeyLoadingException.java | 41 ++++++- .../devkit/utils/unsafe/ReflectMapUtil.java | 5 +- simple-jwt-authzero/build.gradle.kts | 1 + .../authzero/AuthzeroTokenResolver.java | 102 ++++++++++-------- .../config/AuthzeroTokenResolverConfig.java | 42 ++++---- .../simplejwt/config/TokenResolverConfig.java | 14 ++- .../simplejwt/constants/PredefinedKeys.java | 14 +-- .../simplejwt/jjwt/JjwtTokenResolver.java | 6 -- ...uthzeroTokenResolverAutoConfiguration.java | 65 ++++++----- .../properties/SimpleJwtProperties.java | 7 ++ 16 files changed, 204 insertions(+), 130 deletions(-) rename {devkit-utils => map-util-unsafe}/src/main/java/com/onixbyte/devkit/utils/unsafe/ReflectMapUtil.java (98%) diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/AesUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/AesUtil.java index bc9ec22..14baad1 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/AesUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/AesUtil.java @@ -34,8 +34,7 @@ import java.util.UUID; /** - * {@link AesUtil} can help you encrypt and decrypt data with specified secret - * by AES algorithm. + * {@link AesUtil} can help you encrypt and decrypt data with specified secret by AES algorithm. * * @author hubin@baomidou * @version 1.1.0 @@ -81,8 +80,9 @@ public static byte[] decrypt(byte[] data, byte[] secret) { var cipher = Cipher.getInstance(AES_CBC_CIPHER); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(secret)); return cipher.doFinal(data); - } catch (NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedOperationException | - InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | + } catch (NoSuchAlgorithmException | NoSuchPaddingException | + UnsupportedOperationException | InvalidKeyException | + InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException exception) { log.error(exception.getMessage()); for (var stackTraceElement : exception.getStackTrace()) { @@ -100,7 +100,8 @@ public static byte[] decrypt(byte[] data, byte[] secret) { * @return the encryption result or {@code null} if encryption failed */ public static String encrypt(String data, String secret) { - return Base64.getEncoder().encodeToString(encrypt(data.getBytes(StandardCharsets.UTF_8), secret.getBytes(StandardCharsets.UTF_8))); + return Base64.getEncoder().encodeToString(encrypt(data.getBytes(StandardCharsets.UTF_8), + secret.getBytes(StandardCharsets.UTF_8))); } /** diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/Base64Util.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/Base64Util.java index 41a0cc3..18802bf 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/Base64Util.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/Base64Util.java @@ -49,7 +49,7 @@ * provided. It is recommended to specify the charset explicitly to ensure consistent * encoding and decoding. * - * @author Zihlu Wang + * @author zihluwang * @version 1.1.0 * @since 1.0.0 */ diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ChainedCalcUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ChainedCalcUtil.java index a8f395e..2ee3092 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ChainedCalcUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ChainedCalcUtil.java @@ -293,7 +293,9 @@ private ChainedCalcUtil operator(BiFunction * or null if not applicable * @return a ChainedCalcUtil instance with the updated value */ - private ChainedCalcUtil operator(BiFunction operator, Object other, Integer beforeOperateScale) { + private ChainedCalcUtil operator(BiFunction operator, + Object other, + Integer beforeOperateScale) { return baseOperator((otherValue) -> operator.apply(this.value, otherValue), other, @@ -311,7 +313,8 @@ private ChainedCalcUtil operator(BiFunction * @return a ChainedCalcUtil instance with the updated value */ private synchronized ChainedCalcUtil baseOperator(Function operatorFunction, - Object anotherValue, Integer beforeOperateScale) { + Object anotherValue, + Integer beforeOperateScale) { if (Objects.isNull(anotherValue)) { return this; } diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/HashUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/HashUtil.java index f0e4c59..2c24283 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/HashUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/HashUtil.java @@ -61,7 +61,7 @@ * for data integrity checks and password storage, but they should not be used for * encryption purposes. * - * @author Zihlu Wang + * @author zihluwang * @version 1.1.0 * @see java.security.MessageDigest * @since 1.0.0 diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java index aa23699..195020c 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java @@ -30,9 +30,8 @@ * Note: Since version 1.4.2, this util class removed reflection API and transferred to a safer API. * Please see documentation for more information. * - * @author Zihlu Wang + * @author zihluwang * @version 1.4.2 - * @see com.onixbyte.devkit.utils.unsafe.ReflectMapUtil * @since 1.0.0 */ @Slf4j diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java index 0e3c8ac..4a4b3e1 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/KeyLoader.java @@ -50,10 +50,10 @@ private KeyLoader() { * * @param pemKeyText pem-formatted key text * @return loaded private key - * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, or EC Key Factory is - * not loaded, or key spec is invalid + * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, + * or EC Key Factory is not loaded, or key spec is invalid */ - public ECPrivateKey loadEcdsaPrivateKey(String pemKeyText) { + public static ECPrivateKey loadEcdsaPrivateKey(String pemKeyText) { try { var decodedKeyString = Base64.getDecoder().decode(pemKeyText); var keySpec = new PKCS8EncodedKeySpec(decodedKeyString); @@ -76,10 +76,10 @@ public ECPrivateKey loadEcdsaPrivateKey(String pemKeyText) { * * @param pemKeyText pem-formatted key text * @return loaded private key - * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, or EC Key Factory is - * not loaded, or key spec is invalid + * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, + * or EC Key Factory is not loaded, or key spec is invalid */ - public ECPublicKey loadEcdsaPublicKey(String pemKeyText) { + public static ECPublicKey loadEcdsaPublicKey(String pemKeyText) { try { var keyBytes = Base64.getDecoder().decode(pemKeyText); var spec = new X509EncodedKeySpec(keyBytes); diff --git a/key-pair-loader/src/main/java/com/onixbyte/security/exception/KeyLoadingException.java b/key-pair-loader/src/main/java/com/onixbyte/security/exception/KeyLoadingException.java index 4682d67..2c36b10 100644 --- a/key-pair-loader/src/main/java/com/onixbyte/security/exception/KeyLoadingException.java +++ b/key-pair-loader/src/main/java/com/onixbyte/security/exception/KeyLoadingException.java @@ -17,24 +17,63 @@ package com.onixbyte.security.exception; +/** + * {@code KeyLoadingException} is an exception indicating an error occurred while loading a key. + * + * @author zihluwang + * @version 1.6.0 + * @since 1.6.0 + */ public class KeyLoadingException extends RuntimeException { + /** + * Creates a new instance of {@code KeyLoadingException} without a specific message or cause. + */ public KeyLoadingException() { } + /** + * Creates a new instance of {@code KeyLoadingException} with the specified detail message. + * + * @param message the detail message + */ public KeyLoadingException(String message) { super(message); } + /** + * Creates a new instance of {@code KeyLoadingException} with the specified detail message + * and cause. + * + * @param message the detail message + * @param cause the cause of this exception + */ public KeyLoadingException(String message, Throwable cause) { super(message, cause); } + /** + * Creates a new instance of {@code KeyLoadingException} with the specified cause. + * + * @param cause the cause of this exception + */ public KeyLoadingException(Throwable cause) { super(cause); } - public KeyLoadingException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + /** + * Constructs a new exception with the specified detail message, cause, suppression enabled + * or disabled, and writable stack trace enabled or disabled. + * + * @param message the detail message + * @param cause the cause of this exception + * @param enableSuppression whether suppression is enabled or disabled + * @param writableStackTrace whether the stack trace should be writable + */ + public KeyLoadingException(String message, + Throwable cause, + boolean enableSuppression, + boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/unsafe/ReflectMapUtil.java b/map-util-unsafe/src/main/java/com/onixbyte/devkit/utils/unsafe/ReflectMapUtil.java similarity index 98% rename from devkit-utils/src/main/java/com/onixbyte/devkit/utils/unsafe/ReflectMapUtil.java rename to map-util-unsafe/src/main/java/com/onixbyte/devkit/utils/unsafe/ReflectMapUtil.java index 11815ed..daa84c5 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/unsafe/ReflectMapUtil.java +++ b/map-util-unsafe/src/main/java/com/onixbyte/devkit/utils/unsafe/ReflectMapUtil.java @@ -53,7 +53,7 @@ public static Map objectToMap(Object obj) throws IllegalAccessEx var declaredFields = obj.getClass().getDeclaredFields(); for (var field : declaredFields) { field.setAccessible(true); - Object result = field.get(obj); + var result = field.get(obj); if (result != null) { map.put(field.getName(), result); } @@ -157,7 +157,8 @@ public static T getFieldValue(Object obj, String fieldName, Class fieldTy * @throws IllegalAccessException if an error occurs while accessing the field * @throws NoSuchMethodException if the specific setter is not present */ - public static void setFieldValue(Object obj, String fieldName, Object fieldValue) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { + public static void setFieldValue(Object obj, String fieldName, Object fieldValue) + throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { var objectClass = obj.getClass(); var methodName = getMethodName("set", fieldName); var method = objectClass.getDeclaredMethod(methodName, fieldValue.getClass()); diff --git a/simple-jwt-authzero/build.gradle.kts b/simple-jwt-authzero/build.gradle.kts index b6f85b3..55a62ea 100644 --- a/simple-jwt-authzero/build.gradle.kts +++ b/simple-jwt-authzero/build.gradle.kts @@ -33,6 +33,7 @@ version = buildVersion dependencies { implementation(project(":devkit-utils")) implementation(project(":guid")) + implementation(project(":key-pair-loader")) implementation(project(":simple-jwt-facade")) implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") implementation("com.auth0:java-jwt:$javaJwtVersion") diff --git a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java b/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java index da627b5..cf97ac5 100644 --- a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java +++ b/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java @@ -25,6 +25,7 @@ import com.onixbyte.simplejwt.annotations.ExcludeFromPayload; import com.onixbyte.simplejwt.annotations.TokenEnum; import com.onixbyte.simplejwt.authzero.config.AuthzeroTokenResolverConfig; +import com.onixbyte.simplejwt.config.TokenResolverConfig; import com.onixbyte.simplejwt.constants.PredefinedKeys; import com.onixbyte.simplejwt.constants.TokenAlgorithm; import com.auth0.jwt.JWT; @@ -45,15 +46,13 @@ import java.util.*; /** - * The {@code AuthzeroTokenResolver} class is an implementation of the {@link - * TokenResolver} interface. It uses the {@code - * com.auth0:java-jwt} library to handle JSON Web Token (JWT) resolution. This - * resolver provides functionality to create, extract, verify, and renew JWT + * The {@code AuthzeroTokenResolver} class is an implementation of the {@link TokenResolver} + * interface. It uses the {@code com.auth0:java-jwt} library to handle JSON Web Token (JWT) + * resolution. This resolver provides functionality to create, extract, verify, and renew JWT * tokens using various algorithms and custom payload data. *

* Usage: - * To use the {@code AuthzeroTokenResolver}, first, create an instance of this - * class: + * To use the {@code AuthzeroTokenResolver}, first, create an instance of this class: *

{@code
  * TokenResolver tokenResolver =
  *     new AuthzeroTokenResolver(TokenAlgorithm.HS256,
@@ -62,8 +61,7 @@
  *                               "Token Secret");
  * }
*

- * Then, you can utilize the various methods provided by this resolver to - * handle JWT tokens: + * Then, you can utilize the various methods provided by this resolver to handle JWT tokens: *

{@code
  * // Creating a new JWT token
  * String token =
@@ -82,10 +80,9 @@
  * }
*

* Note: - * It is essential to configure the appropriate algorithms, secret, and issuer - * according to your specific use case when using this resolver. - * Additionally, ensure that the {@code com.auth0:java-jwt} library is - * correctly configured in your project's dependencies. + * It is essential to configure the appropriate algorithms, secret, and issuer according to your + * specific use case when using this resolver. Additionally, ensure that the + * {@code com.auth0:java-jwt} library is correctly configured in your project's dependencies. * * @author Zihlu Wang * @version 1.1.1 @@ -100,61 +97,77 @@ public class AuthzeroTokenResolver implements TokenResolver { /** - * Creates a new instance of {@code AuthzeroTokenResolver} with the - * provided configurations. + * Creates a new instance of {@code AuthzeroTokenResolver} with the provided configurations. * - * @param jtiCreator the {@link GuidCreator} used for generating unique - * identifiers for "jti" claim in JWT tokens - * @param algorithm the algorithm used for signing and verifying JWT - * tokens + * @param jtiCreator the {@link GuidCreator} used for generating unique identifiers for "jti" + * claim in JWT tokens + * @param algorithm the algorithm used for signing and verifying JWT tokens * @param issuer the issuer claim value to be included in JWT tokens - * @param secret the secret used for HMAC-based algorithms (HS256, - * HS384, HS512) for token signing and verification + * @param privateKey the secret used for HMAC-based algorithms (HS256, HS384, HS512) for + * token signing and verification, or the private key for ECDSA-based + * algorithms + * @param publicKey the public key for ECDSA-based algorithms * @param objectMapper JSON handler */ - public AuthzeroTokenResolver(GuidCreator jtiCreator, TokenAlgorithm algorithm, String issuer, String secret, ObjectMapper objectMapper) { - if (secret == null || secret.isBlank()) { - throw new IllegalArgumentException("A secret is required to build a JSON Web Token."); - } + public AuthzeroTokenResolver(GuidCreator jtiCreator, + TokenAlgorithm algorithm, + String issuer, + String privateKey, + String publicKey, + ObjectMapper objectMapper) { + if (TokenResolverConfig.HMAC_ALGORITHMS.contains(algorithm)) { + if (privateKey == null || privateKey.isBlank()) { + throw new IllegalArgumentException("A secret is required to build a JSON Web Token."); + } - if (secret.length() < 32) { - log.warn("The provided secret which owns {} characters is too weak. Please consider replacing it with a stronger one.", secret.length()); + if (privateKey.length() < 32) { + log.warn("The provided secret which owns {} characters is too weak. Please consider" + + " replacing it with a stronger one.", privateKey.length()); + } } this.jtiCreator = jtiCreator; this.algorithm = config .getAlgorithm(algorithm) - .apply(secret); + .apply(privateKey, publicKey); this.issuer = issuer; this.verifier = JWT.require(this.algorithm).build(); this.objectMapper = objectMapper; } /** - * Creates a new instance of {@link AuthzeroTokenResolver} with the - * provided configurations and a simple UUID GuidCreator. + * Creates a new instance of {@link AuthzeroTokenResolver} with the provided configurations + * and a simple UUID GuidCreator. * * @param algorithm the algorithm used for signing and verifying JWT tokens * @param issuer the issuer claim value to be included in JWT tokens - * @param secret the secret used for HMAC-based algorithms (HS256, - * HS384, HS512) for token signing and verification + * @param privateKey the secret used for HMAC-based algorithms (HS256, HS384, HS512) for + * token signing and verification, or the private key for ECDSA-based + * algorithms + * @param publicKey the public key for ECDSA-based algorithms * @param objectMapper Jackson Databind JSON Handler */ - public AuthzeroTokenResolver(TokenAlgorithm algorithm, String issuer, String secret, ObjectMapper objectMapper) { - this(UUID::randomUUID, algorithm, issuer, secret, objectMapper); + public AuthzeroTokenResolver(TokenAlgorithm algorithm, + String issuer, + String privateKey, + String publicKey, + ObjectMapper objectMapper) { + this(UUID::randomUUID, algorithm, issuer, privateKey, publicKey, objectMapper); } /** - * Creates a new instance of {@link AuthzeroTokenResolver} with the - * provided configurations and a simple UUID GuidCreator. + * Creates a new instance of {@link AuthzeroTokenResolver} with the provided configurations + * and a simple UUID GuidCreator. * - * @param algorithm the algorithm used for signing and verifying JWT tokens - * @param issuer the issuer claim value to be included in JWT tokens - * @param secret the secret used for HMAC-based algorithms (HS256, - * HS384, HS512) for token signing and verification + * @param algorithm the algorithm used for signing and verifying JWT tokens + * @param issuer the issuer claim value to be included in JWT tokens + * @param privateKey the secret used for HMAC-based algorithms (HS256, HS384, HS512) for + * token signing and verification, or the private key for ECDSA-based + * algorithms + * @param publicKey the public key for ECDSA-based algorithms */ - public AuthzeroTokenResolver(TokenAlgorithm algorithm, String issuer, String secret) { - this(UUID::randomUUID, algorithm, issuer, secret, new ObjectMapper()); + public AuthzeroTokenResolver(TokenAlgorithm algorithm, String issuer, String privateKey, String publicKey) { + this(UUID::randomUUID, algorithm, issuer, privateKey, publicKey, new ObjectMapper()); } /** @@ -163,11 +176,10 @@ public AuthzeroTokenResolver(TokenAlgorithm algorithm, String issuer, String sec * UUID GuidCreator. * * @param issuer the issuer claim value to be included in JWT tokens - * @param secret the secret used for HMAC-based algorithms (HS256, - * HS384, HS512) for token signing and verification + * @param secret the secret used for HS256 algorithms for token signing and verification */ public AuthzeroTokenResolver(String issuer, String secret) { - this(UUID::randomUUID, TokenAlgorithm.HS256, issuer, secret, new ObjectMapper()); + this(UUID::randomUUID, TokenAlgorithm.HS256, issuer, secret, "", new ObjectMapper()); } /** @@ -183,7 +195,7 @@ public AuthzeroTokenResolver(String issuer) { this.jtiCreator = UUID::randomUUID; this.algorithm = config .getAlgorithm(TokenAlgorithm.HS256) - .apply(secret); + .apply(secret, ""); this.issuer = issuer; this.verifier = JWT.require(this.algorithm).build(); this.objectMapper = new ObjectMapper(); diff --git a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java b/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java index 768b8ce..829c8ad 100644 --- a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java +++ b/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java @@ -17,6 +17,7 @@ package com.onixbyte.simplejwt.authzero.config; +import com.onixbyte.security.KeyLoader; import com.onixbyte.simplejwt.TokenResolver; import com.onixbyte.simplejwt.authzero.AuthzeroTokenResolver; import com.onixbyte.simplejwt.config.TokenResolverConfig; @@ -30,6 +31,7 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.*; +import java.util.function.BiFunction; import java.util.function.Function; /** @@ -58,7 +60,7 @@ * @since 1.0.0 */ public final class AuthzeroTokenResolverConfig - implements TokenResolverConfig> { + implements TokenResolverConfig> { /** * Gets the instance of {@code AuthzeroTokenResolverConfig}. @@ -92,7 +94,7 @@ public static AuthzeroTokenResolverConfig getInstance() { * this implementation */ @Override - public Function getAlgorithm(TokenAlgorithm algorithm) { + public BiFunction getAlgorithm(TokenAlgorithm algorithm) { return Optional.of(SUPPORTED_ALGORITHMS).map((entry) -> entry.get(algorithm)) .orElseThrow(() -> new UnsupportedAlgorithmException("The specified algorithm is not supported yet.")); } @@ -127,26 +129,20 @@ private AuthzeroTokenResolverConfig() { * specific algorithms. The mapping is used to provide proper algorithm * resolution and processing within the {@link AuthzeroTokenResolver}. */ - private static final Map> SUPPORTED_ALGORITHMS = new HashMap<>() {{ - put(TokenAlgorithm.HS256, Algorithm::HMAC256); - put(TokenAlgorithm.HS384, Algorithm::HMAC384); - put(TokenAlgorithm.HS512, Algorithm::HMAC512); - put(TokenAlgorithm.ES256, (String privateKey) -> { - try { - var keyBytes = Base64.getDecoder().decode(privateKey); - var spec = new PKCS8EncodedKeySpec(keyBytes); - var kf = KeyFactory.getInstance("EC"); - var key = kf.generatePrivate(spec); - if (key instanceof ECPrivateKey pk) { - return Algorithm.ECDSA256(pk); - } else { - throw new RuntimeException("Type error!"); - } - } catch (NoSuchAlgorithmException ignored) { - } catch (InvalidKeySpecException e) { - throw new RuntimeException(e); - } - return null; - }); + private static final + Map> SUPPORTED_ALGORITHMS = + new HashMap<>() {{ + put(TokenAlgorithm.HS256, (String secret, String ignoredValue) -> + Algorithm.HMAC256(secret)); + put(TokenAlgorithm.HS384, (String secret, String ignoredValue) -> + Algorithm.HMAC384(secret)); + put(TokenAlgorithm.HS512, (String secret, String ignoredValue) -> + Algorithm.HMAC512(secret)); + put(TokenAlgorithm.ES256, (String privateKey, String publicKey) -> + Algorithm.ECDSA256(KeyLoader.loadEcdsaPrivateKey(privateKey))); + put(TokenAlgorithm.ES384, (String privateKey, String publicKey) -> + Algorithm.ECDSA256(KeyLoader.loadEcdsaPrivateKey(privateKey))); + put(TokenAlgorithm.ES512, (String privateKey, String publicKey) -> + Algorithm.ECDSA256(KeyLoader.loadEcdsaPrivateKey(privateKey))); }}; } diff --git a/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/config/TokenResolverConfig.java b/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/config/TokenResolverConfig.java index 2252563..6b961d8 100644 --- a/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/config/TokenResolverConfig.java +++ b/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/config/TokenResolverConfig.java @@ -20,6 +20,8 @@ import com.onixbyte.simplejwt.TokenResolver; import com.onixbyte.simplejwt.constants.TokenAlgorithm; +import java.util.List; + /** * The {@code TokenResolverConfig} provides a mechanism to configure an * implementation of {@link TokenResolver} with algorithm functions. @@ -49,11 +51,15 @@ public interface TokenResolverConfig { * implementation that can be used by the {@link TokenResolver} to handle * the specific algorithm. * - * @param algorithm the {@link TokenAlgorithm} for which the algorithm - * function is required - * @return the algorithm function associated with the given {@link - * TokenAlgorithm} + * @param algorithm the {@link TokenAlgorithm} for which the algorithm function is required + * @return the algorithm function associated with the given {@link TokenAlgorithm} */ Algo getAlgorithm(TokenAlgorithm algorithm); + List ECDSA_ALGORITHMS = + List.of(TokenAlgorithm.ES256, TokenAlgorithm.ES384, TokenAlgorithm.ES512); + + List HMAC_ALGORITHMS = + List.of(TokenAlgorithm.HS256, TokenAlgorithm.HS384, TokenAlgorithm.HS512); + } diff --git a/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/constants/PredefinedKeys.java b/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/constants/PredefinedKeys.java index 0a9bdb5..c531d56 100644 --- a/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/constants/PredefinedKeys.java +++ b/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/constants/PredefinedKeys.java @@ -35,13 +35,14 @@ *

  • {@link #JWT_ID}: Represents the "jti" (JWT ID) claim.
  • * *

    - * The class also contains a list of all the standard claim constants, accessible via the {@link #KEYS} field. This - * list can be useful for iterating through all the standard claims or checking for the presence of specific claims. + * The class also contains a list of all the standard claim constants, accessible via the {@link + * #KEYS} field. This list can be useful for iterating through all the standard claims or checking + * for the presence of specific claims. *

    - * Note: This class is final and cannot be instantiated. It only serves as a utility class to hold the standard JWT - * claim constants. + * Note: This class is final and cannot be instantiated. It only serves as a utility class to hold + * the standard JWT claim constants. * - * @author Zihlu Wang + * @author zihluwang * @version 1.1.0 * @since 1.0.0 */ @@ -85,7 +86,8 @@ public final class PredefinedKeys { /** * List containing all the standard JWT claim constants. */ - public static final List KEYS = List.of(ISSUER, SUBJECT, AUDIENCE, EXPIRATION_TIME, NOT_BEFORE, ISSUED_AT, JWT_ID); + public static final List KEYS = + List.of(ISSUER, SUBJECT, AUDIENCE, EXPIRATION_TIME, NOT_BEFORE, ISSUED_AT, JWT_ID); /** * Private constructor will protect this class from being instantiated. diff --git a/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/JjwtTokenResolver.java b/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/JjwtTokenResolver.java index 3607631..dc63840 100644 --- a/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/JjwtTokenResolver.java +++ b/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/JjwtTokenResolver.java @@ -112,9 +112,6 @@ public JjwtTokenResolver(GuidCreator jtiCreator, TokenAlgorithm algorithm, St } if (secret.length() < 32) { - log.error(""" - The provided secret which owns {} characters is too weak. Please replace it with a stronger one.""", - secret.length()); throw new WeakSecretException(""" The provided secret which owns %s characters is too weak. Please replace it with a stronger one.""" .formatted(secret.length())); @@ -166,9 +163,6 @@ public JjwtTokenResolver(String issuer, String secret) { } if (secret.length() < 32) { - log.error( - "The provided secret which owns {} characters is too weak. Please replace it with a stronger one.", - secret.length()); throw new WeakSecretException( "The provided secret which owns %s characters is too weak. Please replace it with a stronger one." .formatted(secret.length())); diff --git a/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java b/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java index 03dad48..4c93400 100644 --- a/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java +++ b/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java @@ -23,6 +23,7 @@ import com.onixbyte.simplejwt.autoconfiguration.properties.SimpleJwtProperties; import com.auth0.jwt.interfaces.DecodedJWT; import com.fasterxml.jackson.databind.ObjectMapper; +import com.onixbyte.simplejwt.config.TokenResolverConfig; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -35,25 +36,23 @@ import org.springframework.context.annotation.Bean; /** - * {@code AuthzeroTokenResolverAutoConfiguration} is responsible for - * automatically configuring the Simple JWT library with - * {@code com.auth0:java-jwt} when used in a Spring Boot application. It - * provides default settings and configurations to ensure that the library - * works smoothly without requiring manual configuration. + * {@code AuthzeroTokenResolverAutoConfiguration} is responsible for automatically configuring the + * Simple JWT library with + * {@code com.auth0:java-jwt} when used in a Spring Boot application. It provides default settings + * and configurations to ensure that the library works smoothly without requiring + * manual configuration. *

    - * This autoconfiguration class sets up the necessary beans and components - * required for JWT generation and validation. It automatically creates and - * configures the {@link AuthzeroTokenResolver} bean based on the available - * options and properties. + * This autoconfiguration class sets up the necessary beans and components required for JWT + * generation and validation. It automatically creates and configures the + * {@link AuthzeroTokenResolver} bean based on the available options and properties. *

    - * Developers using the Simple JWT library with Spring Boot do not need to - * explicitly configure the library, as the autoconfiguration takes care of - * setting up the necessary components and configurations automatically. - * However, developers still have the flexibility to customise the behavior of - * the library by providing their own configurations and properties. + * Developers using the Simple JWT library with Spring Boot do not need to explicitly configure the + * library, as the autoconfiguration takes care of setting up the necessary components and + * configurations automatically. However, developers still have the flexibility to customise the + * behavior of the library by providing their own configurations and properties. * - * @author Zihlu Wang - * @version 1.0.0 + * @author zihluwang + * @version 1.6.0 * @since 1.0.0 */ @Slf4j @@ -70,11 +69,13 @@ public class AuthzeroTokenResolverAutoConfiguration { * provided SimpleJwtProperties. * * @param simpleJwtProperties a {@link SimpleJwtProperties} instance - * @param jtiCreator a creator to create ids for JSON Web Token - * @param objectMapper jackson JSON Handler + * @param jtiCreator a creator to create ids for JSON Web Token + * @param objectMapper jackson JSON Handler */ @Autowired - public AuthzeroTokenResolverAutoConfiguration(SimpleJwtProperties simpleJwtProperties, @Qualifier("jtiCreator") GuidCreator jtiCreator, ObjectMapper objectMapper) { + public AuthzeroTokenResolverAutoConfiguration(SimpleJwtProperties simpleJwtProperties, + @Qualifier("jtiCreator") GuidCreator jtiCreator, + ObjectMapper objectMapper) { this.jtiCreator = jtiCreator; this.simpleJwtProperties = simpleJwtProperties; this.objectMapper = objectMapper; @@ -90,13 +91,25 @@ public AuthzeroTokenResolverAutoConfiguration(SimpleJwtProperties simpleJwtPrope */ @Bean public TokenResolver tokenResolver() { - return new AuthzeroTokenResolver( - jtiCreator, - simpleJwtProperties.algorithm(), - simpleJwtProperties.issuer(), - simpleJwtProperties.secret(), - objectMapper - ); + if (TokenResolverConfig.HMAC_ALGORITHMS.contains(simpleJwtProperties.algorithm())) { + return new AuthzeroTokenResolver( + jtiCreator, + simpleJwtProperties.algorithm(), + simpleJwtProperties.issuer(), + simpleJwtProperties.secret(), + "", + objectMapper + ); + } else { + return new AuthzeroTokenResolver( + jtiCreator, + simpleJwtProperties.algorithm(), + simpleJwtProperties.issuer(), + simpleJwtProperties.getPrivateKey(), + simpleJwtProperties.getPublicKey(), + objectMapper + ); + } } private final GuidCreator jtiCreator; diff --git a/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/properties/SimpleJwtProperties.java b/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/properties/SimpleJwtProperties.java index 47d121e..cf1b90d 100644 --- a/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/properties/SimpleJwtProperties.java +++ b/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/properties/SimpleJwtProperties.java @@ -71,6 +71,13 @@ public SimpleJwtProperties() { */ private String secret = SecretCreator.createSecret(32, true, true, true); + /** + * The private key of + */ + private String privateKey; + + private String publicKey; + /** * Returns the JWT algorithm configured in the properties. * From fe887886116a48aadec13935962f76f6bfba8eb9 Mon Sep 17 00:00:00 2001 From: zihluwang Date: Tue, 6 Aug 2024 22:02:00 +0800 Subject: [PATCH 5/5] feat: added ECDSA-based algorithm support make the simple-jwt auth0 implementation can use ECDSA-based algorithms BREAKING CHANGE: the io.jsonwebtoken:jjwt implementation was discontinued since its design made a big challenge to encapsulation --- gradle.properties | 6 +- settings.gradle.kts | 6 +- .../authzero/AuthzeroTokenResolver.java | 299 +++++++---- .../config/AuthzeroTokenResolverConfig.java | 148 ------ .../authzero/config/package-info.java | 54 -- .../simplejwt/config/TokenResolverConfig.java | 65 --- .../simplejwt/config/package-info.java | 32 -- .../simplejwt/constants/TokenAlgorithm.java | 16 + .../exceptions/IllegalKeyPairException.java | 53 ++ .../exceptions/IllegalSecretException.java | 54 ++ simple-jwt-jjwt/README.md | 54 -- simple-jwt-jjwt/build.gradle.kts | 107 ---- .../simplejwt/jjwt/JjwtTokenResolver.java | 474 ------------------ .../jjwt/config/JjwtTokenResolverConfig.java | 121 ----- .../simplejwt/jjwt/config/package-info.java | 53 -- .../onixbyte/simplejwt/jjwt/package-info.java | 58 --- .../src/main/resources/logback.xml | 32 -- .../build.gradle.kts | 7 +- ...uthzeroTokenResolverAutoConfiguration.java | 34 +- .../JjwtTokenResolverAutoConfiguration.java | 110 ---- .../properties/SimpleJwtProperties.java | 36 +- 21 files changed, 343 insertions(+), 1476 deletions(-) delete mode 100644 simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java delete mode 100644 simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/package-info.java delete mode 100644 simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/config/TokenResolverConfig.java delete mode 100644 simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/config/package-info.java create mode 100644 simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/exceptions/IllegalKeyPairException.java create mode 100644 simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/exceptions/IllegalSecretException.java delete mode 100644 simple-jwt-jjwt/README.md delete mode 100644 simple-jwt-jjwt/build.gradle.kts delete mode 100644 simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/JjwtTokenResolver.java delete mode 100644 simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/config/JjwtTokenResolverConfig.java delete mode 100644 simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/config/package-info.java delete mode 100644 simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/package-info.java delete mode 100644 simple-jwt-jjwt/src/main/resources/logback.xml delete mode 100644 simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/JjwtTokenResolverAutoConfiguration.java diff --git a/gradle.properties b/gradle.properties index 7812492..c4abfd9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,9 +15,9 @@ # limitations under the License. # -jacksonVersion=2.17.0 +jacksonVersion=2.17.2 javaJwtVersion=4.4.0 -jjwtVersion=0.12.5 +jjwtVersion=0.12.6 junitVersion=5.10.2 logbackVersion=1.5.4 lombokVersion=1.18.30 @@ -26,7 +26,7 @@ springVersion=6.1.3 springBootVersion=3.2.3 buildGroupId=com.onixbyte -buildVersion=1.5.0 +buildVersion=1.6.0 projectUrl=https://onixbyte.com/JDevKit projectGithubUrl=https://github.com/OnixByte/JDevKit licenseName=The Apache License, Version 2.0 diff --git a/settings.gradle.kts b/settings.gradle.kts index 3496393..d39293c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,13 +20,11 @@ rootProject.name = "JDevKit" include( "devkit-core", "devkit-utils", + "map-util-unsafe", "guid", - "webcal", + "key-pair-loader", "simple-jwt-facade", "simple-jwt-authzero", - "simple-jwt-jjwt", "simple-jwt-spring-boot-starter", "property-guard-spring-boot-starter" ) -include("map-util-unsafe") -include("key-pair-loader") diff --git a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java b/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java index cf97ac5..8e6b308 100644 --- a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java +++ b/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/AuthzeroTokenResolver.java @@ -19,13 +19,11 @@ import com.onixbyte.devkit.utils.Base64Util; import com.onixbyte.guid.GuidCreator; -import com.onixbyte.simplejwt.SecretCreator; +import com.onixbyte.security.KeyLoader; import com.onixbyte.simplejwt.TokenPayload; import com.onixbyte.simplejwt.TokenResolver; import com.onixbyte.simplejwt.annotations.ExcludeFromPayload; import com.onixbyte.simplejwt.annotations.TokenEnum; -import com.onixbyte.simplejwt.authzero.config.AuthzeroTokenResolverConfig; -import com.onixbyte.simplejwt.config.TokenResolverConfig; import com.onixbyte.simplejwt.constants.PredefinedKeys; import com.onixbyte.simplejwt.constants.TokenAlgorithm; import com.auth0.jwt.JWT; @@ -37,13 +35,20 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.onixbyte.simplejwt.exceptions.IllegalKeyPairException; +import com.onixbyte.simplejwt.exceptions.IllegalSecretException; +import com.onixbyte.simplejwt.exceptions.UnsupportedAlgorithmException; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationTargetException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import java.time.Duration; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Function; /** * The {@code AuthzeroTokenResolver} class is an implementation of the {@link TokenResolver} @@ -97,110 +102,160 @@ public class AuthzeroTokenResolver implements TokenResolver { /** - * Creates a new instance of {@code AuthzeroTokenResolver} with the provided configurations. + * Create a builder of {@link AuthzeroTokenResolver}. * - * @param jtiCreator the {@link GuidCreator} used for generating unique identifiers for "jti" - * claim in JWT tokens - * @param algorithm the algorithm used for signing and verifying JWT tokens - * @param issuer the issuer claim value to be included in JWT tokens - * @param privateKey the secret used for HMAC-based algorithms (HS256, HS384, HS512) for - * token signing and verification, or the private key for ECDSA-based - * algorithms - * @param publicKey the public key for ECDSA-based algorithms - * @param objectMapper JSON handler + * @return a builder instance */ - public AuthzeroTokenResolver(GuidCreator jtiCreator, - TokenAlgorithm algorithm, - String issuer, - String privateKey, - String publicKey, - ObjectMapper objectMapper) { - if (TokenResolverConfig.HMAC_ALGORITHMS.contains(algorithm)) { - if (privateKey == null || privateKey.isBlank()) { - throw new IllegalArgumentException("A secret is required to build a JSON Web Token."); - } - - if (privateKey.length() < 32) { - log.warn("The provided secret which owns {} characters is too weak. Please consider" + - " replacing it with a stronger one.", privateKey.length()); - } - } - - this.jtiCreator = jtiCreator; - this.algorithm = config - .getAlgorithm(algorithm) - .apply(privateKey, publicKey); - this.issuer = issuer; - this.verifier = JWT.require(this.algorithm).build(); - this.objectMapper = objectMapper; + public static Builder builder() { + return new Builder(); } /** - * Creates a new instance of {@link AuthzeroTokenResolver} with the provided configurations - * and a simple UUID GuidCreator. - * - * @param algorithm the algorithm used for signing and verifying JWT tokens - * @param issuer the issuer claim value to be included in JWT tokens - * @param privateKey the secret used for HMAC-based algorithms (HS256, HS384, HS512) for - * token signing and verification, or the private key for ECDSA-based - * algorithms - * @param publicKey the public key for ECDSA-based algorithms - * @param objectMapper Jackson Databind JSON Handler + * Builder for {@link AuthzeroTokenResolver} */ - public AuthzeroTokenResolver(TokenAlgorithm algorithm, - String issuer, - String privateKey, - String publicKey, - ObjectMapper objectMapper) { - this(UUID::randomUUID, algorithm, issuer, privateKey, publicKey, objectMapper); - } + public static class Builder { + + /** + * GuidCreator used for generating unique identifiers for "jti" claim in JWT tokens. + */ + private GuidCreator jtiCreator; + + /** + * The algorithm used for signing and verifying JWT tokens. + */ + private Algorithm algorithm; + + /** + * The issuer claim value to be included in JWT tokens. + */ + private String issuer; + + /** + * Jackson JSON handler. + */ + private ObjectMapper objectMapper; + + /** + * The secret to sign a JWT with HMAC based algorithm. + */ + private String secret; + + /** + * The private key to sign a JWT with ECDSA based algorithm. + */ + private ECPrivateKey privateKey; + + /** + * The public key to read a JWT with ECDSA based algorithm. + */ + private ECPublicKey publicKey; + + /** + * Private constructor prevents this class being initialised at somewhere it should not + * be initialised. + */ + private Builder() { + } - /** - * Creates a new instance of {@link AuthzeroTokenResolver} with the provided configurations - * and a simple UUID GuidCreator. - * - * @param algorithm the algorithm used for signing and verifying JWT tokens - * @param issuer the issuer claim value to be included in JWT tokens - * @param privateKey the secret used for HMAC-based algorithms (HS256, HS384, HS512) for - * token signing and verification, or the private key for ECDSA-based - * algorithms - * @param publicKey the public key for ECDSA-based algorithms - */ - public AuthzeroTokenResolver(TokenAlgorithm algorithm, String issuer, String privateKey, String publicKey) { - this(UUID::randomUUID, algorithm, issuer, privateKey, publicKey, new ObjectMapper()); - } + /** + * Set the secret to sign a JWT. + * + * @param secret the secret + * @return the builder instance + */ + public Builder secret(String secret) { + this.secret = secret; + return this; + } - /** - * Creates a new instance of {@link AuthzeroTokenResolver} with the - * provided configurations, HMAC256 algorithm and a simple - * UUID GuidCreator. - * - * @param issuer the issuer claim value to be included in JWT tokens - * @param secret the secret used for HS256 algorithms for token signing and verification - */ - public AuthzeroTokenResolver(String issuer, String secret) { - this(UUID::randomUUID, TokenAlgorithm.HS256, issuer, secret, "", new ObjectMapper()); - } + /** + * Set the key pair to sign a JWT. + * + * @param publicKey the pem formatted public key text + * @param privateKey the pem formatted private key text + * @return the builder instance + */ + public Builder keyPair(String publicKey, String privateKey) { + this.publicKey = KeyLoader.loadEcdsaPublicKey(publicKey); + this.privateKey = KeyLoader.loadEcdsaPrivateKey(privateKey); + return this; + } - /** - * Creates a new instance of {@link AuthzeroTokenResolver} with the - * provided configurations, HMAC256 algorithm and a simple - * UUID GuidCreator. - * - * @param issuer the issuer claim value to be included in JWT tokens - */ - public AuthzeroTokenResolver(String issuer) { - var secret = SecretCreator.createSecret(32, true, true, true); + /** + * Set the algorithm to sign a JWT. + *

    + * A secret required by HMAC-based algorithms, or key pair required by ECDSA-based + * algorithms need to be set before initialise an algorithm. + * + * @param algorithm an {@link TokenAlgorithm} value + * @return the builder instance + */ + public Builder algorithm(TokenAlgorithm algorithm) { + // check the secret or key pair before algorithm initialised + if (HMAC_ALGORITHMS.containsKey(algorithm)) { + if (Objects.isNull(secret) || secret.isBlank()) { + throw new IllegalSecretException(""" + Please specify a secret before define an algorithm."""); + } + } else if (ECDSA_ALGORITHMS.containsKey(algorithm)) { + if (Objects.isNull(publicKey) || Objects.isNull(privateKey)) { + throw new IllegalKeyPairException(""" + Please specify a ECDSA key pair before define an algorithm."""); + } + } - this.jtiCreator = UUID::randomUUID; - this.algorithm = config - .getAlgorithm(TokenAlgorithm.HS256) - .apply(secret, ""); - this.issuer = issuer; - this.verifier = JWT.require(this.algorithm).build(); - this.objectMapper = new ObjectMapper(); + // initialise algorithm + this.algorithm = switch (algorithm) { + case HS256, HS384, HS512 -> HMAC_ALGORITHMS.get(algorithm).apply(secret); + case ES256, ES384, ES512 -> ECDSA_ALGORITHMS.get(algorithm) + .apply(publicKey, privateKey); + default -> throw new UnsupportedAlgorithmException(""" + This algorithm is not supported yet."""); + }; + return this; + } + + /** + * Set the object mapper. + * + * @param objectMapper an object mapper + * @return the builder instance + */ + public Builder objectMapper(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + return this; + } + + /** + * Set the creator of JWT id. + * + * @param jtiCreator a creator to create JWT id + * @return the builder instance + */ + public Builder jtiCreator(GuidCreator jtiCreator) { + this.jtiCreator = jtiCreator; + return this; + } - log.info("The secret has been set to {}.", secret); + /** + * Set the issuer of created JWT. + * + * @param issuer the person or organisation issued this JWT + * @return the builder instance + */ + public Builder issuer(String issuer) { + this.issuer = issuer; + return this; + } + + /** + * Create an {@link AuthzeroTokenResolver} instance + * + * @return created instance + */ + public AuthzeroTokenResolver build() { + return new AuthzeroTokenResolver(jtiCreator, algorithm, issuer, objectMapper); + } } /** @@ -251,7 +306,7 @@ public String createToken(Duration expireAfter, String audience, String subject, */ @Override public String createToken(Duration expireAfter, String audience, String subject, T payload) { - final JWTCreator.Builder builder = JWT.create(); + final var builder = JWT.create(); buildBasicInfo(builder, expireAfter, subject, audience); var payloadClass = payload.getClass(); @@ -294,7 +349,7 @@ public String createToken(Duration expireAfter, String */ @Override public DecodedJWT resolve(String token) { - return verifier.verify(token); + return jwtVerifier.verify(token); } /** @@ -450,15 +505,15 @@ private void buildBasicInfo(JWTCreator.Builder builder, Duration expireAfter, St // bind issuer (iss) builder.withIssuer(issuer); // bind issued at (iat) - builder.withIssuedAt(Date.from(now.atZone(ZoneId.systemDefault()).toInstant())); + builder.withIssuedAt(now.atZone(ZoneId.systemDefault()).toInstant()); // bind not before (nbf) - builder.withNotBefore(Date.from(now.atZone(ZoneId.systemDefault()).toInstant())); + builder.withNotBefore(now.atZone(ZoneId.systemDefault()).toInstant()); // bind audience (aud) builder.withAudience(audience); // bind subject (sub) builder.withSubject(subject); // bind expire at (exp) - builder.withExpiresAt(Date.from(now.plus(expireAfter).atZone(ZoneId.systemDefault()).toInstant())); + builder.withExpiresAt(now.plus(expireAfter).atZone(ZoneId.systemDefault()).toInstant()); // bind JWT Id (jti) builder.withJWTId(jtiCreator.nextId().toString()); } @@ -534,14 +589,13 @@ private String buildToken(JWTCreator.Builder builder) { /** * Default type reference for Map. */ - private static class MapTypeReference extends TypeReference> { + public static class MapTypeReference extends TypeReference> { MapTypeReference() { } } /** - * GuidCreator used for generating unique identifiers for "jti" claim in - * JWT tokens. + * GuidCreator used for generating unique identifiers for "jti" claim in JWT tokens. */ private final GuidCreator jtiCreator; @@ -558,12 +612,45 @@ private static class MapTypeReference extends TypeReference> /** * The JSON Web Token resolver. */ - private final JWTVerifier verifier; + private final JWTVerifier jwtVerifier; /** * Jackson JSON handler. */ private final ObjectMapper objectMapper; - private final AuthzeroTokenResolverConfig config = AuthzeroTokenResolverConfig.getInstance(); + /** + * A map contains all HMAC-SHA based algorithms. + */ + public static final Map> HMAC_ALGORITHMS = Map.of( + TokenAlgorithm.HS256, Algorithm::HMAC256, + TokenAlgorithm.HS384, Algorithm::HMAC384, + TokenAlgorithm.HS512, Algorithm::HMAC512 + ); + + /** + * A map contains all ECDSA based algorithms. + */ + public static final Map> ECDSA_ALGORITHMS = Map.of( + TokenAlgorithm.ES256, Algorithm::ECDSA256, + TokenAlgorithm.ES384, Algorithm::ECDSA384, + TokenAlgorithm.ES512, Algorithm::ECDSA512 + ); + + /** + * Private constructor prevent this class being initialised mistakenly. + * + * @param jtiCreator a creator that can create JWT id + * @param algorithm an algorithm to sign this JWT + * @param issuer the person or organisation who issued this JWT + * @param objectMapper a mapper for handling JSON serialisation and deserialization + */ + private AuthzeroTokenResolver(GuidCreator jtiCreator, Algorithm algorithm, String issuer, ObjectMapper objectMapper) { + this.jtiCreator = jtiCreator; + this.algorithm = algorithm; + this.issuer = issuer; + this.objectMapper = objectMapper; + this.jwtVerifier = JWT.require(algorithm).build(); + } + } diff --git a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java b/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java deleted file mode 100644 index 829c8ad..0000000 --- a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2024-2024 OnixByte. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.onixbyte.simplejwt.authzero.config; - -import com.onixbyte.security.KeyLoader; -import com.onixbyte.simplejwt.TokenResolver; -import com.onixbyte.simplejwt.authzero.AuthzeroTokenResolver; -import com.onixbyte.simplejwt.config.TokenResolverConfig; -import com.onixbyte.simplejwt.constants.TokenAlgorithm; -import com.onixbyte.simplejwt.exceptions.UnsupportedAlgorithmException; -import com.auth0.jwt.algorithms.Algorithm; - -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.ECPrivateKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.*; -import java.util.function.BiFunction; -import java.util.function.Function; - -/** - * The {@code AuthzeroTokenResolverConfig} class provides the configuration for - * the {@link AuthzeroTokenResolver}. - *

    - * This configuration is used to establish the mapping between the standard {@link TokenAlgorithm} - * defined in the {@code cn.org.codecrafters:simple-jwt-facade} and the specific algorithms used - * by the {@code com.auth0:java-jwt} library, which is the underlying library used by - * {@link AuthzeroTokenResolver} to handle JSON Web Tokens (JWTs). - *

    - * Algorithm Mapping: - * The {@code AuthzeroTokenResolverConfig} allows specifying the relationships between the standard - * {@link TokenAlgorithm} instances supported by {@link AuthzeroTokenResolver} and the corresponding - * algorithms used by the {@code com.auth0:java-jwt} library. The mapping is achieved using a Map, - * where the keys are the standard {@link TokenAlgorithm} instances, and the values represent the - * algorithm functions used by {@code com.auth0:java-jwt} library for each corresponding key. - *

    - * Note: - * The provided algorithm mapping should be consistent with the actual algorithms supported and used - * by the {@code com.auth0:java-jwt} library. It is crucial to ensure that the mapping is accurate - * to enable proper token validation and processing within the {@link AuthzeroTokenResolver}. - * - * @author Zihlu Wang - * @version 1.1.1 - * @since 1.0.0 - */ -public final class AuthzeroTokenResolverConfig - implements TokenResolverConfig> { - - /** - * Gets the instance of {@code AuthzeroTokenResolverConfig}. - *

    - * This method returns the singleton instance of {@code AuthzeroTokenResolverConfig}. If the - * instance is not yet created, it will create a new instance and return it. Otherwise, it - * returns the existing instance. - * - * @return the instance of {@code AuthzeroTokenResolverConfig} - */ - public static AuthzeroTokenResolverConfig getInstance() { - if (Objects.isNull(instance)) { - instance = new AuthzeroTokenResolverConfig(); - } - - return instance; - } - - /** - * Gets the algorithm function corresponding to the specified {@link TokenAlgorithm}. - *

    - * This method returns the algorithm function associated with the given {@link TokenAlgorithm}. - * The provided {@link TokenAlgorithm} represents the specific algorithm for which the - * corresponding algorithm function is required. The returned Algorithm Function represents the - * function implementation that can be used by the {@link TokenResolver} to handle the - * specific algorithm. - * - * @param algorithm the {@link TokenAlgorithm} for which the algorithm function is required - * @return the algorithm function associated with the given {@link TokenAlgorithm} - * @throws UnsupportedAlgorithmException if the given {@code algorithm} is not supported by - * this implementation - */ - @Override - public BiFunction getAlgorithm(TokenAlgorithm algorithm) { - return Optional.of(SUPPORTED_ALGORITHMS).map((entry) -> entry.get(algorithm)) - .orElseThrow(() -> new UnsupportedAlgorithmException("The specified algorithm is not supported yet.")); - } - - /** - * Constructs a new instance of {@code AuthzeroTokenResolverConfig}. - *

    - * The constructor is set as private to enforce the singleton pattern for - * this configuration class. Instances of - * {@code AuthzeroTokenResolverConfig} should be obtained through the - * {@link #getInstance()} method. - */ - private AuthzeroTokenResolverConfig() { - } - - /** - * The singleton instance of {@code AuthzeroTokenResolverConfig}. - *

    - * This instance is used to ensure that only one instance of - * {@code AuthzeroTokenResolverConfig} is created and shared throughout the - * application. The singleton pattern is implemented to provide centralised - * configuration and avoid redundant object creation. - */ - private static AuthzeroTokenResolverConfig instance; - - /** - * The supported algorithms and their corresponding algorithm functions. - *

    - * This map stores the supported algorithms as keys and their corresponding - * algorithm functions as values. The algorithm functions represent the - * functions used by the {@code com.auth0:java-jwt} library to handle the - * specific algorithms. The mapping is used to provide proper algorithm - * resolution and processing within the {@link AuthzeroTokenResolver}. - */ - private static final - Map> SUPPORTED_ALGORITHMS = - new HashMap<>() {{ - put(TokenAlgorithm.HS256, (String secret, String ignoredValue) -> - Algorithm.HMAC256(secret)); - put(TokenAlgorithm.HS384, (String secret, String ignoredValue) -> - Algorithm.HMAC384(secret)); - put(TokenAlgorithm.HS512, (String secret, String ignoredValue) -> - Algorithm.HMAC512(secret)); - put(TokenAlgorithm.ES256, (String privateKey, String publicKey) -> - Algorithm.ECDSA256(KeyLoader.loadEcdsaPrivateKey(privateKey))); - put(TokenAlgorithm.ES384, (String privateKey, String publicKey) -> - Algorithm.ECDSA256(KeyLoader.loadEcdsaPrivateKey(privateKey))); - put(TokenAlgorithm.ES512, (String privateKey, String publicKey) -> - Algorithm.ECDSA256(KeyLoader.loadEcdsaPrivateKey(privateKey))); - }}; -} diff --git a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/package-info.java b/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/package-info.java deleted file mode 100644 index 1461f12..0000000 --- a/simple-jwt-authzero/src/main/java/com/onixbyte/simplejwt/authzero/config/package-info.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2024-2024 OnixByte. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * The package {@code cn.org.codecrafters.simplejwt.authzero.config} contains - * configuration classes related to the {@link - * com.onixbyte.simplejwt.authzero.AuthzeroTokenResolver} - * implementation. - *

    - * The classes in this package provide configuration options and settings for - * the {@link com.onixbyte.simplejwt.authzero.AuthzeroTokenResolver}, - * which is used for resolving JSON Web Tokens (JWT) using the Auth0 library. - *

    - * The {@link - * com.onixbyte.simplejwt.authzero.config.AuthzeroTokenResolverConfig} - * class is a configuration class that defines the mapping between standard - * {@link com.onixbyte.simplejwt.constants.TokenAlgorithm} and the - * corresponding function implementation used by {@link - * com.onixbyte.simplejwt.authzero.AuthzeroTokenResolver} for handling - * JWT algorithms. It enables developers to specify and customize the - * algorithm functions according to the chosen JWT algorithm and the library - * being used. - *

    - * The configuration options in this package help developers integrate and - * configure the {@link - * com.onixbyte.simplejwt.authzero.AuthzeroTokenResolver} seamlessly - * into their Spring Boot applications. Developers can fine-tune the token - * resolution process and customize algorithm handling to align with their - * specific requirements and desired level of security. - *

    - * It is recommended to explore the classes in this package to understand how - * to configure and use the {@link - * com.onixbyte.simplejwt.authzero.AuthzeroTokenResolver} effectively - * in the Spring Boot environment to handle JWT authentication and - * authorisation securely and efficiently. - * - * @since 1.0.0 - */ - -package com.onixbyte.simplejwt.authzero.config; \ No newline at end of file diff --git a/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/config/TokenResolverConfig.java b/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/config/TokenResolverConfig.java deleted file mode 100644 index 6b961d8..0000000 --- a/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/config/TokenResolverConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2024-2024 OnixByte. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.onixbyte.simplejwt.config; - -import com.onixbyte.simplejwt.TokenResolver; -import com.onixbyte.simplejwt.constants.TokenAlgorithm; - -import java.util.List; - -/** - * The {@code TokenResolverConfig} provides a mechanism to configure an - * implementation of {@link TokenResolver} with algorithm functions. - *

    - * This generic interface is used to define the configuration details for a - * {@link TokenResolver} that utilizes algorithm functions. The interface - * allows for specifying algorithm functions corresponding to different {@link - * TokenAlgorithm} instances supported by the {@link TokenResolver} - * implementation. - * - * @param the type representing algorithm functions used by the - * {@link TokenResolver} - * @author Zihlu Wang - * @version 1.0.0 - * @since 1.0.0 - */ -public interface TokenResolverConfig { - - /** - * Gets the algorithm function corresponding to the specified {@link - * TokenAlgorithm}. - *

    - * This method returns the algorithm function associated with the given - * {@link TokenAlgorithm}. The provided TokenAlgorithm represents the - * specific algorithm for which the corresponding algorithm function is - * required. The returned {@code Algo} represents the function - * implementation that can be used by the {@link TokenResolver} to handle - * the specific algorithm. - * - * @param algorithm the {@link TokenAlgorithm} for which the algorithm function is required - * @return the algorithm function associated with the given {@link TokenAlgorithm} - */ - Algo getAlgorithm(TokenAlgorithm algorithm); - - List ECDSA_ALGORITHMS = - List.of(TokenAlgorithm.ES256, TokenAlgorithm.ES384, TokenAlgorithm.ES512); - - List HMAC_ALGORITHMS = - List.of(TokenAlgorithm.HS256, TokenAlgorithm.HS384, TokenAlgorithm.HS512); - -} diff --git a/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/config/package-info.java b/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/config/package-info.java deleted file mode 100644 index 4ddf654..0000000 --- a/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/config/package-info.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2024-2024 OnixByte. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * The classes in this package provide configuration options and settings for - * the {@code cn.org.codecrafters:simple-jwt-facade} library. They are used - * to customize the behavior of the library and allow developers to fine-tune - * various aspects of JWT generation and validation. - *

    - * Other configuration classes may be added in the future to support additional - * customisation options and features. Developers using the - * {@code cn.org.codecrafters:simple-jwt-facade} library should be familiar - * with the available configuration options to ensure proper integration and - * usage of the library. - * - * @since 1.0.0 - */ -package com.onixbyte.simplejwt.config; \ No newline at end of file diff --git a/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/constants/TokenAlgorithm.java b/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/constants/TokenAlgorithm.java index 10eec52..a0dc86b 100644 --- a/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/constants/TokenAlgorithm.java +++ b/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/constants/TokenAlgorithm.java @@ -19,6 +19,8 @@ import lombok.Getter; +import java.util.List; + /** * The {@code TokenAlgorithm} enum class defines the algorithms that can be * used for signing and verifying JSON Web Tokens (JWT). JWT allows various @@ -92,4 +94,18 @@ public enum TokenAlgorithm { ES512, ; + /** + * HMAC-based algorithms. + */ + public static final List HMAC_ALGORITHMS = List.of( + TokenAlgorithm.HS256, TokenAlgorithm.HS384, TokenAlgorithm.HS512 + ); + + /** + * ECDSA-based algorithms. + */ + public static final List ECDSA_ALGORITHMS = List.of( + TokenAlgorithm.ES256, TokenAlgorithm.ES384, TokenAlgorithm.ES512 + ); + } diff --git a/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/exceptions/IllegalKeyPairException.java b/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/exceptions/IllegalKeyPairException.java new file mode 100644 index 0000000..0b725dd --- /dev/null +++ b/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/exceptions/IllegalKeyPairException.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024-2024 OnixByte. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.simplejwt.exceptions; + +/** + * {@link IllegalKeyPairException} indicates an exception that the key pair is invalid. + * + * @author zihluwang + * @version 1.6.0 + */ +public class IllegalKeyPairException extends RuntimeException { + + /** + * Create a default exception instance. + */ + public IllegalKeyPairException() { + } + + /** + * Create an exception instance with specific message. + * + * @param message the message of the exception + */ + public IllegalKeyPairException(String message) { + super(message); + } + + /** + * Create an exception instance with specific message and cause. + * + * @param message the message of the exception + * @param cause the cause of the exception + */ + public IllegalKeyPairException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/exceptions/IllegalSecretException.java b/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/exceptions/IllegalSecretException.java new file mode 100644 index 0000000..6722ebd --- /dev/null +++ b/simple-jwt-facade/src/main/java/com/onixbyte/simplejwt/exceptions/IllegalSecretException.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024-2024 OnixByte. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.simplejwt.exceptions; + +/** + * {@link IllegalKeyPairException} indicates the secret to sign a JWT is illegal. + * + * @author zihluwang + * @version 1.6.0 + * @since 1.6.0 + */ +public class IllegalSecretException extends RuntimeException { + + /** + * Create a default exception instance. + */ + public IllegalSecretException() { + } + + /** + * Create an exception instance with specific message. + * + * @param message the message of the exception + */ + public IllegalSecretException(String message) { + super(message); + } + + /** + * Create an exception instance with specific message and the cause of this exception. + * + * @param message the message of the exception + * @param cause the cause of the exception + */ + public IllegalSecretException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/simple-jwt-jjwt/README.md b/simple-jwt-jjwt/README.md deleted file mode 100644 index 2dd64e1..0000000 --- a/simple-jwt-jjwt/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Module `simple-jwt-jjwt` - -## Introduction - -The `simple-jwt-jjwt` module is an implementation of module `simple-jwt-facade` which uses the third-party library [`io.jsonwebtoken:jjwt-api`](https://github.com/jwtk/jjwt). By using this module can help you use JWT to help you in your application. - -## Prerequisites - -This whole `JDevKit` is developed by **JDK 17**, which means you have to use JDK 17 for better experience. - -## Installation - -> This module has already imported `simple-jwt-facade`, there is no need to import it again. - -### If you are using `Maven` - -It is quite simple to install this module by `Maven`. The only thing you need to do is find your `pom.xml` file in the project, then find the `` node in the `` node, and add the following codes to `` node: - -```xml - - cn.org.codecrafters - simple-jwt-jjwt - ${simple-jwt-jjwt.version} - -``` - -And run `mvn dependency:get` in your project root folder(i.e., if your `pom.xml` is located at `/path/to/your/project/pom.xml`, then your current work folder should be `/path/to/your/project`), then `Maven` will automatically download the `jar` archive from `Maven Central Repository`. This could be **MUCH EASIER** if you are using IDE(i.e., IntelliJ IDEA), the only thing you need to do is click the refresh button of `Maven`. - -### If you are using `Gradle` - -Add this module to your project with `Gradle` is much easier than doing so with `Maven`. - -Find `build.gradle` in the needed project, and add the following code to the `dependencies` closure in the build script: - -```groovy -implementation 'cn.org.codecrafters:simple-jwt-authzero:${simple-jwt-authzero.version}' -``` - -### If you are not using `Maven` or `Gradle` - -1. Download the `jar` file from the Internet. -2. Create a folder in your project and name it as a name you like(i.e., for me, I prefer `vendor`). -3. Put the `jar` file to the folder you just created in Step 2. -4. Add this folder to your project `classpath`. - -## Use the `JjwtTokenResolver` - -We have implemented `TokenResolver` to make sure you can add JWT to your Java application as soon as possible. All you need to do is to create an instance of `jjwt.com.onixbyte.simplejwt.JjwtTokenResolver` and other operations to JWT could follow our instruction in [`simple-jwt-facade`](../simple-jwt-facade/README.md). - -## Contact - -If you have any suggestions, ideas, don't hesitate contacting us via [GitHub Issues](https://github.com/CodeCraftersCN/jdevkit/issues/new) or [Discord Community](https://discord.gg/NQK9tjcBB8). - -If you face any bugs while using our library and you are able to fix any bugs in our library, we would be happy to accept pull requests from you on [GitHub](https://github.com/CodeCraftersCN/jdevkit/compare). diff --git a/simple-jwt-jjwt/build.gradle.kts b/simple-jwt-jjwt/build.gradle.kts deleted file mode 100644 index 30d6484..0000000 --- a/simple-jwt-jjwt/build.gradle.kts +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2024-2024 OnixByte. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.net.URI - -val buildGroupId: String by project -val buildVersion: String by project -val projectUrl: String by project -val projectGithubUrl: String by project -val licenseName: String by project -val licenseUrl: String by project - -val jacksonVersion: String by project -val jjwtVersion: String by project - -group = buildGroupId -version = buildVersion - -dependencies { - implementation(project(":devkit-utils")) - implementation(project(":guid")) - implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") - implementation("io.jsonwebtoken:jjwt-api:$jjwtVersion") - implementation("io.jsonwebtoken:jjwt-impl:$jjwtVersion") - implementation("io.jsonwebtoken:jjwt-jackson:$jjwtVersion") - implementation(project(":simple-jwt-facade")) -} - -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - withSourcesJar() - withJavadocJar() -} - -tasks.test { - useJUnitPlatform() -} - -publishing { - publications { - create("simpleJwtJjwt") { - groupId = buildGroupId - artifactId = "simple-jwt-jjwt" - version = buildVersion - - pom { - name = "Simple JWT :: JJWT" - description = "SSimple JWT implemented with io.jsonwebtoken:jjwt." - url = projectUrl - - licenses { - license { - name = licenseName - url = licenseUrl - } - } - - scm { - connection = "scm:git:git://github.com:OnixByte/JDevKit.git" - developerConnection = "scm:git:git://github.com:OnixByte/JDevKit.git" - url = projectGithubUrl - } - - developers { - developer { - id = "zihluwang" - name = "Zihlu Wang" - email = "really@zihlu.wang" - timezone = "Asia/Hong_Kong" - } - } - } - - from(components["java"]) - - signing { - sign(publishing.publications["simpleJwtJjwt"]) - } - } - - repositories { - maven { - name = "sonatypeNexus" - url = URI(providers.gradleProperty("repo.maven-central.host").get()) - credentials { - username = providers.gradleProperty("repo.maven-central.username").get() - password = providers.gradleProperty("repo.maven-central.password").get() - } - } - } - } -} \ No newline at end of file diff --git a/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/JjwtTokenResolver.java b/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/JjwtTokenResolver.java deleted file mode 100644 index dc63840..0000000 --- a/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/JjwtTokenResolver.java +++ /dev/null @@ -1,474 +0,0 @@ -/* - * Copyright (C) 2024-2024 OnixByte. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.onixbyte.simplejwt.jjwt; - -import com.onixbyte.devkit.utils.MapUtil; -import com.onixbyte.guid.GuidCreator; -import com.onixbyte.simplejwt.SecretCreator; -import com.onixbyte.simplejwt.TokenPayload; -import com.onixbyte.simplejwt.TokenResolver; -import com.onixbyte.simplejwt.annotations.ExcludeFromPayload; -import com.onixbyte.simplejwt.annotations.TokenEnum; -import com.onixbyte.simplejwt.constants.PredefinedKeys; -import com.onixbyte.simplejwt.constants.TokenAlgorithm; -import com.onixbyte.simplejwt.exceptions.WeakSecretException; -import com.onixbyte.simplejwt.jjwt.config.JjwtTokenResolverConfig; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jws; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; -import io.jsonwebtoken.security.SecureDigestAlgorithm; -import lombok.extern.slf4j.Slf4j; - -import javax.crypto.SecretKey; -import java.lang.reflect.InvocationTargetException; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.*; - -/** - * The {@link JjwtTokenResolver} class is an implementation of the {@link - * TokenResolver} interface. It uses the {@code - * io.jsonwebtoken:jjwt} library to handle JSON Web Token (JWT) resolution. - * This resolver provides functionality to create, extract, verify, and renew - * JWT tokens using various algorithms and custom payload data. - *

    - * Usage: - * To use the {@code JjwtTokenResolver}, first, create an instance of this - * class: - *

    {@code
    - *   TokenResolver> tokenResolver =
    - *       new JjwtTokenResolver(TokenAlgorithm.HS256,
    - *                                 "Token Subject",
    - *                                 "Token Issuer",
    - *                                 "Token Secret");
    - *   }
    - *

    - * Then, you can utilize the various methods provided by this resolver to - * handle JWT tokens: - *

    {@code
    - *   // Creating a new JWT token
    - *   String token =
    - *       tokenResolver.createToken(Duration.ofHours(1),
    - *                                 "your_subject",
    - *                                 "your_audience",
    - *                                 customPayloads);
    - *
    - *   // Extracting payload data from a JWT token
    - *   DecodedJWT decodedJWT = tokenResolver.resolve(token);
    - *   T payloadData = decodedJWT.extract(token, T.class);
    - *
    - *   // Renewing an existing JWT token
    - *   String renewedToken =
    - *       tokenResolver.renew(token, Duration.ofMinutes(30), customPayloads);
    - *   }
    - *

    - * Note: - * It is essential to configure the appropriate algorithms, secret, and issuer - * according to your specific use case when using this resolver. - * Additionally, ensure that the {@code io.jsonwebtoken:jjwt} library is - * correctly configured in your project's dependencies. - * - * @author Zihlu Wang - * @version 1.1.0 - * @see Claims - * @see Jws - * @see Jwts - * @see SecureDigestAlgorithm - * @see Keys - * @since 1.0.0 - */ -@Slf4j -public class JjwtTokenResolver implements TokenResolver> { - - /** - * Create a resolver with specified algorithm, issuer, secret and guid strategy. - * - * @param jtiCreator jwt id creator - * @param algorithm specified algorithm - * @param issuer specified issuer - * @param secret specified secret - */ - public JjwtTokenResolver(GuidCreator jtiCreator, TokenAlgorithm algorithm, String issuer, String secret) { - if (Objects.isNull(secret) || secret.isBlank()) { - throw new IllegalArgumentException("A secret is required to build a JSON Web Token."); - } - - if (secret.length() < 32) { - throw new WeakSecretException(""" - The provided secret which owns %s characters is too weak. Please replace it with a stronger one.""" - .formatted(secret.length())); - } - - this.jtiCreator = jtiCreator; - this.algorithm = config.getAlgorithm(algorithm); - this.issuer = issuer; - this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); - } - - /** - * Create a resolver with specified algorithm, issuer, secret and default guid strategy. - * - * @param algorithm specified algorithm - * @param issuer specified issuer - * @param secret specified secret - */ - public JjwtTokenResolver(TokenAlgorithm algorithm, String issuer, String secret) { - if (secret == null || secret.isBlank()) { - throw new IllegalArgumentException("A secret is required to build a JSON Web Token."); - } - - if (secret.length() < 32) { - log.error( - "The provided secret which owns {} characters is too weak. Please replace it with a stronger one.", - secret.length()); - throw new WeakSecretException( - "The provided secret which owns %s characters is too weak. Please replace it with a stronger one." - .formatted(secret.length())); - } - - this.jtiCreator = UUID::randomUUID; - this.algorithm = config.getAlgorithm(algorithm); - this.issuer = issuer; - this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); - } - - /** - * Create a resolver with specified issuer, secret, default algorithm and guid strategy. - * - * @param issuer specified issuer - * @param secret specified secret - * @see #JjwtTokenResolver(TokenAlgorithm, String, String) - */ - public JjwtTokenResolver(String issuer, String secret) { - if (secret == null || secret.isBlank()) { - throw new IllegalArgumentException("A secret is required to build a JSON Web Token."); - } - - if (secret.length() < 32) { - throw new WeakSecretException( - "The provided secret which owns %s characters is too weak. Please replace it with a stronger one." - .formatted(secret.length())); - } - - this.jtiCreator = UUID::randomUUID; - this.algorithm = config.getAlgorithm(TokenAlgorithm.HS256); - this.issuer = issuer; - this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); - } - - /** - * Create a resolver with specified issuer, random secret string, default algorithm and guid strategy. - * - * @param issuer specified issuer - * @see #JjwtTokenResolver(String, String) - */ - public JjwtTokenResolver(String issuer) { - this.jtiCreator = UUID::randomUUID; - this.algorithm = config.getAlgorithm(TokenAlgorithm.HS256); - this.issuer = issuer; - this.key = Keys.hmacShaKeyFor(SecretCreator.createSecret(32, true, true, true).getBytes(StandardCharsets.UTF_8)); - } - - /** - * Creates a new token with the specified expiration time, subject, and - * audience. - * - * @param expireAfter the duration after which the token will expire - * @param audience the audience for which the token is intended - * @param subject the subject of the token - * @return the generated token as a {@code String} - */ - @Override - public String createToken(Duration expireAfter, String audience, String subject) { - return buildToken(expireAfter, audience, subject, null); - } - - /** - * Creates a new token with the specified expiration time, subject, - * audience, and custom payload data. - * - * @param expireAfter the duration after which the token will expire - * @param audience the audience for which the token is intended - * @param subject the subject of the token - * @param payload the custom payload data to be included in the token - * @return the generated token as a {@code String} - */ - @Override - public String createToken(Duration expireAfter, String audience, String subject, Map payload) { - return buildToken(expireAfter, audience, subject, payload); - } - - /** - * Creates a new token with the specified expiration time, subject, - * audience, and strongly-typed payload data. - * - * @param expireAfter the duration after which the token will expire - * @param audience the audience for which the token is intended - * @param subject the subject of the token - * @param payload the strongly-typed payload data to be included in the - * token - * @return the generated token as a {@code String} or {@code null} if - * creation fails - * @see MapUtil#objectToMap(Object, Map) - */ - @Override - public String createToken(Duration expireAfter, String audience, String subject, T payload) { - var fields = payload.getClass().getDeclaredFields(); - var payloadMap = new HashMap(); - - for (var field : fields) { - if (field.isAnnotationPresent(ExcludeFromPayload.class)) - continue; - - try { - var getter = payload.getClass().getDeclaredMethod("get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1)); - // Build Claims - /* - * Note (17 Oct, 2023): The jjwt can only add a map to be added. - */ - var fieldValue = getter.invoke(payload); - - // Handle enum fields. - if (field.isAnnotationPresent(TokenEnum.class)) { - var annotation = field.getAnnotation(TokenEnum.class); - var enumGetter = field.getType().getDeclaredMethod("get" + annotation.propertyName().substring(0, 1).toUpperCase() + annotation.propertyName().substring(1)); - fieldValue = enumGetter.invoke(fieldValue); - } - payloadMap.put(field.getName(), fieldValue); - } catch (IllegalAccessException | NoSuchMethodException e) { - log.error("Cannot access field {}!", field.getName()); - } catch (InvocationTargetException e) { - log.error("Cannot invoke getter.", e); - } - } - - return buildToken(expireAfter, audience, subject, payloadMap); - } - - /** - * Resolves the given token into a {@link Jws} object. - * - * @param token the token to be resolved - * @return a ResolvedTokenType object - */ - @Override - public Jws resolve(String token) { - return Jwts.parser() - .verifyWith(key) - .build() - .parseSignedClaims(token); - } - - /** - * Extracts the payload information from the given token and maps it to the - * specified target type. - * - * @param token the token from which to extract the payload - * @param targetType the target class representing the payload data type - * @return an instance of the specified target type with the extracted - * payload data, or {@code null} if extraction fails. - * @see MapUtil#mapToObject(Map, Object, Map) - */ - @Override - public T extract(String token, Class targetType) { - var resolvedToken = resolve(token); - - var claims = resolvedToken.getPayload(); - try { - var bean = targetType.getConstructor().newInstance(); - - for (var entry : claims.entrySet()) { - // Jump all JWT pre-defined properties and the fields that are annotated to be excluded. - if (PredefinedKeys.KEYS.contains(entry.getKey()) || targetType.getDeclaredField(entry.getKey()).isAnnotationPresent(ExcludeFromPayload.class)) - continue; - - var field = targetType.getDeclaredField(entry.getKey()); - var fieldValue = entry.getValue(); - if (field.isAnnotationPresent(TokenEnum.class)) { - var annotation = field.getAnnotation(TokenEnum.class); - var enumStaticLoader = field.getType().getDeclaredMethod("loadBy" + annotation.propertyName().substring(0, 1).toUpperCase() + annotation.propertyName().substring(1), annotation.dataType().getMappedClass()); - fieldValue = enumStaticLoader.invoke(null, entry.getValue()); - } - - var setter = targetType.getDeclaredMethod("set" + entry.getKey().substring(0, 1).toUpperCase() + entry.getKey().substring(1), fieldValue.getClass()); - if (setter.canAccess(bean)) { - setter.invoke(bean, fieldValue); - } else { - log.error("Setter for field {} can't be accessed.", entry.getKey()); - } - } - - return bean; - } catch (InvocationTargetException e) { - log.error("Target is not invokable.", e); - } catch (NoSuchMethodException e) { - log.error("Cannot find method according to given data.", e); - } catch (InstantiationException e) { - log.error("The required type {} is abstract or an interface.", targetType.getCanonicalName()); - } catch (IllegalAccessException e) { - log.error("An error occurs while accessing the fields of the object.", e); - } catch (NoSuchFieldException e) { - log.error("Cannot load field according to given field name.", e); - } - - return null; - } - - /** - * Re-generate a new token with the payload in the old one. - * - * @param oldToken the old token - * @param expireAfter how long the new token can be valid for - * @return re-generated token with the payload in the old one - */ - @Override - public String renew(String oldToken, Duration expireAfter) { - var resolvedToken = resolve(oldToken); - var tokenPayloads = resolvedToken.getPayload(); - - var audience = tokenPayloads.getAudience().toArray(new String[]{})[0]; - var subject = tokenPayloads.getSubject(); - - PredefinedKeys.KEYS.forEach(tokenPayloads::remove); - - return createToken(expireAfter, audience, subject, tokenPayloads); - } - - /** - * Renews the given expired token with the specified custom payload data. - * - * @param oldToken the expired token to be renewed - * @param expireAfter specify when does the new token invalid - * @param payload the custom payload data to be included in the renewed - * token - * @return the renewed token as a {@code String} - */ - @Override - public String renew(String oldToken, Duration expireAfter, Map payload) { - var resolvedTokenClaims = resolve(oldToken).getPayload(); - var audience = resolvedTokenClaims.getAudience().toArray(new String[]{})[0]; - var subject = resolvedTokenClaims.getSubject(); - - return createToken(expireAfter, audience, subject, payload); - } - - /** - * Renews the given expired token with the specified custom payload data. - * - * @param oldToken the expired token to be renewed - * @param payload the custom payload data to be included in the renewed - * token - * @return the renewed token as a {@code String} - */ - @Override - public String renew(String oldToken, Map payload) { - return renew(oldToken, Duration.ofMinutes(30), payload); - } - - /** - * Renews the given expired token with the specified strongly-typed - * payload data. - * - * @param oldToken the expired token to be renewed - * @param expireAfter specify when does the new token invalid - * @param payload the strongly-typed payload data to be included in the - * renewed token - * @return the renewed token as a {@code String} - */ - @Override - public String renew(String oldToken, Duration expireAfter, T payload) { - var resolvedTokenClaims = resolve(oldToken).getPayload(); - var audience = resolvedTokenClaims.getAudience().toArray(new String[]{})[0]; - var subject = resolvedTokenClaims.getSubject(); - - return createToken(expireAfter, audience, subject, payload); - } - - /** - * Renews the given expired token with the specified strongly-typed - * payload data. - * - * @param oldToken the expired token to be renewed - * @param payload the strongly-typed payload data to be included in the - * renewed token - * @return the renewed token as a {@code String} - */ - @Override - public String renew(String oldToken, T payload) { - return renew(oldToken, Duration.ofMinutes(30), payload); - } - - /** - * Build a new token with specified data. - * - * @param expireAfter the validity time of the token - * @param audience the audience of the token - * @param subject the subject of the token - * @param claims the data to be included in the token - * @return the built token - */ - private String buildToken(Duration expireAfter, String audience, String subject, Map claims) { - var now = LocalDateTime.now(); - var builder = Jwts.builder() - .header().add("typ", "JWT") - .and() - .issuedAt(Date.from(now.atZone(ZoneId.systemDefault()).toInstant())) - .notBefore(Date.from(now.atZone(ZoneId.systemDefault()).toInstant())) - .expiration(Date.from(now.plus(expireAfter).atZone(ZoneId.systemDefault()).toInstant())) - .subject(subject) - .issuer(this.issuer) - .audience().add(audience) - .and() - .id(jtiCreator.nextId().toString()); - - if (claims != null && !claims.isEmpty()) { - builder.claims(claims); - } - - return builder.signWith(key, algorithm) - .compact(); - } - - /** - * The ID creator for creating unique JWT IDs. - */ - private final GuidCreator jtiCreator; - - /** - * The algorithm to sign this token. - */ - private final SecureDigestAlgorithm algorithm; - - /** - * The issuer of this token. - */ - private final String issuer; - - /** - * The signature key of this token. - */ - private final SecretKey key; - - /** - * The config of this token resolver. - */ - private final JjwtTokenResolverConfig config = JjwtTokenResolverConfig.getInstance(); -} diff --git a/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/config/JjwtTokenResolverConfig.java b/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/config/JjwtTokenResolverConfig.java deleted file mode 100644 index db955be..0000000 --- a/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/config/JjwtTokenResolverConfig.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2024-2024 OnixByte. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.onixbyte.simplejwt.jjwt.config; - -import com.onixbyte.simplejwt.TokenResolver; -import com.onixbyte.simplejwt.config.TokenResolverConfig; -import com.onixbyte.simplejwt.constants.TokenAlgorithm; -import com.onixbyte.simplejwt.exceptions.UnsupportedAlgorithmException; -import com.onixbyte.simplejwt.jjwt.JjwtTokenResolver; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.SecureDigestAlgorithm; - -import javax.crypto.SecretKey; -import java.util.HashMap; -import java.util.Map; - -/** - * The {@code JjwtTokenResolverConfig} class provides the configuration for - * the {@link JjwtTokenResolver}. - *

    - * This configuration is used to establish the mapping between the standard - * {@link TokenAlgorithm} defined in the - * {@code cn.org.codecrafters:simple-jwt-facade} and the specific algorithms - * used by the {@code io.jsonwebtoken:jjwt} library, which is the underlying - * library used by {@link JjwtTokenResolver} to handle JSON Web Tokens - * (JWTs). - *

    - * Algorithm Mapping: - * The {@code JjwtTokenResolverConfig} allows specifying the relationships - * between the standard {@link TokenAlgorithm} instances supported by - * {@link JjwtTokenResolver} and the corresponding algorithms used by the - * {@code io.jsonwebtoken:jjwt} library. The mapping is achieved using a Map, - * where the keys are the standard {@link TokenAlgorithm} instances, and the - * values represent the algorithm functions used by - * {@code io.jsonwebtoken:jjwt} library for each corresponding key. - *

    - * Note: - * The provided algorithm mapping should be consistent with the actual - * algorithms supported and used by the {@code io.jsonwebtoken:jjwt} library. - * It is crucial to ensure that the mapping is accurate to enable proper token - * validation and processing within the {@link JjwtTokenResolver}. - * - * @author Zihlu Wang - * @version 1.1.1 - * @since 1.0.0 - */ -public final class JjwtTokenResolverConfig implements TokenResolverConfig> { - - /** - * Get the instance for io.jsonwebtoken:jjwt config. - * @return the instance for io.jsonwebtoken:jjwt config - */ - public static JjwtTokenResolverConfig getInstance() { - if (instance == null) { - instance = new JjwtTokenResolverConfig(); - } - - return instance; - } - - /** - * Gets the algorithm function corresponding to the specified - * {@link TokenAlgorithm}. - *

    - * This method returns the algorithm function associated with the given - * {@link TokenAlgorithm}. The provided {@link TokenAlgorithm} represents - * the specific algorithm for which the corresponding algorithm function is - * required. The returned algorithm function represents the function - * implementation that can be used by the {@link TokenResolver} to handle - * the specific algorithm. - * - * @param algorithm the {@link TokenAlgorithm} for which the algorithm - * function is required - * @return the algorithm function associated with the given {@link - * TokenAlgorithm} - */ - @Override - public SecureDigestAlgorithm getAlgorithm(TokenAlgorithm algorithm) { - if (!SUPPORTED_ALGORITHMS.containsKey(algorithm)) { - throw new UnsupportedAlgorithmException(""" - The request algorithm is not supported by our system yet. Please change to supported ones."""); - } - return SUPPORTED_ALGORITHMS.get(algorithm); - } - - /** - * Private constructor will protect this class from being instantiated. - */ - private JjwtTokenResolverConfig() { - } - - /** - * A {@code Map} to map a {@link TokenAlgorithm} to {@link SecureDigestAlgorithm}. - */ - private static final Map> SUPPORTED_ALGORITHMS = new HashMap<>() {{ - put(TokenAlgorithm.HS256, Jwts.SIG.HS256); - put(TokenAlgorithm.HS384, Jwts.SIG.HS384); - put(TokenAlgorithm.HS512, Jwts.SIG.HS512); - }}; - - /** - * The instance of this config class. - */ - private static JjwtTokenResolverConfig instance; - -} diff --git a/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/config/package-info.java b/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/config/package-info.java deleted file mode 100644 index e94187a..0000000 --- a/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/config/package-info.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2024-2024 OnixByte. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * The package {@code cn.org.codecrafters.simplejwt.jjwt.config} contains - * configuration classes related to the {@link - * com.onixbyte.simplejwt.jjwt.JjwtTokenResolver} - * implementation. - *

    - * The classes in this package provide configuration options and settings for - * the {@link com.onixbyte.simplejwt.jjwt.JjwtTokenResolver}, - * which is used for resolving JSON Web Tokens (JWT) using the Auth0 library. - *

    - * The {@link - * com.onixbyte.simplejwt.jjwt.config.JjwtTokenResolverConfig} - * class is a configuration class that defines the mapping between standard - * {@link com.onixbyte.simplejwt.constants.TokenAlgorithm} and the - * corresponding function implementation used by {@link - * com.onixbyte.simplejwt.jjwt.JjwtTokenResolver} for handling - * JWT algorithms. It enables developers to specify and customize the - * algorithm functions according to the chosen JWT algorithm and the library - * being used. - *

    - * The configuration options in this package help developers integrate and - * configure the {@link - * com.onixbyte.simplejwt.jjwt.JjwtTokenResolver} seamlessly - * into their Spring Boot applications. Developers can fine-tune the token - * resolution process and customize algorithm handling to align with their - * specific requirements and desired level of security. - *

    - * It is recommended to explore the classes in this package to understand how - * to configure and use the {@link - * com.onixbyte.simplejwt.jjwt.JjwtTokenResolver} effectively - * in the Spring Boot environment to handle JWT authentication and - * authorisation securely and efficiently. - * - * @since 1.0.0 - */ -package com.onixbyte.simplejwt.jjwt.config; \ No newline at end of file diff --git a/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/package-info.java b/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/package-info.java deleted file mode 100644 index 23be51d..0000000 --- a/simple-jwt-jjwt/src/main/java/com/onixbyte/simplejwt/jjwt/package-info.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2024-2024 OnixByte. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This package contains classes related to the integration of the {@code - * io.jsonwebtoken:jjwt-api} library in the Simple JWT project. {@code - * io.jsonwebtoken:jjwt-api} is a powerful and widely-used Identity as a Service - * (IDaaS) platform that provides secure authentication and authorisation - * solutions for web and mobile applications. The classes in this package - * provide the necessary functionality to handle JSON Web Tokens (JWTs) using - * the {@code io.jsonwebtoken:jjwt-api} library. - *

    - * The main class in this package is the {@link - * com.onixbyte.simplejwt.jjwt.JjwtTokenResolver}, which - * implements the {@link com.onixbyte.simplejwt.TokenResolver} interface - * and uses the {@code io.jsonwebtoken:jjwt-api} library to handle JWT - * operations. It provides the functionality to create, validate, and extract - * JWTs using the {@code io.jsonwebtoken:jjwt-api} library. Developers can use - * this class as the main token resolver in the Simple JWT project when - * integrating {@code io.jsonwebtoken:jjwt-api} as the JWT management library. - *

    - * The {@link com.onixbyte.simplejwt.jjwt.JjwtTokenResolver} relies on - * the {@code io.jsonwebtoken:jjwt-api} - * library to handle the underlying JWT operations, including token creation, - * validation, and extraction. It utilizes the {@code io.jsonwebtoken:jjwt-api} - * {@link io.jsonwebtoken.SignatureAlgorithm} class to define and use different - * algorithms for JWT signing and verification. - *

    - * To use the {@link com.onixbyte.simplejwt.jjwt.JjwtTokenResolver}, - * developers must provide the necessary configurations and dependencies, such - * as the {@link com.onixbyte.guid.GuidCreator} for generating unique - * JWT IDs (JTI), the supported algorithm function, the issuer name, and the - * secret key used for token signing and validation. The - * {@link com.onixbyte.simplejwt.jjwt.config.JjwtTokenResolverConfig} - * class provides a convenient way to configure these dependencies. - *

    - * Developers using the {@code io.jsonwebtoken:jjwt-api} integration should be - * familiar with the concepts and usage of the {@code io.jsonwebtoken:jjwt-api} - * library and follow the official {@code io.jsonwebtoken:jjwt-api} - * documentation for best practices and security considerations. - * - * @since 1.0.0 - */ -package com.onixbyte.simplejwt.jjwt; \ No newline at end of file diff --git a/simple-jwt-jjwt/src/main/resources/logback.xml b/simple-jwt-jjwt/src/main/resources/logback.xml deleted file mode 100644 index f229a7e..0000000 --- a/simple-jwt-jjwt/src/main/resources/logback.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - ${COLOURFUL_OUTPUT} - - - - - - \ No newline at end of file diff --git a/simple-jwt-spring-boot-starter/build.gradle.kts b/simple-jwt-spring-boot-starter/build.gradle.kts index b267e79..3464e97 100644 --- a/simple-jwt-spring-boot-starter/build.gradle.kts +++ b/simple-jwt-spring-boot-starter/build.gradle.kts @@ -25,7 +25,7 @@ val licenseName: String by project val licenseUrl: String by project val javaJwtVersion: String by project -val jjwtVersion: String by project +val jacksonVersion: String by project val springBootVersion: String by project group = buildGroupId @@ -36,10 +36,7 @@ dependencies { implementation(project(":simple-jwt-facade")) compileOnly("com.auth0:java-jwt:$javaJwtVersion") compileOnly(project(":simple-jwt-authzero")) - compileOnly("io.jsonwebtoken:jjwt-api:$jjwtVersion") - compileOnly("io.jsonwebtoken:jjwt-impl:$jjwtVersion") - compileOnly("io.jsonwebtoken:jjwt-jackson:$jjwtVersion") - compileOnly(project(":simple-jwt-jjwt")) + implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") implementation("org.springframework.boot:spring-boot-autoconfigure:$springBootVersion") implementation("org.springframework.boot:spring-boot-starter-logging:$springBootVersion") implementation("org.springframework.boot:spring-boot-configuration-processor:$springBootVersion") diff --git a/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java b/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java index 4c93400..e3b26ec 100644 --- a/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java +++ b/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java @@ -23,7 +23,7 @@ import com.onixbyte.simplejwt.autoconfiguration.properties.SimpleJwtProperties; import com.auth0.jwt.interfaces.DecodedJWT; import com.fasterxml.jackson.databind.ObjectMapper; -import com.onixbyte.simplejwt.config.TokenResolverConfig; +import com.onixbyte.simplejwt.constants.TokenAlgorithm; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -91,25 +91,21 @@ public AuthzeroTokenResolverAutoConfiguration(SimpleJwtProperties simpleJwtPrope */ @Bean public TokenResolver tokenResolver() { - if (TokenResolverConfig.HMAC_ALGORITHMS.contains(simpleJwtProperties.algorithm())) { - return new AuthzeroTokenResolver( - jtiCreator, - simpleJwtProperties.algorithm(), - simpleJwtProperties.issuer(), - simpleJwtProperties.secret(), - "", - objectMapper - ); - } else { - return new AuthzeroTokenResolver( - jtiCreator, - simpleJwtProperties.algorithm(), - simpleJwtProperties.issuer(), - simpleJwtProperties.getPrivateKey(), - simpleJwtProperties.getPublicKey(), - objectMapper - ); + var builder = AuthzeroTokenResolver.builder(); + + if (TokenAlgorithm.HMAC_ALGORITHMS.contains(simpleJwtProperties.getAlgorithm())) { + builder.keyPair(simpleJwtProperties.getPublicKey(), simpleJwtProperties.getPrivateKey()) + .algorithm(simpleJwtProperties.getAlgorithm()); + } else if (TokenAlgorithm.ECDSA_ALGORITHMS.contains(simpleJwtProperties.getAlgorithm())) { + builder.secret(simpleJwtProperties.getSecret()) + .algorithm(simpleJwtProperties.getAlgorithm()); } + + builder.issuer(simpleJwtProperties.getIssuer()); + builder.jtiCreator(jtiCreator); + builder.objectMapper(objectMapper); + + return builder.build(); } private final GuidCreator jtiCreator; diff --git a/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/JjwtTokenResolverAutoConfiguration.java b/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/JjwtTokenResolverAutoConfiguration.java deleted file mode 100644 index 37f2d54..0000000 --- a/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/JjwtTokenResolverAutoConfiguration.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2024-2024 OnixByte. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.onixbyte.simplejwt.autoconfiguration; - -import com.onixbyte.guid.GuidCreator; -import com.onixbyte.simplejwt.TokenResolver; -import com.onixbyte.simplejwt.autoconfiguration.properties.SimpleJwtProperties; -import com.onixbyte.simplejwt.jjwt.JjwtTokenResolver; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jws; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -/** - * {@code JjwtTokenResolverAutoConfiguration} is responsible for automatically - * configuring the Simple JWT library with {@code io.jsonwebtoken:jjwt-api} - * when used in a Spring Boot application. It provides default settings and - * configurations to ensure that the library works smoothly without requiring - * manual configuration. - *

    - * This autoconfiguration class sets up the necessary beans and components - * required for JWT generation and validation. It automatically creates and - * configures the {@link JjwtTokenResolver} bean based on the available options - * and properties. - *

    - * Developers using the Simple JWT library with Spring Boot do not need to - * explicitly configure the library, as the autoconfiguration takes care of - * setting up the necessary components and configurations automatically. - * However, developers still have the flexibility to customize the behavior of - * the library by providing their own configurations and properties. - * - * @author Zihlu Wang - * @version 1.1.0 - * @since 1.0.0 - */ -@Slf4j -@AutoConfiguration -@EnableConfigurationProperties(value = {SimpleJwtProperties.class}) -@ConditionalOnClass({Jws.class, Claims.class, JjwtTokenResolver.class}) -@ConditionalOnMissingBean({TokenResolver.class}) -@ConditionalOnBean(value = {GuidCreator.class}, name = "jtiCreator") -@AutoConfigureAfter(value = GuidAutoConfiguration.class) -public class JjwtTokenResolverAutoConfiguration { - - /** - * Constructs a new {@code SimpleJwtAutoConfiguration} instance with the provided - * {@link SimpleJwtProperties}. - * - * @param jtiCreator a creator to create JSON Web Token ids - * @param simpleJwtProperties the SimpleJwtProperties instance - */ - @Autowired - public JjwtTokenResolverAutoConfiguration(SimpleJwtProperties simpleJwtProperties, @Qualifier("jtiCreator") GuidCreator jtiCreator) { - this.jtiCreator = jtiCreator; - this.simpleJwtProperties = simpleJwtProperties; - } - - /** - * Creates a new {@link TokenResolver} bean using {@link JjwtTokenResolver} if no existing - * {@link TokenResolver} bean is found. The {@link JjwtTokenResolver} is configured with the - * provided {@link GuidCreator}, {@code algorithm}, {@code issuer}, and {@code secret} - * properties from {@link SimpleJwtProperties}. - * - * @return the {@link TokenResolver} instance - */ - @Bean - public TokenResolver> tokenResolver() { - return new JjwtTokenResolver( - jtiCreator, - simpleJwtProperties.algorithm(), - simpleJwtProperties.issuer(), - simpleJwtProperties.secret() - ); - } - - /** - * The GuidCreator instance to be used for generating JWT IDs (JTI). - */ - private final GuidCreator jtiCreator; - - /** - * The {@code SimpleJwtProperties} instance containing the configuration properties - * for Simple JWT. - */ - private final SimpleJwtProperties simpleJwtProperties; - -} diff --git a/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/properties/SimpleJwtProperties.java b/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/properties/SimpleJwtProperties.java index cf1b90d..fb92243 100644 --- a/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/properties/SimpleJwtProperties.java +++ b/simple-jwt-spring-boot-starter/src/main/java/com/onixbyte/simplejwt/autoconfiguration/properties/SimpleJwtProperties.java @@ -20,7 +20,6 @@ import com.onixbyte.simplejwt.SecretCreator; import com.onixbyte.simplejwt.autoconfiguration.AuthzeroTokenResolverAutoConfiguration; import com.onixbyte.simplejwt.constants.TokenAlgorithm; -import com.onixbyte.simplejwt.jjwt.JjwtTokenResolver; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -31,9 +30,8 @@ * "onixbyte.simple-jwt". *

    * {@code SimpleJwtProperties} provides configuration options for the JWT algorithm, issuer, - * and secret. The properties are used by the {@link AuthzeroTokenResolverAutoConfiguration} and - * {@link JjwtTokenResolver} to set up the necessary configurations for JWT generation - * and validation. + * and secret. The properties are used by the {@link AuthzeroTokenResolverAutoConfiguration} to + * set up the necessary configurations for JWT generation and validation. *

    * Developers can customise the JWT algorithm, issuer, and secret by setting the corresponding * properties in the application's properties file. The {@code SimpleJwtAutoConfiguration} class @@ -72,38 +70,14 @@ public SimpleJwtProperties() { private String secret = SecretCreator.createSecret(32, true, true, true); /** - * The private key of + * The private key, PEM formatted. */ private String privateKey; - private String publicKey; - - /** - * Returns the JWT algorithm configured in the properties. - * - * @return the JWT algorithm - */ - public final TokenAlgorithm algorithm() { - return algorithm; - } - /** - * Returns the issuer value configured in the properties. - * - * @return the issuer value + * The public key, PEM formatted */ - public final String issuer() { - return issuer; - } - - /** - * Returns the secret key configured in the properties. - * - * @return the secret key - */ - public final String secret() { - return secret; - } + private String publicKey; }