From 948521cb4c59bab98ccb0f2fc6732e4b88bb8cf4 Mon Sep 17 00:00:00 2001 From: Zihlu Wang Date: Sun, 30 Jul 2023 22:47:17 +0800 Subject: [PATCH] feat(simple-jwt-authzero): finished authzero implementation --- pom.xml | 13 + simple-jwt-authzero/pom.xml | 54 ++ .../authzero/AuthzeroTokenResolver.java | 461 ++++++++++++++++++ .../config/AuthzeroTokenResolverConfig.java | 139 ++++++ .../src/main/resources/logback.xml | 38 ++ .../test/TestAuthzeroTokenResolver.java | 40 ++ 6 files changed, 745 insertions(+) create mode 100644 simple-jwt-authzero/pom.xml create mode 100644 simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/AuthzeroTokenResolver.java create mode 100644 simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java create mode 100644 simple-jwt-authzero/src/main/resources/logback.xml create mode 100644 simple-jwt-authzero/src/test/java/cn/org/codecrafters/simplejwt/authzero/test/TestAuthzeroTokenResolver.java diff --git a/pom.xml b/pom.xml index ff7bfb0..c94141b 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ guid dev-utils simple-jwt-facade + simple-jwt-authzero @@ -110,6 +111,18 @@ devkit-core ${project.version} + + + cn.org.codecrafters + guid + ${project.version} + + + + cn.org.codecrafters + simple-jwt-facade + ${project.version} + diff --git a/simple-jwt-authzero/pom.xml b/simple-jwt-authzero/pom.xml new file mode 100644 index 0000000..2b5ff33 --- /dev/null +++ b/simple-jwt-authzero/pom.xml @@ -0,0 +1,54 @@ + + + + + 4.0.0 + + cn.org.codecrafters + jdevkit + 1.0.0 + + + simple-jwt-authzero + + + 17 + 17 + UTF-8 + + + + + cn.org.codecrafters + guid + + + + cn.org.codecrafters + simple-jwt-facade + + + + com.auth0 + java-jwt + + + + \ No newline at end of file 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 new file mode 100644 index 0000000..7e4e52d --- /dev/null +++ b/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/AuthzeroTokenResolver.java @@ -0,0 +1,461 @@ +/* + * 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.authzero; + +import cn.org.codecrafters.devkit.guid.GuidCreator; +import cn.org.codecrafters.simplejwt.TokenPayload; +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.constants.TokenAlgorithm; +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTCreator; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.JWTVerifier; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.InvocationTargetException; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; + +/** + * The {@code AuthzeroTokenResolver} class is an implementation of the {@link + * cn.org.codecrafters.simplejwt.TokenResolver} interface. It uses the {@code + * com.auth0:java-jwt} library to handle JSON Web Token (JWT) resolution. This + * resolver provides functionality to create, extract, verify, and renew JWT + * tokens using various algorithms and custom payload data. + *

+ * Dependencies: + * This implementation relies on the {@code com.auth0:java-jwt} library. Please + * ensure you have added this library as a dependency to your project before + * using this resolver. + *

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

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

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

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

+ * Note: + * It is essential to configure the appropriate algorithms, secret, and issuer + * according to your specific use case when using this resolver. + * Additionally, ensure that the {@code com.auth0:java-jwt} library is + * correctly configured in your project's dependencies. + * + * @author Zihlu Wang + * @version 1.0.0 + * @see GuidCreator + * @see Algorithm + * @see JWTVerifier + * @see JWTCreator + * @see JWTCreator.Builder + * @since 1.0.0 + */ +@Slf4j +public class AuthzeroTokenResolver implements TokenResolver { + + /** + * GuidCreator used for generating unique identifiers for "jti" claim in + * JWT tokens. + */ + private final GuidCreator jtiCreator; + + /** + * The algorithm used for signing and verifying JWT tokens. + */ + private final Algorithm algorithm; + + /** + * The issuer claim value to be included in JWT tokens. + */ + private final String issuer; + + /** + * The JSON Web Token resolver. + */ + private final JWTVerifier verifier; + + /** + * Creates a new instance of AuthzeroTokenResolver with the provided + * configurations. + * + * @param jtiCreator the 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 + */ + public AuthzeroTokenResolver(GuidCreator jtiCreator, TokenAlgorithm algorithm, String issuer, String secret) { + if (secret == null || secret.isBlank()) { + throw new IllegalArgumentException("A secret is required to build a JSON Web Token."); + } + this.jtiCreator = jtiCreator; + this.algorithm = AuthzeroTokenResolverConfig.getInstance().getFunction(algorithm).apply(secret); + this.issuer = issuer; + this.verifier = JWT.require(this.algorithm).build(); + } + + /** + * Creates a new instance of 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 + */ + public AuthzeroTokenResolver(TokenAlgorithm algorithm, String issuer, String secret) { + this((GuidCreator) UUID::randomUUID, algorithm, issuer, secret); + } + + /** + * Creates a new instance of AuthzeroTokenResolver with the provided + * configurations, HMAC256 algorithm and a simple UUID GuidCreator. + * + * @param issuer the issuer claim value to be included in JWT tokens + * @param secret the secret used for HMAC-based algorithms (HS256, + * HS384, HS512) for token signing and verification + */ + public AuthzeroTokenResolver(String issuer, String secret) { + this(TokenAlgorithm.HS256, issuer, secret); + } + + /** + * Builds the basic information of the JSON Web Token (JWT) using the + * provided parameters and adds it to the JWTCreator.Builder. + * + * @param subject the subject claim value to be included in the JWT + * @param audience an array of audience claim values to be included in + * the JWT + * @param expireAfter the duration after which the JWT will expire + * @param builder the JWTCreator.Builder instance to which the basic + * information will be added + */ + private void buildBasicInfo(JWTCreator.Builder builder, Duration expireAfter, String subject, String... audience) { + var now = LocalDateTime.now(); + + // bind issuer (iss) + builder.withIssuer(issuer); + // bind issued at (iat) + builder.withIssuedAt(Date.from(now.atZone(ZoneId.systemDefault()).toInstant())); + // bind not before (nbf) + builder.withNotBefore(Date.from(now.atZone(ZoneId.systemDefault()).toInstant())); + // bind audience (aud) + builder.withAudience(audience); + // bind subject (sub) + builder.withSubject(subject); + // bind expire at (exp) + builder.withExpiresAt(Date.from(now.plus(expireAfter).atZone(ZoneId.systemDefault()).toInstant())); + // bind JWT Id (jti) + builder.withJWTId(jtiCreator.nextId().toString()); + } + + /** + * Add a claim to a builder. + * + * @param builder the builder to build this JSON Web Token + * @param name the property name + * @param value the property value + */ + private void addClaim(JWTCreator.Builder builder, String name, Object value) { + if (Objects.nonNull(value)) { + if (value instanceof Boolean v) { + builder.withClaim(name, v); + } else if (value instanceof Double v) { + builder.withClaim(name, v); + } else if (value instanceof Float v) { + builder.withClaim(name, v.doubleValue()); + } else if (value instanceof Integer v) { + builder.withClaim(name, v); + } else if (value instanceof Long v) { + builder.withClaim(name, v); + } else if (value instanceof String v) { + builder.withClaim(name, v); + } else if (value instanceof Date v) { + builder.withClaim(name, v); + } else if (value instanceof List v) { + builder.withClaim(name, v); + } else { + log.warn(""" + Unable to determine the type of field {}, converting it to a string now. + """, name); + builder.withClaim(name, value.toString()); + } + } else { + builder.withNullClaim(name); + } + } + + /** + * Builds the custom claims of the JSON Web Token (JWT) using the provided + * Map of claims and adds them to the JWTCreator.Builder. + *

+ * This method is used to add custom claims to the JWT. It takes a Map of + * claims, where each entry represents a custom claim name (key) and its + * corresponding value (value). The custom claims will be added to the JWT + * using the JWTCreator.Builder. + * + * @param claims a Map containing the custom claims to be added to the JWT + * @param builder the JWTCreator.Builder instance to which the custom + * claims will be added + */ + private void buildMapClaims(JWTCreator.Builder builder, Map claims) { + if (Objects.nonNull(claims)) { + for (var e : claims.entrySet()) { + addClaim(builder, e.getKey(), e.getValue()); + } + } + } + + /** + * Finish creating a token. + *

+ * This is the final step of create a token, to sign this token. + * + * @param builder the builder to build this JWT + * @return the generated token as a {@code String} + */ + private String buildToken(JWTCreator.Builder builder) { + return builder.sign(algorithm); + } + + /** + * Creates a new token with the specified expiration time, subject, and + * audience. + * + * @param expireAfter the duration after which the token will expire + * @param subject the subject of the token + * @param audience the audience for which the token is intended + * @return the generated token as a {@code String} + */ + @Override + public String createToken(Duration expireAfter, String audience, String subject) { + final var builder = JWT.create(); + buildBasicInfo(builder, expireAfter, subject, audience); + return buildToken(builder); + } + + /** + * Creates a new token with the specified expiration time, subject, + * audience, and custom payload data. + * + * @param expireAfter the duration after which the token will expire + * @param subject the subject of the token + * @param audience the audience for which the token is intended + * @param payloads the custom payload data to be included in the token + * @return the generated token as a {@code String} + */ + @Override + public String createToken(Duration expireAfter, String audience, String subject, Map payloads) { + // Create token. + final var builder = JWT.create(); + buildBasicInfo(builder, expireAfter, subject, audience); + buildMapClaims(builder, payloads); + return buildToken(builder); + } + + /** + * Creates a new token with the specified expiration time, subject, + * audience, and strongly-typed payload data. + * + * @param expireAfter the duration after which the token will expire + * @param subject the subject of the token + * @param audience the audience for which the token is intended + * @param payload the strongly-typed payload data to be included in the + * token + * @return the generated token as a {@code String} + */ + @Override + public String createToken(Duration expireAfter, String audience, String subject, T payload) { + final JWTCreator.Builder builder = JWT.create(); + buildBasicInfo(builder, expireAfter, subject, audience); + + var payloadClass = payload.getClass(); + 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); + // Build Claims + addClaim(builder, field.getName(), field.get(payload)); + } catch (IllegalAccessException e) { + log.error("Cannot access field %s!".formatted(field.getName())); + } + } + + return buildToken(builder); + } + + /** + * Resolves the given token into a DecodedJWT object. + * + * @param token the token to be resolved + * @return a ResolvedToken object + */ + @Override + public DecodedJWT resolve(String token) { + return verifier.verify(token); + } + + /** + * Extracts the payload information from the given token and maps it to the + * specified target type. + * + * @param token the token from which to extract the payload + * @param targetType the target class representing the payload data type + * @return an instance of the specified target type with the extracted + * payload data, or {@code null} when extraction fails + */ + @Override + public T extract(String token, Class targetType) { + // Get claims from token. + var claims = resolve(token).getClaims(); + + try { + // Get the no-argument constructor to create an instance. + T bean = targetType.getConstructor().newInstance(); + + var fields = targetType.getDeclaredFields(); + for (var field : fields) { + // Ignore the field annotated with @ExcludeFromPayload. + if (field.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); + } + } + + return bean; + } catch (NoSuchMethodException e) { + log.error("Unable to find a no-argument constructor declaration for class %s.".formatted(targetType.getCanonicalName())); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + log.error("Unable to create a new instance of class %s.".formatted(targetType.getCanonicalName())); + } + return null; + } + + /** + * Renews the given expired token with the specified custom payload data. + * + * @param oldToken the expired token to be renewed + * @param payload the custom payload data to be included in the renewed + * token + * @return the renewed token as a {@code String} + */ + @Override + public String renew(String oldToken, Duration expireAfter, Map payload) { + final var resolvedToken = this.resolve(oldToken); + var audience = resolvedToken.getAudience().get(0); + + return createToken(expireAfter, audience, resolvedToken.getSubject(), payload); + } + + /** + * Renews the given expired token with the specified custom payload data. + * + * @param oldToken the expired token to be renewed + * @param payload the custom payload data to be included in the renewed + * token + * @return the renewed token as a {@code String} + */ + @Override + public String renew(String oldToken, Map payload) { + return renew(oldToken, Duration.ofMinutes(30), payload); + } + + /** + * Renews the given expired token with the new specified strongly-typed + * payload data. + * + * @param oldToken the expired token to be renewed + * @param payload the strongly-typed payload data to be included in the + * renewed token + * @return the renewed token as a {@code String} + */ + @Override + public String renew(String oldToken, Duration expireAfter, T payload) { + final var resolvedToken = this.resolve(oldToken); + var audience = resolvedToken.getAudience().get(0); + + return createToken(expireAfter, audience, resolvedToken.getSubject(), payload); + } + + /** + * Renews the given expired token with the new specified strongly-typed + * payload data. + * + * @param the type of the payload data, must implement + * {@link TokenPayload} + * @param oldToken the expired token to be renewed + * @param payload the strongly-typed payload data to be included in the + * renewed token + * @return the renewed token as a {@code String} + */ + @Override + public String renew(String oldToken, T payload) { + return renew(oldToken, Duration.ofMinutes(30), payload); + } +} diff --git a/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java b/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java new file mode 100644 index 0000000..e78ee2c --- /dev/null +++ b/simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/config/AuthzeroTokenResolverConfig.java @@ -0,0 +1,139 @@ +/* + * 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.authzero.config; + +import cn.org.codecrafters.simplejwt.config.TokenResolverConfig; +import cn.org.codecrafters.simplejwt.constants.TokenAlgorithm; +import cn.org.codecrafters.simplejwt.exceptions.UnsupportedAlgorithmException; +import com.auth0.jwt.algorithms.Algorithm; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +/** + * The AuthzeroTokenResolverConfig class provides the configuration for the + * AuthzeroTokenResolver. + *

+ * This configuration class is used to establish the mapping between the + * standard TokenAlgorithm defined within the AuthzeroTokenResolver facade and + * the specific algorithms used by the Auth0 Java JWT library, which is the + * underlying library used by AuthzeroTokenResolver to handle JSON Web Tokens + * (JWTs). + * + *

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

+ * Note: + * The provided algorithm mapping should be consistent with the actual + * algorithms supported and used by the Auth0 Java JWT library. It is crucial + * to ensure that the mapping is accurate to enable proper token validation + * and processing within the AuthzeroTokenResolver. + * + * @author Zihlu Wang + * @version 1.0.0 + * @since 1.0.0 + */ +public final class AuthzeroTokenResolverConfig implements TokenResolverConfig> { + + /** + * Constructs a new instance of AuthzeroTokenResolverConfig. + *

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

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

+ * This map stores the supported algorithms as keys and their corresponding + * algorithm functions as values. The algorithm functions represent the + * functions used by the Auth0 Java JWT library to handle the specific + * algorithms. The mapping is used to provide proper algorithm resolution + * and processing within the AuthzeroTokenResolver. + */ + private static final Map> SUPPORTED_ALGORITHMS = new HashMap<>() {{ + put(TokenAlgorithm.HS256, Algorithm::HMAC256); + put(TokenAlgorithm.HS384, Algorithm::HMAC384); + put(TokenAlgorithm.HS512, Algorithm::HMAC512); + }}; + + /** + * Gets the instance of AuthzeroTokenResolverConfig. + *

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

+ * This method returns the algorithm function associated with the given + * TokenAlgorithm. The provided TokenAlgorithm represents the specific + * algorithm for which the corresponding algorithm function is required. + * The returned AlgorithmFunction represents the function implementation + * that can be used by the TokenResolver to handle the specific algorithm. + * + * @param algorithm the TokenAlgorithm for which the algorithm function is + * required + * @return the algorithm function associated with the given TokenAlgorithm + * @throws UnsupportedAlgorithmException if the given {@code algorithm} is + * not supported by this + * implementation + */ + @Override + public Function getFunction(TokenAlgorithm algorithm) { + return Optional.of(SUPPORTED_ALGORITHMS).map((entry) -> entry.get(algorithm)) + .orElseThrow(() -> new UnsupportedAlgorithmException("The specified algorithm is not supported yet.")); + } +} diff --git a/simple-jwt-authzero/src/main/resources/logback.xml b/simple-jwt-authzero/src/main/resources/logback.xml new file mode 100644 index 0000000..af1c7a3 --- /dev/null +++ b/simple-jwt-authzero/src/main/resources/logback.xml @@ -0,0 +1,38 @@ + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} : %msg%n + + + + + + %date{yyyy-MM-dd HH:mm:ss} [%thread] %highlight(%-5level) %cyan(%logger{50}) : %msg%n + + + + + + + \ No newline at end of file diff --git a/simple-jwt-authzero/src/test/java/cn/org/codecrafters/simplejwt/authzero/test/TestAuthzeroTokenResolver.java b/simple-jwt-authzero/src/test/java/cn/org/codecrafters/simplejwt/authzero/test/TestAuthzeroTokenResolver.java new file mode 100644 index 0000000..52e2f18 --- /dev/null +++ b/simple-jwt-authzero/src/test/java/cn/org/codecrafters/simplejwt/authzero/test/TestAuthzeroTokenResolver.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.authzero.test; + +import cn.org.codecrafters.simplejwt.authzero.AuthzeroTokenResolver; +import cn.org.codecrafters.simplejwt.constants.TokenAlgorithm; +import org.junit.jupiter.api.Test; + +import java.time.Duration; + +/** + * TestAuthzeroTokenResolver + * + * @author Zihlu Wang + */ +public class TestAuthzeroTokenResolver { + + @Test + public void test01() { + var tokenResolver = new AuthzeroTokenResolver(TokenAlgorithm.HS384, "Test Issuer", "Test Secret"); + var testToken = tokenResolver.createToken(Duration.ofMinutes(30), "Test Audience", "User00001"); + System.out.println(testToken); + } + +}