diff --git a/devkit-core/pom.xml b/devkit-core/pom.xml
index 8de2632..ccc7c75 100644
--- a/devkit-core/pom.xml
+++ b/devkit-core/pom.xml
@@ -23,7 +23,7 @@
cn.org.codecrafters
jdevkit
- 1.1.2
+ 1.2.0
devkit-core
diff --git a/devkit-utils/pom.xml b/devkit-utils/pom.xml
index b896679..382d8e0 100644
--- a/devkit-utils/pom.xml
+++ b/devkit-utils/pom.xml
@@ -6,7 +6,7 @@
cn.org.codecrafters
jdevkit
- 1.1.2
+ 1.2.0
devkit-utils
diff --git a/guid/pom.xml b/guid/pom.xml
index 1b1e332..8b433d8 100644
--- a/guid/pom.xml
+++ b/guid/pom.xml
@@ -23,7 +23,7 @@
cn.org.codecrafters
jdevkit
- 1.1.2
+ 1.2.0
guid
diff --git a/pom.xml b/pom.xml
index 5e7ef0c..aab7e1b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
cn.org.codecrafters
jdevkit
- 1.1.2
+ 1.2.0
2023
pom
diff --git a/property-guard-spring-boot-starter/pom.xml b/property-guard-spring-boot-starter/pom.xml
index 20c5f21..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.1.2
+ 1.2.0
property-guard-spring-boot-starter
diff --git a/simple-jwt-authzero/pom.xml b/simple-jwt-authzero/pom.xml
index c046d47..868218b 100644
--- a/simple-jwt-authzero/pom.xml
+++ b/simple-jwt-authzero/pom.xml
@@ -23,7 +23,7 @@
cn.org.codecrafters
jdevkit
- 1.1.2
+ 1.2.0
simple-jwt-authzero
@@ -40,6 +40,11 @@
simple-jwt-facade
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
com.auth0
java-jwt
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..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
@@ -17,21 +17,32 @@
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;
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;
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.Field;
import java.lang.reflect.InvocationTargetException;
import java.time.Duration;
import java.time.LocalDateTime;
@@ -114,21 +125,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 +160,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 +187,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 +200,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 +219,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);
}
@@ -370,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);
}
}
@@ -408,46 +427,69 @@ 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();
+ 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);
+ }
- // 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());
+ if (setter.canAccess(bean)) {
setter.invoke(bean, fieldValue);
+ } 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;
}
+ /**
+ * 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 +551,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
+
+ cn.org.codecrafters
+ devkit-utils
+
+
cn.org.codecrafters
guid
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);
+ }
}
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..2258b54
--- /dev/null
+++ b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/annotations/TokenEnum.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+/**
+ * This annotation marks the enum field declared in payload class will be
+ * handled as basic data types in {@link TokenDataType}.
+ *
+ * @author Zihlu Wang
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@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
new file mode 100644
index 0000000..767c205
--- /dev/null
+++ b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/TokenDataType.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+/**
+ * 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) {
+ this.mappedClass = mappedClass;
+ }
+
+}
diff --git a/simple-jwt-jjwt/pom.xml b/simple-jwt-jjwt/pom.xml
index ee61cf3..487d9c6 100644
--- a/simple-jwt-jjwt/pom.xml
+++ b/simple-jwt-jjwt/pom.xml
@@ -23,7 +23,7 @@
cn.org.codecrafters
jdevkit
- 1.1.2
+ 1.2.0
simple-jwt-jjwt
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..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,9 +23,12 @@ 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;
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;
@@ -245,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);
}
}
@@ -289,20 +302,65 @@ 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 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("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) {
- 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;
}
+ /**
+ * 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.
*
diff --git a/simple-jwt-spring-boot-starter/pom.xml b/simple-jwt-spring-boot-starter/pom.xml
index f77febf..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.1.2
+ 1.2.0
simple-jwt-spring-boot-starter
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
);
}
diff --git a/webcal/pom.xml b/webcal/pom.xml
index 65b198e..8422095 100644
--- a/webcal/pom.xml
+++ b/webcal/pom.xml
@@ -23,7 +23,7 @@
cn.org.codecrafters
jdevkit
- 1.1.2
+ 1.2.0
webcal