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) {