From da04fb4c5587f015a415728e53250889f4fc1528 Mon Sep 17 00:00:00 2001 From: Zihlu Wang Date: Tue, 17 Oct 2023 10:25:24 +0800 Subject: [PATCH] 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 ); }