From a44ad6804192b06f141c12e065e13c617909292c Mon Sep 17 00:00:00 2001 From: Zihlu Wang Date: Tue, 17 Oct 2023 03:38:47 +0800 Subject: [PATCH 01/10] feat(simple-jwt): Added method to create new tokens using data from old ones and the default implementation for the methods that don't need a expireAfter. --- .../codecrafters/simplejwt/TokenResolver.java | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/TokenResolver.java b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/TokenResolver.java index c69818d..d0df2cb 100644 --- a/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/TokenResolver.java +++ b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/TokenResolver.java @@ -110,6 +110,26 @@ public interface TokenResolver { */ T extract(String token, Class targetType); + /** + * 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 + */ + String renew(String oldToken, Duration expireAfter); + + /** + * Re-generate a new token with the payload in the old one. + * + * @param oldToken the old token + * @return re-generated token with the payload in the old one + * @see #renew(String, Duration) + */ + default String renew(String oldToken) { + return renew(oldToken, Duration.ofMinutes(30)); + } + /** * Renews the given expired token with the specified custom payload data. * @@ -129,7 +149,9 @@ public interface TokenResolver { * token * @return the renewed token as a {@code String} */ - String renew(String oldToken, Map payload); + default String renew(String oldToken, Map payload) { + return renew(oldToken, Duration.ofMinutes(30), payload); + } /** * Renews the given expired token with the specified strongly-typed @@ -156,6 +178,8 @@ public interface TokenResolver { * renewed token * @return the renewed token as a {@code String} */ - String renew(String oldToken, T payload); + default String renew(String oldToken, T payload) { + return renew(oldToken, Duration.ofMinutes(30), payload); + } } From f95efc71c3501a623cabc97eab6b514398066dab Mon Sep 17 00:00:00 2001 From: Zihlu Wang Date: Tue, 17 Oct 2023 03:40:05 +0800 Subject: [PATCH 02/10] feat(global): Upgrade to v1.2.0-alpha --- devkit-core/pom.xml | 2 +- devkit-utils/pom.xml | 2 +- guid/pom.xml | 2 +- pom.xml | 2 +- property-guard-spring-boot-starter/pom.xml | 2 +- simple-jwt-authzero/pom.xml | 2 +- simple-jwt-facade/pom.xml | 2 +- simple-jwt-jjwt/pom.xml | 2 +- simple-jwt-spring-boot-starter/pom.xml | 2 +- webcal/pom.xml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/devkit-core/pom.xml b/devkit-core/pom.xml index f4bf40e..e9aaf22 100644 --- a/devkit-core/pom.xml +++ b/devkit-core/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.1.1 + 1.2.0-alpha devkit-core diff --git a/devkit-utils/pom.xml b/devkit-utils/pom.xml index 1ddf6c5..e79956c 100644 --- a/devkit-utils/pom.xml +++ b/devkit-utils/pom.xml @@ -6,7 +6,7 @@ cn.org.codecrafters jdevkit - 1.1.1 + 1.2.0-alpha devkit-utils diff --git a/guid/pom.xml b/guid/pom.xml index e3742fb..fa3186e 100644 --- a/guid/pom.xml +++ b/guid/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.1.1 + 1.2.0-alpha guid diff --git a/pom.xml b/pom.xml index 3ea9171..3471beb 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ cn.org.codecrafters jdevkit - 1.1.1 + 1.2.0-alpha 2023 pom diff --git a/property-guard-spring-boot-starter/pom.xml b/property-guard-spring-boot-starter/pom.xml index 8df59be..a4cad59 100644 --- a/property-guard-spring-boot-starter/pom.xml +++ b/property-guard-spring-boot-starter/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.1.1 + 1.2.0-alpha property-guard-spring-boot-starter diff --git a/simple-jwt-authzero/pom.xml b/simple-jwt-authzero/pom.xml index 29e96b1..312a239 100644 --- a/simple-jwt-authzero/pom.xml +++ b/simple-jwt-authzero/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.1.1 + 1.2.0-alpha simple-jwt-authzero diff --git a/simple-jwt-facade/pom.xml b/simple-jwt-facade/pom.xml index d16ecf6..d9ff380 100644 --- a/simple-jwt-facade/pom.xml +++ b/simple-jwt-facade/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.1.1 + 1.2.0-alpha simple-jwt-facade diff --git a/simple-jwt-jjwt/pom.xml b/simple-jwt-jjwt/pom.xml index ff49ffa..36479b7 100644 --- a/simple-jwt-jjwt/pom.xml +++ b/simple-jwt-jjwt/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.1.1 + 1.2.0-alpha simple-jwt-jjwt diff --git a/simple-jwt-spring-boot-starter/pom.xml b/simple-jwt-spring-boot-starter/pom.xml index 50a9890..66d8704 100644 --- a/simple-jwt-spring-boot-starter/pom.xml +++ b/simple-jwt-spring-boot-starter/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.1.1 + 1.2.0-alpha simple-jwt-spring-boot-starter diff --git a/webcal/pom.xml b/webcal/pom.xml index eb49cb2..cdec1fd 100644 --- a/webcal/pom.xml +++ b/webcal/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.1.1 + 1.2.0-alpha webcal From d62d64c57285bd2cbdd589efbd930f63232326b5 Mon Sep 17 00:00:00 2001 From: Zihlu Wang Date: Tue, 17 Oct 2023 10:25:06 +0800 Subject: [PATCH 03/10] build: Added jackson for json processing. --- simple-jwt-authzero/pom.xml | 5 +++++ simple-jwt-facade/pom.xml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/simple-jwt-authzero/pom.xml b/simple-jwt-authzero/pom.xml index 312a239..b162358 100644 --- a/simple-jwt-authzero/pom.xml +++ b/simple-jwt-authzero/pom.xml @@ -40,6 +40,11 @@ simple-jwt-facade + + com.fasterxml.jackson.core + jackson-databind + + com.auth0 java-jwt diff --git a/simple-jwt-facade/pom.xml b/simple-jwt-facade/pom.xml index d9ff380..f45f12f 100644 --- a/simple-jwt-facade/pom.xml +++ b/simple-jwt-facade/pom.xml @@ -40,6 +40,11 @@ devkit-core + + cn.org.codecrafters + devkit-utils + + cn.org.codecrafters guid From da04fb4c5587f015a415728e53250889f4fc1528 Mon Sep 17 00:00:00 2001 From: Zihlu Wang Date: Tue, 17 Oct 2023 10:25:24 +0800 Subject: [PATCH 04/10] feat(simple-jwt-authzero): Added implementation of renew a token with the data within the old one. --- .../authzero/AuthzeroTokenResolver.java | 107 ++++++++++++------ ...uthzeroTokenResolverAutoConfiguration.java | 11 +- 2 files changed, 79 insertions(+), 39 deletions(-) diff --git a/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/AuthzeroTokenResolver.java b/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/AuthzeroTokenResolver.java index c2d83ce..1dfd676 100644 --- a/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/AuthzeroTokenResolver.java +++ b/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/AuthzeroTokenResolver.java @@ -17,6 +17,7 @@ package cn.org.codecrafters.simplejwt.authzero; +import cn.org.codecrafters.devkit.utils.Base64Util; import cn.org.codecrafters.guid.GuidCreator; import cn.org.codecrafters.simplejwt.SecretCreator; import cn.org.codecrafters.simplejwt.TokenPayload; @@ -24,12 +25,20 @@ import cn.org.codecrafters.simplejwt.TokenResolver; import cn.org.codecrafters.simplejwt.annotations.ExcludeFromPayload; import cn.org.codecrafters.simplejwt.authzero.config.AuthzeroTokenResolverConfig; import cn.org.codecrafters.simplejwt.config.TokenResolverConfig; +import cn.org.codecrafters.simplejwt.constants.PredefinedKeys; import cn.org.codecrafters.simplejwt.constants.TokenAlgorithm; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTCreator; import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.JWTVerifier; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationTargetException; @@ -114,21 +123,27 @@ public class AuthzeroTokenResolver implements TokenResolver { */ private final JWTVerifier verifier; + /** + * Jackson JSON handler. + */ + private final ObjectMapper objectMapper; + private final AuthzeroTokenResolverConfig config = AuthzeroTokenResolverConfig.getInstance(); /** * 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 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 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 objectMapper JSON handler */ - public AuthzeroTokenResolver(GuidCreator jtiCreator, TokenAlgorithm algorithm, String issuer, String secret) { + 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."); } @@ -143,6 +158,21 @@ public class AuthzeroTokenResolver implements TokenResolver { .apply(secret); 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. + * + * @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 objectMapper Jackson Databind JSON Handler + */ + public AuthzeroTokenResolver(TokenAlgorithm algorithm, String issuer, String secret, ObjectMapper objectMapper) { + this(UUID::randomUUID, algorithm, issuer, secret, objectMapper); } /** @@ -155,20 +185,7 @@ public class AuthzeroTokenResolver implements TokenResolver { * HS384, HS512) for token signing and verification */ public AuthzeroTokenResolver(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.warn("The provided secret which owns {} characters is too weak. Please consider replacing it with a stronger one.", secret.length()); - } - - this.jtiCreator = UUID::randomUUID; - this.algorithm = config - .getAlgorithm(algorithm) - .apply(secret); - this.issuer = issuer; - this.verifier = JWT.require(this.algorithm).build(); + this(UUID::randomUUID, algorithm, issuer, secret, new ObjectMapper()); } /** @@ -181,20 +198,7 @@ public class AuthzeroTokenResolver implements TokenResolver { * HS384, HS512) for token signing and verification */ public AuthzeroTokenResolver(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.warn("The provided secret which owns {} characters is too weak. Please consider replacing it with a stronger one.", secret.length()); - } - - this.jtiCreator = UUID::randomUUID; - this.algorithm = config - .getAlgorithm(TokenAlgorithm.HS256) - .apply(secret); - this.issuer = issuer; - this.verifier = JWT.require(this.algorithm).build(); + this(UUID::randomUUID, TokenAlgorithm.HS256, issuer, secret, new ObjectMapper()); } /** @@ -213,6 +217,7 @@ public class AuthzeroTokenResolver implements TokenResolver { .apply(secret); this.issuer = issuer; this.verifier = JWT.require(this.algorithm).build(); + this.objectMapper = new ObjectMapper(); log.info("The secret has been set to {}.", secret); } @@ -448,6 +453,31 @@ public class AuthzeroTokenResolver implements TokenResolver { 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 or + * {@code null} if an {@link JsonProcessingException} occurred. + */ + @Override + public String renew(String oldToken, Duration expireAfter) { + var resolved = resolve(oldToken); + + try { + var payload = objectMapper.readValue(Base64Util.decode(resolved.getPayload()), ObjectNode.class); + payload.remove(PredefinedKeys.KEYS); + + var payloadMap = objectMapper.convertValue(payload, new MapTypeReference()); + return createToken(expireAfter, resolved.getAudience().get(0), resolved.getSubject(), payloadMap); + } catch (JsonProcessingException e) { + log.error("Cannot read payload content, error details:", e); + } + + return null; + } + /** * Renews the given expired token with the specified custom payload data. * @@ -509,4 +539,9 @@ public class AuthzeroTokenResolver implements TokenResolver { public String renew(String oldToken, T payload) { return renew(oldToken, Duration.ofMinutes(30), payload); } + + private static class MapTypeReference extends TypeReference> { + MapTypeReference() { + } + } } diff --git a/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java index 747b36a..504c575 100644 --- a/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java +++ b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java @@ -22,6 +22,7 @@ import cn.org.codecrafters.simplejwt.TokenResolver; import cn.org.codecrafters.simplejwt.authzero.AuthzeroTokenResolver; import cn.org.codecrafters.simplejwt.autoconfiguration.properties.SimpleJwtProperties; import com.auth0.jwt.interfaces.DecodedJWT; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -31,7 +32,6 @@ 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; -import org.springframework.context.annotation.DependsOn; /** * {@code AuthzeroTokenResolverAutoConfiguration} is responsible for @@ -75,16 +75,20 @@ public class AuthzeroTokenResolverAutoConfiguration { */ private final SimpleJwtProperties simpleJwtProperties; + private final ObjectMapper objectMapper; + /** * Constructs a new {@code SimpleJwtAutoConfiguration} instance with the * provided SimpleJwtProperties. * * @param simpleJwtProperties the SimpleJwtProperties instance + * @param objectMapper Jackson JSON Handler */ @Autowired - public AuthzeroTokenResolverAutoConfiguration(SimpleJwtProperties simpleJwtProperties, GuidCreator jtiCreator) { + public AuthzeroTokenResolverAutoConfiguration(SimpleJwtProperties simpleJwtProperties, GuidCreator jtiCreator, ObjectMapper objectMapper) { this.jtiCreator = jtiCreator; this.simpleJwtProperties = simpleJwtProperties; + this.objectMapper = objectMapper; } /** @@ -102,7 +106,8 @@ public class AuthzeroTokenResolverAutoConfiguration { jtiCreator, simpleJwtProperties.algorithm(), simpleJwtProperties.issuer(), - simpleJwtProperties.secret() + simpleJwtProperties.secret(), + objectMapper ); } From fd281ff99ecda2084c817bb8914c9c7911202495 Mon Sep 17 00:00:00 2001 From: Zihlu Wang Date: Tue, 17 Oct 2023 14:15:32 +0800 Subject: [PATCH 05/10] feat(simple-jwt-jjwt): Added the feature to generate a new Token using data from the original Token. --- .../simplejwt/jjwt/JjwtTokenResolver.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java b/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java index 5d8db00..09e96bd 100644 --- a/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java +++ b/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java @@ -23,9 +23,11 @@ import cn.org.codecrafters.simplejwt.SecretCreator; import cn.org.codecrafters.simplejwt.TokenPayload; import cn.org.codecrafters.simplejwt.TokenResolver; import cn.org.codecrafters.simplejwt.annotations.ExcludeFromPayload; +import cn.org.codecrafters.simplejwt.constants.PredefinedKeys; import cn.org.codecrafters.simplejwt.constants.TokenAlgorithm; import cn.org.codecrafters.simplejwt.exceptions.WeakSecretException; import cn.org.codecrafters.simplejwt.jjwt.config.JjwtTokenResolverConfig; +import com.fasterxml.jackson.core.type.TypeReference; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; @@ -303,6 +305,26 @@ public class JjwtTokenResolver implements TokenResolver> { 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.getBody(); + + var audience = tokenPayloads.getAudience(); + 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. * From 5b4b6e799733fabee932be462e43e3cc9228aa6b Mon Sep 17 00:00:00 2001 From: Zihlu Wang Date: Tue, 17 Oct 2023 21:12:33 +0800 Subject: [PATCH 06/10] refactor(simple-jwt): Changed the implementation of extracting data in the token payload. --- .../authzero/AuthzeroTokenResolver.java | 46 ++++++++----------- .../simplejwt/jjwt/JjwtTokenResolver.java | 19 +++++++- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/AuthzeroTokenResolver.java b/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/AuthzeroTokenResolver.java index 1dfd676..5dd9694 100644 --- a/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/AuthzeroTokenResolver.java +++ b/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/AuthzeroTokenResolver.java @@ -413,42 +413,32 @@ public class AuthzeroTokenResolver implements TokenResolver { */ @Override public T extract(String token, Class targetType) { - // Get claims from token. - var claims = resolve(token).getClaims(); - try { + // Get claims from token. + var payloads = objectMapper.readValue(Base64Util.decode(resolve(token).getPayload()), new MapTypeReference()); // Get the no-argument constructor to create an instance. - T bean = targetType.getConstructor().newInstance(); + var bean = targetType.getConstructor().newInstance(); - var fields = targetType.getDeclaredFields(); - for (var field : fields) { - // Ignore the field annotated with @ExcludeFromPayload. - if (field.isAnnotationPresent(ExcludeFromPayload.class)) + for (var entry : payloads.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; - // Get the name of this field. - var fieldName = field.getName(); - - // Prevent this class is annotated @Slf4j or added logger. - if ("log".equalsIgnoreCase(fieldName) || "logger".equalsIgnoreCase(fieldName)) - continue; - - // Get the value of this field. - var fieldValue = Optional.ofNullable(claims.get(fieldName)) - .map(claim -> claim.as(field.getType())) - .orElse(null); - if (fieldValue != null) { - // Set the field value by invoking the setter method. - var setter = targetType.getDeclaredMethod("set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1), fieldValue.getClass()); - setter.invoke(bean, fieldValue); + var setter = targetType.getDeclaredMethod("set" + entry.getKey().substring(0, 1).toUpperCase() + entry.getKey().substring(1), entry.getValue().getClass()); + if (setter.canAccess(bean)) { + setter.invoke(bean, entry.getValue()); + } else { + log.error("Setter for field {} can't be accessed.", entry.getKey()); } } - return bean; - } catch (NoSuchMethodException e) { - log.error("Unable to find a no-argument constructor declaration for class {}.", targetType.getCanonicalName()); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - log.error("Unable to create a new instance of class {}.", targetType.getCanonicalName()); + } catch (JsonProcessingException e) { + log.error("Unable to read payload as a Map.", e); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException | + NoSuchMethodException e) { + log.error("Unable to load the constructor or setter.", e); + } catch (NoSuchFieldException e) { + log.error("Unable to load the field.", e); } return null; } diff --git a/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java b/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java index 09e96bd..203cbd4 100644 --- a/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java +++ b/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java @@ -291,7 +291,20 @@ public class JjwtTokenResolver implements TokenResolver> { var claims = resolvedToken.getBody(); try { - return MapUtil.mapToObject(claims, targetType); + 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 setter = targetType.getDeclaredMethod("set" + entry.getKey().substring(0, 1).toUpperCase() + entry.getKey().substring(1), entry.getValue().getClass()); + if (setter.canAccess(bean)) { + setter.invoke(bean, entry.getValue()); + } else { + log.error("Setter for field {} can't be accessed.", entry.getKey()); + } + } } catch (InvocationTargetException e) { log.error("An error occurs while invoking the constructor of type {}.", targetType.getCanonicalName()); } catch (NoSuchMethodException e) { @@ -299,7 +312,9 @@ public class JjwtTokenResolver implements TokenResolver> { } 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."); + 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; From ce556194b8945ef93c833d771c8785a6158f65a2 Mon Sep 17 00:00:00 2001 From: Zihlu Wang Date: Tue, 17 Oct 2023 21:44:29 +0800 Subject: [PATCH 07/10] build(global): Upgrade versions. --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 3471beb..1ad6740 100644 --- a/pom.xml +++ b/pom.xml @@ -86,10 +86,10 @@ 3.3.0 - 1.4.8 + 1.4.11 5.10.0 - 2.0.7 - 1.18.28 + 2.0.9 + 1.18.30 2.15.2 4.4.0 0.11.5 From c0aa871765c8cd3fdaf171837f818aed9d0a6318 Mon Sep 17 00:00:00 2001 From: Zihlu Wang Date: Wed, 18 Oct 2023 00:32:03 +0800 Subject: [PATCH 08/10] feat(simple-jwt): Added the feature to handle enumerated data using the base data type. --- .../authzero/AuthzeroTokenResolver.java | 38 ++++++++++++---- .../simplejwt/annotations/TokenEnum.java | 40 +++++++++++++++++ .../simplejwt/constants/TokenDataType.java | 44 +++++++++++++++++++ .../simplejwt/jjwt/JjwtTokenResolver.java | 35 ++++++++++++--- 4 files changed, 142 insertions(+), 15 deletions(-) create mode 100644 simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/annotations/TokenEnum.java create mode 100644 simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/TokenDataType.java diff --git a/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/AuthzeroTokenResolver.java b/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/AuthzeroTokenResolver.java index 5dd9694..e60e1d4 100644 --- a/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/AuthzeroTokenResolver.java +++ b/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/AuthzeroTokenResolver.java @@ -23,6 +23,7 @@ import cn.org.codecrafters.simplejwt.SecretCreator; import cn.org.codecrafters.simplejwt.TokenPayload; import cn.org.codecrafters.simplejwt.TokenResolver; import cn.org.codecrafters.simplejwt.annotations.ExcludeFromPayload; +import cn.org.codecrafters.simplejwt.annotations.TokenEnum; import cn.org.codecrafters.simplejwt.authzero.config.AuthzeroTokenResolverConfig; import cn.org.codecrafters.simplejwt.config.TokenResolverConfig; import cn.org.codecrafters.simplejwt.constants.PredefinedKeys; @@ -41,6 +42,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.time.Duration; import java.time.LocalDateTime; @@ -375,16 +377,28 @@ public class AuthzeroTokenResolver implements TokenResolver { var fields = payloadClass.getDeclaredFields(); for (var field : fields) { - // Skip the fields which are annotated with ExcludeFromPayload - if (field.isAnnotationPresent(ExcludeFromPayload.class)) - continue; - try { - field.setAccessible(true); + var fieldName = field.getName(); + // Skip the fields which are annotated with ExcludeFromPayload + if (field.isAnnotationPresent(ExcludeFromPayload.class)) + continue; + + Object invokeObj = payload; + var getter = payloadClass.getDeclaredMethod("get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1)); + if (field.isAnnotationPresent(TokenEnum.class)) { + var tokenEnum = field.getAnnotation(TokenEnum.class); + invokeObj = getter.invoke(payload); + getter = field.getType().getDeclaredMethod("get" + tokenEnum.propertyName().substring(0, 1).toUpperCase() + tokenEnum.propertyName().substring(1)); + } + // Build Claims - addClaim(builder, field.getName(), field.get(payload)); + addClaim(builder, fieldName, getter.invoke(invokeObj)); } catch (IllegalAccessException e) { log.error("Cannot access field {}!", field.getName()); + } catch (NoSuchMethodException e) { + log.error("Unable to find setter according to given field name.", e); + } catch (InvocationTargetException e) { + log.info("Cannot invoke method.", e); } } @@ -424,9 +438,17 @@ public class AuthzeroTokenResolver implements TokenResolver { if (PredefinedKeys.KEYS.contains(entry.getKey()) || targetType.getDeclaredField(entry.getKey()).isAnnotationPresent(ExcludeFromPayload.class)) continue; - var setter = targetType.getDeclaredMethod("set" + entry.getKey().substring(0, 1).toUpperCase() + entry.getKey().substring(1), entry.getValue().getClass()); + var field = targetType.getDeclaredField(entry.getKey()); + var setter = targetType.getDeclaredMethod("set" + entry.getKey().substring(0, 1).toUpperCase() + entry.getKey().substring(1), field.getType()); + 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, fieldValue); + } + if (setter.canAccess(bean)) { - setter.invoke(bean, entry.getValue()); + setter.invoke(bean, fieldValue); } else { log.error("Setter for field {} can't be accessed.", entry.getKey()); } diff --git a/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/annotations/TokenEnum.java b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/annotations/TokenEnum.java new file mode 100644 index 0000000..3fac142 --- /dev/null +++ b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/annotations/TokenEnum.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 CodeCraftersCN. + * + * 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 cn.org.codecrafters.simplejwt.annotations; + +import cn.org.codecrafters.simplejwt.constants.TokenDataType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * JwtEnum + * + * @author Zihlu Wang + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface TokenEnum { + + String propertyName(); + + TokenDataType dataType(); + +} diff --git a/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/TokenDataType.java b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/TokenDataType.java new file mode 100644 index 0000000..b7e064a --- /dev/null +++ b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/TokenDataType.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 CodeCraftersCN. + * + * 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 cn.org.codecrafters.simplejwt.constants; + +import lombok.Getter; + +/** + * TokenDataType + * + * @author Zihlu Wang + */ +@Getter +public enum TokenDataType { + + BOOLEAN(Boolean.class), + DOUBLE(Long.class), + FLOAT(Float.class), + INTEGER(Integer.class), + LONG(Long.class), + STRING(String.class), + ; + + private final Class mappedClass; + + TokenDataType(Class mappedClass) { + this.mappedClass = mappedClass; + } + +} diff --git a/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java b/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java index 203cbd4..ba1f12f 100644 --- a/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java +++ b/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java @@ -23,6 +23,7 @@ import cn.org.codecrafters.simplejwt.SecretCreator; import cn.org.codecrafters.simplejwt.TokenPayload; import cn.org.codecrafters.simplejwt.TokenResolver; import cn.org.codecrafters.simplejwt.annotations.ExcludeFromPayload; +import cn.org.codecrafters.simplejwt.annotations.TokenEnum; import cn.org.codecrafters.simplejwt.constants.PredefinedKeys; import cn.org.codecrafters.simplejwt.constants.TokenAlgorithm; import cn.org.codecrafters.simplejwt.exceptions.WeakSecretException; @@ -247,14 +248,24 @@ public class JjwtTokenResolver implements TokenResolver> { continue; try { - field.setAccessible(true); + 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. */ - payloadMap.put(field.getName(), field.get(payload)); - } catch (IllegalAccessException e) { + 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); } } @@ -298,17 +309,27 @@ public class JjwtTokenResolver implements TokenResolver> { if (PredefinedKeys.KEYS.contains(entry.getKey()) || targetType.getDeclaredField(entry.getKey()).isAnnotationPresent(ExcludeFromPayload.class)) continue; - var setter = targetType.getDeclaredMethod("set" + entry.getKey().substring(0, 1).toUpperCase() + entry.getKey().substring(1), entry.getValue().getClass()); + 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, entry.getValue()); + setter.invoke(bean, fieldValue); } else { log.error("Setter for field {} can't be accessed.", entry.getKey()); } } + + return bean; } catch (InvocationTargetException e) { - log.error("An error occurs while invoking the constructor of type {}.", targetType.getCanonicalName()); + log.error("Target is not invokable.", e); } catch (NoSuchMethodException e) { - log.error("The constructor of the required type {} is not found.", targetType.getCanonicalName()); + 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) { From 49f44fb2b756ff2b5037f0482d3741c0b6ed3c75 Mon Sep 17 00:00:00 2001 From: Zihlu Wang Date: Wed, 18 Oct 2023 00:40:46 +0800 Subject: [PATCH 09/10] docs(simple-jwt): Optimised Javadoc. Closes #18 --- .../simplejwt/annotations/TokenEnum.java | 11 +++++++- .../simplejwt/constants/TokenDataType.java | 28 ++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/annotations/TokenEnum.java b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/annotations/TokenEnum.java index 3fac142..2258b54 100644 --- a/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/annotations/TokenEnum.java +++ b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/annotations/TokenEnum.java @@ -25,7 +25,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * JwtEnum + * This annotation marks the enum field declared in payload class will be + * handled as basic data types in {@link TokenDataType}. * * @author Zihlu Wang */ @@ -33,8 +34,16 @@ import java.lang.annotation.Target; @Target({ElementType.FIELD}) public @interface TokenEnum { + /** + * The name of the field of the base data corresponding to the + * enumeration data. + */ String propertyName(); + /** + * The attribute {@code dataType} specifies what base data type to treat + * this enum as. + */ TokenDataType dataType(); } diff --git a/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/TokenDataType.java b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/TokenDataType.java index b7e064a..767c205 100644 --- a/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/TokenDataType.java +++ b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/TokenDataType.java @@ -20,21 +20,47 @@ package cn.org.codecrafters.simplejwt.constants; import lombok.Getter; /** - * TokenDataType + * The base data types used to process enum data. * * @author Zihlu Wang */ @Getter public enum TokenDataType { + /** + * Marks enumeration being processed as Boolean. + */ BOOLEAN(Boolean.class), + + /** + * Marks enumeration being processed as Double. + */ DOUBLE(Long.class), + + /** + * Marks enumeration being processed as Float. + */ FLOAT(Float.class), + + /** + * Marks enumeration being processed as Integer. + */ INTEGER(Integer.class), + + /** + * Marks enumeration being processed as Long. + */ LONG(Long.class), + + /** + * Marks enumeration being processed as String. + */ STRING(String.class), ; + /** + * The mapped class to this mark. + */ private final Class mappedClass; TokenDataType(Class mappedClass) { From 66c2c4c8b7d2cd509fe2bd5f53aa11c175231eea Mon Sep 17 00:00:00 2001 From: Zihlu Wang Date: Wed, 18 Oct 2023 00:45:49 +0800 Subject: [PATCH 10/10] build(global): Build the version 1.2.0. --- devkit-core/pom.xml | 2 +- devkit-utils/pom.xml | 2 +- guid/pom.xml | 2 +- pom.xml | 2 +- property-guard-spring-boot-starter/pom.xml | 2 +- simple-jwt-authzero/pom.xml | 2 +- simple-jwt-facade/pom.xml | 2 +- simple-jwt-jjwt/pom.xml | 2 +- simple-jwt-spring-boot-starter/pom.xml | 2 +- webcal/pom.xml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/devkit-core/pom.xml b/devkit-core/pom.xml index e9aaf22..ccc7c75 100644 --- a/devkit-core/pom.xml +++ b/devkit-core/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.2.0-alpha + 1.2.0 devkit-core diff --git a/devkit-utils/pom.xml b/devkit-utils/pom.xml index e79956c..382d8e0 100644 --- a/devkit-utils/pom.xml +++ b/devkit-utils/pom.xml @@ -6,7 +6,7 @@ cn.org.codecrafters jdevkit - 1.2.0-alpha + 1.2.0 devkit-utils diff --git a/guid/pom.xml b/guid/pom.xml index fa3186e..8b433d8 100644 --- a/guid/pom.xml +++ b/guid/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.2.0-alpha + 1.2.0 guid diff --git a/pom.xml b/pom.xml index 1ad6740..aab7e1b 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ cn.org.codecrafters jdevkit - 1.2.0-alpha + 1.2.0 2023 pom diff --git a/property-guard-spring-boot-starter/pom.xml b/property-guard-spring-boot-starter/pom.xml index a4cad59..a64a046 100644 --- a/property-guard-spring-boot-starter/pom.xml +++ b/property-guard-spring-boot-starter/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.2.0-alpha + 1.2.0 property-guard-spring-boot-starter diff --git a/simple-jwt-authzero/pom.xml b/simple-jwt-authzero/pom.xml index b162358..868218b 100644 --- a/simple-jwt-authzero/pom.xml +++ b/simple-jwt-authzero/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.2.0-alpha + 1.2.0 simple-jwt-authzero diff --git a/simple-jwt-facade/pom.xml b/simple-jwt-facade/pom.xml index f45f12f..771967b 100644 --- a/simple-jwt-facade/pom.xml +++ b/simple-jwt-facade/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.2.0-alpha + 1.2.0 simple-jwt-facade diff --git a/simple-jwt-jjwt/pom.xml b/simple-jwt-jjwt/pom.xml index 36479b7..487d9c6 100644 --- a/simple-jwt-jjwt/pom.xml +++ b/simple-jwt-jjwt/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.2.0-alpha + 1.2.0 simple-jwt-jjwt diff --git a/simple-jwt-spring-boot-starter/pom.xml b/simple-jwt-spring-boot-starter/pom.xml index 66d8704..01077f4 100644 --- a/simple-jwt-spring-boot-starter/pom.xml +++ b/simple-jwt-spring-boot-starter/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.2.0-alpha + 1.2.0 simple-jwt-spring-boot-starter diff --git a/webcal/pom.xml b/webcal/pom.xml index cdec1fd..8422095 100644 --- a/webcal/pom.xml +++ b/webcal/pom.xml @@ -23,7 +23,7 @@ cn.org.codecrafters jdevkit - 1.2.0-alpha + 1.2.0 webcal