refactor: moved MapUtil implemented by reflect API to another package

This commit is contained in:
zihluwang
2024-08-05 19:04:51 +08:00
parent 7e4fdd5404
commit 62b8cb8118
16 changed files with 204 additions and 130 deletions
@@ -34,8 +34,7 @@ import java.util.Objects;
import java.util.UUID; import java.util.UUID;
/** /**
* {@link AesUtil} can help you encrypt and decrypt data with specified secret * {@link AesUtil} can help you encrypt and decrypt data with specified secret by AES algorithm.
* by AES algorithm.
* *
* @author hubin@baomidou * @author hubin@baomidou
* @version 1.1.0 * @version 1.1.0
@@ -81,8 +80,9 @@ public final class AesUtil {
var cipher = Cipher.getInstance(AES_CBC_CIPHER); var cipher = Cipher.getInstance(AES_CBC_CIPHER);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(secret)); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(secret));
return cipher.doFinal(data); return cipher.doFinal(data);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedOperationException | } catch (NoSuchAlgorithmException | NoSuchPaddingException |
InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | UnsupportedOperationException | InvalidKeyException |
InvalidAlgorithmParameterException | IllegalBlockSizeException |
BadPaddingException exception) { BadPaddingException exception) {
log.error(exception.getMessage()); log.error(exception.getMessage());
for (var stackTraceElement : exception.getStackTrace()) { for (var stackTraceElement : exception.getStackTrace()) {
@@ -100,7 +100,8 @@ public final class AesUtil {
* @return the encryption result or {@code null} if encryption failed * @return the encryption result or {@code null} if encryption failed
*/ */
public static String encrypt(String data, String secret) { 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)));
} }
/** /**
@@ -49,7 +49,7 @@ import java.util.Objects;
* provided. It is recommended to specify the charset explicitly to ensure consistent * provided. It is recommended to specify the charset explicitly to ensure consistent
* encoding and decoding. * encoding and decoding.
* *
* @author Zihlu Wang * @author zihluwang
* @version 1.1.0 * @version 1.1.0
* @since 1.0.0 * @since 1.0.0
*/ */
@@ -293,7 +293,9 @@ public final class ChainedCalcUtil {
* or null if not applicable * or null if not applicable
* @return a ChainedCalcUtil instance with the updated value * @return a ChainedCalcUtil instance with the updated value
*/ */
private ChainedCalcUtil operator(BiFunction<BigDecimal, BigDecimal, BigDecimal> operator, Object other, Integer beforeOperateScale) { private ChainedCalcUtil operator(BiFunction<BigDecimal, BigDecimal, BigDecimal> operator,
Object other,
Integer beforeOperateScale) {
return baseOperator((otherValue) -> return baseOperator((otherValue) ->
operator.apply(this.value, otherValue), operator.apply(this.value, otherValue),
other, other,
@@ -311,7 +313,8 @@ public final class ChainedCalcUtil {
* @return a ChainedCalcUtil instance with the updated value * @return a ChainedCalcUtil instance with the updated value
*/ */
private synchronized ChainedCalcUtil baseOperator(Function<BigDecimal, BigDecimal> operatorFunction, private synchronized ChainedCalcUtil baseOperator(Function<BigDecimal, BigDecimal> operatorFunction,
Object anotherValue, Integer beforeOperateScale) { Object anotherValue,
Integer beforeOperateScale) {
if (Objects.isNull(anotherValue)) { if (Objects.isNull(anotherValue)) {
return this; return this;
} }
@@ -61,7 +61,7 @@ import java.util.Optional;
* for data integrity checks and password storage, but they should not be used for * for data integrity checks and password storage, but they should not be used for
* encryption purposes. * encryption purposes.
* *
* @author Zihlu Wang * @author zihluwang
* @version 1.1.0 * @version 1.1.0
* @see java.security.MessageDigest * @see java.security.MessageDigest
* @since 1.0.0 * @since 1.0.0
@@ -30,9 +30,8 @@ import java.util.Optional;
* Note: Since version 1.4.2, this util class removed reflection API and transferred to a safer API. * Note: Since version 1.4.2, this util class removed reflection API and transferred to a safer API.
* Please see documentation for more information. * Please see documentation for more information.
* *
* @author Zihlu Wang * @author zihluwang
* @version 1.4.2 * @version 1.4.2
* @see com.onixbyte.devkit.utils.unsafe.ReflectMapUtil
* @since 1.0.0 * @since 1.0.0
*/ */
@Slf4j @Slf4j
@@ -50,10 +50,10 @@ public class KeyLoader {
* *
* @param pemKeyText pem-formatted key text * @param pemKeyText pem-formatted key text
* @return loaded private key * @return loaded private key
* @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, or EC Key Factory is * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance,
* not loaded, or key spec is invalid * or EC Key Factory is not loaded, or key spec is invalid
*/ */
public ECPrivateKey loadEcdsaPrivateKey(String pemKeyText) { public static ECPrivateKey loadEcdsaPrivateKey(String pemKeyText) {
try { try {
var decodedKeyString = Base64.getDecoder().decode(pemKeyText); var decodedKeyString = Base64.getDecoder().decode(pemKeyText);
var keySpec = new PKCS8EncodedKeySpec(decodedKeyString); var keySpec = new PKCS8EncodedKeySpec(decodedKeyString);
@@ -76,10 +76,10 @@ public class KeyLoader {
* *
* @param pemKeyText pem-formatted key text * @param pemKeyText pem-formatted key text
* @return loaded private key * @return loaded private key
* @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance, or EC Key Factory is * @throws KeyLoadingException if the generated key is not a {@link ECPrivateKey} instance,
* not loaded, or key spec is invalid * or EC Key Factory is not loaded, or key spec is invalid
*/ */
public ECPublicKey loadEcdsaPublicKey(String pemKeyText) { public static ECPublicKey loadEcdsaPublicKey(String pemKeyText) {
try { try {
var keyBytes = Base64.getDecoder().decode(pemKeyText); var keyBytes = Base64.getDecoder().decode(pemKeyText);
var spec = new X509EncodedKeySpec(keyBytes); var spec = new X509EncodedKeySpec(keyBytes);
@@ -17,24 +17,63 @@
package com.onixbyte.security.exception; 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 { public class KeyLoadingException extends RuntimeException {
/**
* Creates a new instance of {@code KeyLoadingException} without a specific message or cause.
*/
public KeyLoadingException() { public KeyLoadingException() {
} }
/**
* Creates a new instance of {@code KeyLoadingException} with the specified detail message.
*
* @param message the detail message
*/
public KeyLoadingException(String message) { public KeyLoadingException(String message) {
super(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) { public KeyLoadingException(String message, Throwable cause) {
super(message, 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) { public KeyLoadingException(Throwable cause) {
super(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); super(message, cause, enableSuppression, writableStackTrace);
} }
} }
@@ -53,7 +53,7 @@ public final class ReflectMapUtil {
var declaredFields = obj.getClass().getDeclaredFields(); var declaredFields = obj.getClass().getDeclaredFields();
for (var field : declaredFields) { for (var field : declaredFields) {
field.setAccessible(true); field.setAccessible(true);
Object result = field.get(obj); var result = field.get(obj);
if (result != null) { if (result != null) {
map.put(field.getName(), result); map.put(field.getName(), result);
} }
@@ -157,7 +157,8 @@ public final class ReflectMapUtil {
* @throws IllegalAccessException if an error occurs while accessing the field * @throws IllegalAccessException if an error occurs while accessing the field
* @throws NoSuchMethodException if the specific setter is not present * @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 objectClass = obj.getClass();
var methodName = getMethodName("set", fieldName); var methodName = getMethodName("set", fieldName);
var method = objectClass.getDeclaredMethod(methodName, fieldValue.getClass()); var method = objectClass.getDeclaredMethod(methodName, fieldValue.getClass());
+1
View File
@@ -33,6 +33,7 @@ version = buildVersion
dependencies { dependencies {
implementation(project(":devkit-utils")) implementation(project(":devkit-utils"))
implementation(project(":guid")) implementation(project(":guid"))
implementation(project(":key-pair-loader"))
implementation(project(":simple-jwt-facade")) implementation(project(":simple-jwt-facade"))
implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")
implementation("com.auth0:java-jwt:$javaJwtVersion") implementation("com.auth0:java-jwt:$javaJwtVersion")
@@ -25,6 +25,7 @@ import com.onixbyte.simplejwt.TokenResolver;
import com.onixbyte.simplejwt.annotations.ExcludeFromPayload; import com.onixbyte.simplejwt.annotations.ExcludeFromPayload;
import com.onixbyte.simplejwt.annotations.TokenEnum; import com.onixbyte.simplejwt.annotations.TokenEnum;
import com.onixbyte.simplejwt.authzero.config.AuthzeroTokenResolverConfig; import com.onixbyte.simplejwt.authzero.config.AuthzeroTokenResolverConfig;
import com.onixbyte.simplejwt.config.TokenResolverConfig;
import com.onixbyte.simplejwt.constants.PredefinedKeys; import com.onixbyte.simplejwt.constants.PredefinedKeys;
import com.onixbyte.simplejwt.constants.TokenAlgorithm; import com.onixbyte.simplejwt.constants.TokenAlgorithm;
import com.auth0.jwt.JWT; import com.auth0.jwt.JWT;
@@ -45,15 +46,13 @@ import java.time.ZoneId;
import java.util.*; import java.util.*;
/** /**
* The {@code AuthzeroTokenResolver} class is an implementation of the {@link * The {@code AuthzeroTokenResolver} class is an implementation of the {@link TokenResolver}
* TokenResolver} interface. It uses the {@code * interface. It uses the {@code com.auth0:java-jwt} library to handle JSON Web Token (JWT)
* com.auth0:java-jwt} library to handle JSON Web Token (JWT) resolution. This * resolution. This resolver provides functionality to create, extract, verify, and renew JWT
* resolver provides functionality to create, extract, verify, and renew JWT
* tokens using various algorithms and custom payload data. * tokens using various algorithms and custom payload data.
* <p> * <p>
* <b>Usage:</b> * <b>Usage:</b>
* To use the {@code AuthzeroTokenResolver}, first, create an instance of this * To use the {@code AuthzeroTokenResolver}, first, create an instance of this class:
* class:
* <pre>{@code * <pre>{@code
* TokenResolver<DecodedJWT> tokenResolver = * TokenResolver<DecodedJWT> tokenResolver =
* new AuthzeroTokenResolver(TokenAlgorithm.HS256, * new AuthzeroTokenResolver(TokenAlgorithm.HS256,
@@ -62,8 +61,7 @@ import java.util.*;
* "Token Secret"); * "Token Secret");
* }</pre> * }</pre>
* <p> * <p>
* Then, you can utilize the various methods provided by this resolver to * Then, you can utilize the various methods provided by this resolver to handle JWT tokens:
* handle JWT tokens:
* <pre>{@code * <pre>{@code
* // Creating a new JWT token * // Creating a new JWT token
* String token = * String token =
@@ -82,10 +80,9 @@ import java.util.*;
* }</pre> * }</pre>
* <p> * <p>
* <b>Note:</b> * <b>Note:</b>
* It is essential to configure the appropriate algorithms, secret, and issuer * It is essential to configure the appropriate algorithms, secret, and issuer according to your
* according to your specific use case when using this resolver. * specific use case when using this resolver. Additionally, ensure that the
* Additionally, ensure that the {@code com.auth0:java-jwt} library is * {@code com.auth0:java-jwt} library is correctly configured in your project's dependencies.
* correctly configured in your project's dependencies.
* *
* @author Zihlu Wang * @author Zihlu Wang
* @version 1.1.1 * @version 1.1.1
@@ -100,61 +97,77 @@ import java.util.*;
public class AuthzeroTokenResolver implements TokenResolver<DecodedJWT> { public class AuthzeroTokenResolver implements TokenResolver<DecodedJWT> {
/** /**
* Creates a new instance of {@code AuthzeroTokenResolver} with the * Creates a new instance of {@code AuthzeroTokenResolver} with the provided configurations.
* provided configurations.
* *
* @param jtiCreator the {@link GuidCreator} used for generating unique * @param jtiCreator the {@link GuidCreator} used for generating unique identifiers for "jti"
* identifiers for "jti" claim in JWT tokens * claim in JWT tokens
* @param algorithm the algorithm used for signing and verifying JWT * @param algorithm the algorithm used for signing and verifying JWT tokens
* tokens
* @param issuer the issuer claim value to be included in JWT tokens * @param issuer the issuer claim value to be included in JWT tokens
* @param secret the secret used for HMAC-based algorithms (HS256, * @param privateKey the secret used for HMAC-based algorithms (HS256, HS384, HS512) for
* HS384, HS512) for token signing and verification * 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 * @param objectMapper JSON handler
*/ */
public AuthzeroTokenResolver(GuidCreator<?> jtiCreator, TokenAlgorithm algorithm, String issuer, String secret, ObjectMapper objectMapper) { public AuthzeroTokenResolver(GuidCreator<?> jtiCreator,
if (secret == null || secret.isBlank()) { 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."); throw new IllegalArgumentException("A secret is required to build a JSON Web Token.");
} }
if (secret.length() < 32) { if (privateKey.length() < 32) {
log.warn("The provided secret which owns {} characters is too weak. Please consider replacing it with a stronger one.", secret.length()); 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.jtiCreator = jtiCreator;
this.algorithm = config this.algorithm = config
.getAlgorithm(algorithm) .getAlgorithm(algorithm)
.apply(secret); .apply(privateKey, publicKey);
this.issuer = issuer; this.issuer = issuer;
this.verifier = JWT.require(this.algorithm).build(); this.verifier = JWT.require(this.algorithm).build();
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
} }
/** /**
* Creates a new instance of {@link AuthzeroTokenResolver} with the * Creates a new instance of {@link AuthzeroTokenResolver} with the provided configurations
* provided configurations and a simple UUID GuidCreator. * and a simple UUID GuidCreator.
* *
* @param algorithm the algorithm used for signing and verifying 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 issuer the issuer claim value to be included in JWT tokens
* @param secret the secret used for HMAC-based algorithms (HS256, * @param privateKey the secret used for HMAC-based algorithms (HS256, HS384, HS512) for
* HS384, HS512) for token signing and verification * 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 * @param objectMapper Jackson Databind JSON Handler
*/ */
public AuthzeroTokenResolver(TokenAlgorithm algorithm, String issuer, String secret, ObjectMapper objectMapper) { public AuthzeroTokenResolver(TokenAlgorithm algorithm,
this(UUID::randomUUID, algorithm, issuer, secret, objectMapper); 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 * Creates a new instance of {@link AuthzeroTokenResolver} with the provided configurations
* provided configurations and a simple UUID GuidCreator. * and a simple UUID GuidCreator.
* *
* @param algorithm the algorithm used for signing and verifying 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 issuer the issuer claim value to be included in JWT tokens
* @param secret the secret used for HMAC-based algorithms (HS256, * @param privateKey the secret used for HMAC-based algorithms (HS256, HS384, HS512) for
* HS384, HS512) for token signing and verification * 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) { public AuthzeroTokenResolver(TokenAlgorithm algorithm, String issuer, String privateKey, String publicKey) {
this(UUID::randomUUID, algorithm, issuer, secret, new ObjectMapper()); this(UUID::randomUUID, algorithm, issuer, privateKey, publicKey, new ObjectMapper());
} }
/** /**
@@ -163,11 +176,10 @@ public class AuthzeroTokenResolver implements TokenResolver<DecodedJWT> {
* UUID GuidCreator. * UUID GuidCreator.
* *
* @param issuer the issuer claim value to be included in JWT tokens * @param issuer the issuer claim value to be included in JWT tokens
* @param secret the secret used for HMAC-based algorithms (HS256, * @param secret the secret used for HS256 algorithms for token signing and verification
* HS384, HS512) for token signing and verification
*/ */
public AuthzeroTokenResolver(String issuer, String secret) { 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 class AuthzeroTokenResolver implements TokenResolver<DecodedJWT> {
this.jtiCreator = UUID::randomUUID; this.jtiCreator = UUID::randomUUID;
this.algorithm = config this.algorithm = config
.getAlgorithm(TokenAlgorithm.HS256) .getAlgorithm(TokenAlgorithm.HS256)
.apply(secret); .apply(secret, "");
this.issuer = issuer; this.issuer = issuer;
this.verifier = JWT.require(this.algorithm).build(); this.verifier = JWT.require(this.algorithm).build();
this.objectMapper = new ObjectMapper(); this.objectMapper = new ObjectMapper();
@@ -17,6 +17,7 @@
package com.onixbyte.simplejwt.authzero.config; package com.onixbyte.simplejwt.authzero.config;
import com.onixbyte.security.KeyLoader;
import com.onixbyte.simplejwt.TokenResolver; import com.onixbyte.simplejwt.TokenResolver;
import com.onixbyte.simplejwt.authzero.AuthzeroTokenResolver; import com.onixbyte.simplejwt.authzero.AuthzeroTokenResolver;
import com.onixbyte.simplejwt.config.TokenResolverConfig; import com.onixbyte.simplejwt.config.TokenResolverConfig;
@@ -30,6 +31,7 @@ import java.security.interfaces.ECPrivateKey;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*; import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
/** /**
@@ -58,7 +60,7 @@ import java.util.function.Function;
* @since 1.0.0 * @since 1.0.0
*/ */
public final class AuthzeroTokenResolverConfig public final class AuthzeroTokenResolverConfig
implements TokenResolverConfig<Function<String, Algorithm>> { implements TokenResolverConfig<BiFunction<String, String, Algorithm>> {
/** /**
* Gets the instance of {@code AuthzeroTokenResolverConfig}. * Gets the instance of {@code AuthzeroTokenResolverConfig}.
@@ -92,7 +94,7 @@ public final class AuthzeroTokenResolverConfig
* this implementation * this implementation
*/ */
@Override @Override
public Function<String, Algorithm> getAlgorithm(TokenAlgorithm algorithm) { public BiFunction<String, String, Algorithm> getAlgorithm(TokenAlgorithm algorithm) {
return Optional.of(SUPPORTED_ALGORITHMS).map((entry) -> entry.get(algorithm)) return Optional.of(SUPPORTED_ALGORITHMS).map((entry) -> entry.get(algorithm))
.orElseThrow(() -> new UnsupportedAlgorithmException("The specified algorithm is not supported yet.")); .orElseThrow(() -> new UnsupportedAlgorithmException("The specified algorithm is not supported yet."));
} }
@@ -127,26 +129,20 @@ public final class AuthzeroTokenResolverConfig
* specific algorithms. The mapping is used to provide proper algorithm * specific algorithms. The mapping is used to provide proper algorithm
* resolution and processing within the {@link AuthzeroTokenResolver}. * resolution and processing within the {@link AuthzeroTokenResolver}.
*/ */
private static final Map<TokenAlgorithm, Function<String, Algorithm>> SUPPORTED_ALGORITHMS = new HashMap<>() {{ private static final
put(TokenAlgorithm.HS256, Algorithm::HMAC256); Map<TokenAlgorithm, BiFunction<String, String, Algorithm>> SUPPORTED_ALGORITHMS =
put(TokenAlgorithm.HS384, Algorithm::HMAC384); new HashMap<>() {{
put(TokenAlgorithm.HS512, Algorithm::HMAC512); put(TokenAlgorithm.HS256, (String secret, String ignoredValue) ->
put(TokenAlgorithm.ES256, (String privateKey) -> { Algorithm.HMAC256(secret));
try { put(TokenAlgorithm.HS384, (String secret, String ignoredValue) ->
var keyBytes = Base64.getDecoder().decode(privateKey); Algorithm.HMAC384(secret));
var spec = new PKCS8EncodedKeySpec(keyBytes); put(TokenAlgorithm.HS512, (String secret, String ignoredValue) ->
var kf = KeyFactory.getInstance("EC"); Algorithm.HMAC512(secret));
var key = kf.generatePrivate(spec); put(TokenAlgorithm.ES256, (String privateKey, String publicKey) ->
if (key instanceof ECPrivateKey pk) { Algorithm.ECDSA256(KeyLoader.loadEcdsaPrivateKey(privateKey)));
return Algorithm.ECDSA256(pk); put(TokenAlgorithm.ES384, (String privateKey, String publicKey) ->
} else { Algorithm.ECDSA256(KeyLoader.loadEcdsaPrivateKey(privateKey)));
throw new RuntimeException("Type error!"); put(TokenAlgorithm.ES512, (String privateKey, String publicKey) ->
} Algorithm.ECDSA256(KeyLoader.loadEcdsaPrivateKey(privateKey)));
} catch (NoSuchAlgorithmException ignored) {
} catch (InvalidKeySpecException e) {
throw new RuntimeException(e);
}
return null;
});
}}; }};
} }
@@ -20,6 +20,8 @@ package com.onixbyte.simplejwt.config;
import com.onixbyte.simplejwt.TokenResolver; import com.onixbyte.simplejwt.TokenResolver;
import com.onixbyte.simplejwt.constants.TokenAlgorithm; import com.onixbyte.simplejwt.constants.TokenAlgorithm;
import java.util.List;
/** /**
* The {@code TokenResolverConfig} provides a mechanism to configure an * The {@code TokenResolverConfig} provides a mechanism to configure an
* implementation of {@link TokenResolver} with algorithm functions. * implementation of {@link TokenResolver} with algorithm functions.
@@ -49,11 +51,15 @@ public interface TokenResolverConfig<Algo> {
* implementation that can be used by the {@link TokenResolver} to handle * implementation that can be used by the {@link TokenResolver} to handle
* the specific algorithm. * the specific algorithm.
* *
* @param algorithm the {@link TokenAlgorithm} for which the algorithm * @param algorithm the {@link TokenAlgorithm} for which the algorithm function is required
* function is required * @return the algorithm function associated with the given {@link TokenAlgorithm}
* @return the algorithm function associated with the given {@link
* TokenAlgorithm}
*/ */
Algo getAlgorithm(TokenAlgorithm algorithm); Algo getAlgorithm(TokenAlgorithm algorithm);
List<TokenAlgorithm> ECDSA_ALGORITHMS =
List.of(TokenAlgorithm.ES256, TokenAlgorithm.ES384, TokenAlgorithm.ES512);
List<TokenAlgorithm> HMAC_ALGORITHMS =
List.of(TokenAlgorithm.HS256, TokenAlgorithm.HS384, TokenAlgorithm.HS512);
} }
@@ -35,13 +35,14 @@ import java.util.List;
* <li>{@link #JWT_ID}: Represents the "jti" (JWT ID) claim.</li> * <li>{@link #JWT_ID}: Represents the "jti" (JWT ID) claim.</li>
* </ul> * </ul>
* <p> * <p>
* The class also contains a list of all the standard claim constants, accessible via the {@link #KEYS} field. This * The class also contains a list of all the standard claim constants, accessible via the {@link
* list can be useful for iterating through all the standard claims or checking for the presence of specific claims. * #KEYS} field. This list can be useful for iterating through all the standard claims or checking
* for the presence of specific claims.
* <p> * <p>
* Note: This class is final and cannot be instantiated. It only serves as a utility class to hold the standard JWT * Note: This class is final and cannot be instantiated. It only serves as a utility class to hold
* claim constants. * the standard JWT claim constants.
* *
* @author Zihlu Wang * @author zihluwang
* @version 1.1.0 * @version 1.1.0
* @since 1.0.0 * @since 1.0.0
*/ */
@@ -85,7 +86,8 @@ public final class PredefinedKeys {
/** /**
* List containing all the standard JWT claim constants. * List containing all the standard JWT claim constants.
*/ */
public static final List<String> KEYS = List.of(ISSUER, SUBJECT, AUDIENCE, EXPIRATION_TIME, NOT_BEFORE, ISSUED_AT, JWT_ID); public static final List<String> KEYS =
List.of(ISSUER, SUBJECT, AUDIENCE, EXPIRATION_TIME, NOT_BEFORE, ISSUED_AT, JWT_ID);
/** /**
* Private constructor will protect this class from being instantiated. * Private constructor will protect this class from being instantiated.
@@ -112,9 +112,6 @@ public class JjwtTokenResolver implements TokenResolver<Jws<Claims>> {
} }
if (secret.length() < 32) { 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(""" throw new WeakSecretException("""
The provided secret which owns %s characters is too weak. Please replace it with a stronger one.""" The provided secret which owns %s characters is too weak. Please replace it with a stronger one."""
.formatted(secret.length())); .formatted(secret.length()));
@@ -166,9 +163,6 @@ public class JjwtTokenResolver implements TokenResolver<Jws<Claims>> {
} }
if (secret.length() < 32) { 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( throw new WeakSecretException(
"The provided secret which owns %s characters is too weak. Please replace it with a stronger one." "The provided secret which owns %s characters is too weak. Please replace it with a stronger one."
.formatted(secret.length())); .formatted(secret.length()));
@@ -23,6 +23,7 @@ import com.onixbyte.simplejwt.authzero.AuthzeroTokenResolver;
import com.onixbyte.simplejwt.autoconfiguration.properties.SimpleJwtProperties; import com.onixbyte.simplejwt.autoconfiguration.properties.SimpleJwtProperties;
import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.onixbyte.simplejwt.config.TokenResolverConfig;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
@@ -35,25 +36,23 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
/** /**
* {@code AuthzeroTokenResolverAutoConfiguration} is responsible for * {@code AuthzeroTokenResolverAutoConfiguration} is responsible for automatically configuring the
* automatically configuring the Simple JWT library with * Simple JWT library with
* {@code com.auth0:java-jwt} when used in a Spring Boot application. It * {@code com.auth0:java-jwt} when used in a Spring Boot application. It provides default settings
* provides default settings and configurations to ensure that the library * and configurations to ensure that the library works smoothly without requiring
* works smoothly without requiring manual configuration. * manual configuration.
* <p> * <p>
* This autoconfiguration class sets up the necessary beans and components * This autoconfiguration class sets up the necessary beans and components required for JWT
* required for JWT generation and validation. It automatically creates and * generation and validation. It automatically creates and configures the
* configures the {@link AuthzeroTokenResolver} bean based on the available * {@link AuthzeroTokenResolver} bean based on the available options and properties.
* options and properties.
* <p> * <p>
* Developers using the Simple JWT library with Spring Boot do not need to * Developers using the Simple JWT library with Spring Boot do not need to explicitly configure the
* explicitly configure the library, as the autoconfiguration takes care of * library, as the autoconfiguration takes care of setting up the necessary components and
* setting up the necessary components and configurations automatically. * configurations automatically. However, developers still have the flexibility to customise the
* However, developers still have the flexibility to customise the behavior of * behavior of the library by providing their own configurations and properties.
* the library by providing their own configurations and properties.
* *
* @author Zihlu Wang * @author zihluwang
* @version 1.0.0 * @version 1.6.0
* @since 1.0.0 * @since 1.0.0
*/ */
@Slf4j @Slf4j
@@ -74,7 +73,9 @@ public class AuthzeroTokenResolverAutoConfiguration {
* @param objectMapper jackson JSON Handler * @param objectMapper jackson JSON Handler
*/ */
@Autowired @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.jtiCreator = jtiCreator;
this.simpleJwtProperties = simpleJwtProperties; this.simpleJwtProperties = simpleJwtProperties;
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
@@ -90,13 +91,25 @@ public class AuthzeroTokenResolverAutoConfiguration {
*/ */
@Bean @Bean
public TokenResolver<DecodedJWT> tokenResolver() { public TokenResolver<DecodedJWT> tokenResolver() {
if (TokenResolverConfig.HMAC_ALGORITHMS.contains(simpleJwtProperties.algorithm())) {
return new AuthzeroTokenResolver( return new AuthzeroTokenResolver(
jtiCreator, jtiCreator,
simpleJwtProperties.algorithm(), simpleJwtProperties.algorithm(),
simpleJwtProperties.issuer(), simpleJwtProperties.issuer(),
simpleJwtProperties.secret(), simpleJwtProperties.secret(),
"",
objectMapper objectMapper
); );
} else {
return new AuthzeroTokenResolver(
jtiCreator,
simpleJwtProperties.algorithm(),
simpleJwtProperties.issuer(),
simpleJwtProperties.getPrivateKey(),
simpleJwtProperties.getPublicKey(),
objectMapper
);
}
} }
private final GuidCreator<?> jtiCreator; private final GuidCreator<?> jtiCreator;
@@ -71,6 +71,13 @@ public class SimpleJwtProperties {
*/ */
private String secret = SecretCreator.createSecret(32, true, true, true); 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. * Returns the JWT algorithm configured in the properties.
* *