From 6557b65d8a61f83ad590093708fafef4ce5348fe Mon Sep 17 00:00:00 2001 From: Zihlu Wang Date: Fri, 4 Aug 2023 19:45:23 +0800 Subject: [PATCH] feat(simple-jwt-jjwt): Complete the implementation with io.jsonwebtoken:jjwt-api --- pom.xml | 47 +++ .../authzero/AuthzeroTokenResolver.java | 11 +- .../codecrafters/simplejwt/TokenResolver.java | 27 +- simple-jwt-jjwt/pom.xml | 69 ++++ .../simplejwt/jjwt/JjwtTokenResolver.java | 313 ++++++++++++++++++ .../jjwt/config/JjwtTokenResolverConfig.java | 76 +++++ simple-jwt-spring-boot-starter/pom.xml | 30 ++ ...thzeroTokenResolverAutoConfiguration.java} | 22 +- .../JjwtTokenResolverAutoConfiguration.java | 111 +++++++ .../properties/SimpleJwtProperties.java | 20 +- ...ot.autoconfigure.AutoConfiguration.imports | 3 +- 11 files changed, 693 insertions(+), 36 deletions(-) create mode 100644 simple-jwt-jjwt/pom.xml create mode 100644 simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java create mode 100644 simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/config/JjwtTokenResolverConfig.java rename simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/{SimpleJwtAutoConfiguration.java => AuthzeroTokenResolverAutoConfiguration.java} (85%) create mode 100644 simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/JjwtTokenResolverAutoConfiguration.java diff --git a/pom.xml b/pom.xml index 3cebc71..9c1141b 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,7 @@ guid simple-jwt-facade simple-jwt-authzero + simple-jwt-jjwt simple-jwt-spring-boot-starter @@ -70,7 +71,9 @@ 5.10.0 2.0.7 1.18.28 + 2.15.2 4.4.0 + 0.11.5 x.x.x 3.1.0 @@ -103,12 +106,44 @@ ${lombok.version} + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + com.auth0 java-jwt ${auth0-jwt.version} + + io.jsonwebtoken + jjwt-api + ${jjwt-jwt.version} + + + + io.jsonwebtoken + jjwt-impl + ${jjwt-jwt.version} + runtime + + + + io.jsonwebtoken + jjwt-jackson + ${jjwt-jwt.version} + runtime + + + com.fasterxml.jackson.core + jackson-databind + + + + cn.org.codecrafters devkit-core @@ -121,6 +156,12 @@ ${project.version} + + cn.org.codecrafters + devkit-utils + ${project.version} + + cn.org.codecrafters simple-jwt-facade @@ -133,6 +174,12 @@ ${project.version} + + cn.org.codecrafters + simple-jwt-jjwt + ${project.version} + + org.springframework.boot 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 7c963cf..f7ebe31 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.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.config.TokenResolverConfig; import cn.org.codecrafters.simplejwt.constants.TokenAlgorithm; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTCreator; @@ -124,6 +125,8 @@ public class AuthzeroTokenResolver implements TokenResolver { */ private final JWTVerifier verifier; + private final AuthzeroTokenResolverConfig config = AuthzeroTokenResolverConfig.getInstance(); + /** * Creates a new instance of AuthzeroTokenResolver with the provided * configurations. @@ -146,7 +149,7 @@ public class AuthzeroTokenResolver implements TokenResolver { } this.jtiCreator = jtiCreator; - this.algorithm = AuthzeroTokenResolverConfig.getInstance() + this.algorithm = config .getAlgorithm(algorithm) .apply(secret); this.issuer = issuer; @@ -172,7 +175,7 @@ public class AuthzeroTokenResolver implements TokenResolver { } this.jtiCreator = (GuidCreator) UUID::randomUUID; - this.algorithm = AuthzeroTokenResolverConfig.getInstance() + this.algorithm = config .getAlgorithm(algorithm) .apply(secret); this.issuer = issuer; @@ -197,7 +200,7 @@ public class AuthzeroTokenResolver implements TokenResolver { } this.jtiCreator = (GuidCreator) UUID::randomUUID; - this.algorithm = AuthzeroTokenResolverConfig.getInstance() + this.algorithm = config .getAlgorithm(TokenAlgorithm.HS256) .apply(secret); this.issuer = issuer; @@ -214,7 +217,7 @@ public class AuthzeroTokenResolver implements TokenResolver { var secret = SecretCreator.createSecret(32, true, true, true); this.jtiCreator = (GuidCreator) UUID::randomUUID; - this.algorithm = AuthzeroTokenResolverConfig.getInstance() + this.algorithm = config .getAlgorithm(TokenAlgorithm.HS256) .apply(secret); this.issuer = issuer; 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 a4c9ff0..65189d3 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 @@ -17,6 +17,7 @@ package cn.org.codecrafters.simplejwt; +import java.lang.reflect.InvocationTargetException; import java.time.Duration; import java.util.Map; @@ -25,7 +26,7 @@ import java.util.Map; * The {@code TokenResolver} interface defines methods for creating, * extracting, and renewing tokens, particularly JSON Web Tokens (JWTs). It * provides a set of methods to generate tokens with various payload - * configurations, extract payloads from tokens, and renew expired tokens. + * configurations, extract payload from tokens, and renew expired tokens. * *

* Token Creation: @@ -72,10 +73,10 @@ public interface TokenResolver { * @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 + * @param payload the custom payload data to be included in the token * @return the generated token as a {@code String} */ - String createToken(Duration expireAfter, String audience, String subject, Map payloads); + String createToken(Duration expireAfter, String audience, String subject, Map payload); /** * Creates a new token with the specified expiration time, subject, @@ -113,16 +114,6 @@ public interface TokenResolver { */ T extract(String token, Class targetType); - /** - * 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} - */ - String renew(String oldToken, Map payload); - /** * Renews the given expired token with the specified custom payload data. * @@ -134,6 +125,16 @@ public interface TokenResolver { */ String renew(String oldToken, Duration expireAfter, Map 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} + */ + String renew(String oldToken, Map payload); + /** * Renews the given expired token with the specified strongly-typed * payload data. diff --git a/simple-jwt-jjwt/pom.xml b/simple-jwt-jjwt/pom.xml new file mode 100644 index 0000000..85c66e5 --- /dev/null +++ b/simple-jwt-jjwt/pom.xml @@ -0,0 +1,69 @@ + + + + + 4.0.0 + + cn.org.codecrafters + jdevkit + 1.0.0 + + + simple-jwt-jjwt + + + 17 + 17 + UTF-8 + + + + + cn.org.codecrafters + devkit-utils + + + + com.fasterxml.jackson.core + jackson-databind + + + + io.jsonwebtoken + jjwt-api + + + + io.jsonwebtoken + jjwt-impl + + + + io.jsonwebtoken + jjwt-jackson + + + + cn.org.codecrafters + simple-jwt-facade + + + + \ No newline at end of file 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 new file mode 100644 index 0000000..f38c287 --- /dev/null +++ b/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java @@ -0,0 +1,313 @@ +/* + * 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.jjwt; + +import cn.org.codecrafters.devkit.utils.MapUtil; +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.constants.TokenAlgorithm; +import cn.org.codecrafters.simplejwt.exceptions.WeakSecretException; +import cn.org.codecrafters.simplejwt.jjwt.config.JjwtTokenResolverConfig; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; +import java.util.Map; +import java.util.UUID; + +/** + * JjwtTokenResolver + * + * @author Zihlu Wang + */ +@Slf4j +public class JjwtTokenResolver implements TokenResolver> { + + private final GuidCreator jtiCreator; + + private final SignatureAlgorithm algorithm; + + private final String issuer; + + private final Key key; + + private final JjwtTokenResolverConfig config = JjwtTokenResolverConfig.getInstance(); + + public JjwtTokenResolver(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."); + } + + if (secret.length() <= 32) { + log.error(""" + The provided secret which owns {} characters is too weak. Please replace it with a stronger one. + """, secret.length()); + throw new WeakSecretException(""" + The provided secret which owns %s characters is too weak. Please replace it with a stronger one. + """.formatted(secret.length())); + } + + this.jtiCreator = jtiCreator; + this.algorithm = config.getAlgorithm(algorithm); + this.issuer = issuer; + this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + } + + public JjwtTokenResolver(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.error(""" + The provided secret which owns {} characters is too weak. Please replace it with a stronger one. + """, secret.length()); + throw new WeakSecretException(""" + The provided secret which owns %s characters is too weak. Please replace it with a stronger one. + """.formatted(secret.length())); + } + + this.jtiCreator = UUID::randomUUID; + this.algorithm = config.getAlgorithm(algorithm); + this.issuer = issuer; + this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + } + + public JjwtTokenResolver(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.error(""" + The provided secret which owns {} characters is too weak. Please replace it with a stronger one. + """, secret.length()); + throw new WeakSecretException(""" + The provided secret which owns %s characters is too weak. Please replace it with a stronger one. + """.formatted(secret.length())); + } + + this.jtiCreator = UUID::randomUUID; + this.algorithm = config.getAlgorithm(TokenAlgorithm.HS256); + this.issuer = issuer; + this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + } + + public JjwtTokenResolver(String issuer) { + this.jtiCreator = UUID::randomUUID; + this.algorithm = config.getAlgorithm(TokenAlgorithm.HS256); + this.issuer = issuer; + this.key = Keys.hmacShaKeyFor(SecretCreator.createSecret(32, true, true, true).getBytes(StandardCharsets.UTF_8)); + } + + private String buildToken(Duration expireAfter, String audience, String subject, LocalDateTime now, Map claims) { + var builder = Jwts.builder() + .setHeaderParam("typ", "JWT") + .setIssuedAt(Date.from(now.atZone(ZoneId.systemDefault()).toInstant())) + .setNotBefore(Date.from(now.atZone(ZoneId.systemDefault()).toInstant())) + .setExpiration(Date.from(now.plus(expireAfter).atZone(ZoneId.systemDefault()).toInstant())) + .setSubject(subject) + .setAudience(audience) + .setIssuer(this.issuer) + .setId(jtiCreator.nextId().toString()); + + if (claims != null && !claims.isEmpty()) { + builder.setClaims(claims); + } + + return builder.signWith(key, algorithm) + .compact(); + } + + /** + * Creates a new token with the specified expiration time, subject, and + * audience. + * + * @param expireAfter the duration after which the token will expire + * @param audience the audience for which the token is intended + * @param subject the subject of the token + * @return the generated token as a {@code String} + */ + @Override + public String createToken(Duration expireAfter, String audience, String subject) { + var now = LocalDateTime.now(); + return buildToken(expireAfter, audience, subject, now, null); + } + + /** + * 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 audience the audience for which the token is intended + * @param subject the subject of the token + * @param payload 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 payload) { + var now = LocalDateTime.now(); + return buildToken(expireAfter, audience, subject, now, payload); + } + + /** + * 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 audience the audience for which the token is intended + * @param subject the subject of the token + * @param payload the strongly-typed payload data to be included in the + * token + * @return the generated token as a {@code String} or {@code null} if + * creation fails + * @see MapUtil#objectToMap(Object) + */ + @Override + public String createToken(Duration expireAfter, String audience, String subject, T payload) { + var now = LocalDateTime.now(); + try { + var claims = MapUtil.objectToMap(payload); + return buildToken(expireAfter, audience, subject, now, claims); + } catch (IllegalAccessException e) { + log.error("An error occurs while accessing the fields of the object"); + } + + return null; + } + + /** + * Resolves the given token into a ResolvedTokenType object. + * + * @param token the token to be resolved + * @return a ResolvedTokenType object + */ + @Override + public Jws resolve(String token) { + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(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} if extraction fails. + * @see MapUtil#mapToObject(Map, Class) + */ + @Override + public T extract(String token, Class targetType) { + var resolvedToken = resolve(token); + + var claims = resolvedToken.getBody(); + try { + return MapUtil.mapToObject(claims, targetType); + } catch (InvocationTargetException e) { + log.info("An error occurs while invoking the constructor of type {}.", targetType.getCanonicalName()); + } catch (NoSuchMethodException e) { + log.error("The constructor of the required type {} is not found.", targetType.getCanonicalName()); + } 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."); + } + + return null; + } + + /** + * Renews the given expired token with the specified custom payload data. + * + * @param oldToken the expired token to be renewed + * @param expireAfter specify when does the new token invalid + * @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) { + var resolvedTokenClaims = resolve(oldToken).getBody(); + var audience = resolvedTokenClaims.getAudience(); + var subject = resolvedTokenClaims.getSubject(); + + return createToken(expireAfter, audience, subject, 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 specified strongly-typed + * payload data. + * + * @param oldToken the expired token to be renewed + * @param expireAfter specify when does the new token invalid + * @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) { + var resolvedTokenClaims = resolve(oldToken).getBody(); + var audience = resolvedTokenClaims.getAudience(); + var subject = resolvedTokenClaims.getSubject(); + + return createToken(expireAfter, audience, subject, payload); + } + + /** + * Renews the given expired token with the 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, T payload) { + return renew(oldToken, Duration.ofMinutes(30), payload); + } +} diff --git a/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/config/JjwtTokenResolverConfig.java b/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/config/JjwtTokenResolverConfig.java new file mode 100644 index 0000000..c0e8aa2 --- /dev/null +++ b/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/config/JjwtTokenResolverConfig.java @@ -0,0 +1,76 @@ +/* + * 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.jjwt.config; + +import cn.org.codecrafters.simplejwt.config.TokenResolverConfig; +import cn.org.codecrafters.simplejwt.constants.TokenAlgorithm; +import cn.org.codecrafters.simplejwt.exceptions.UnsupportedAlgorithmException; +import io.jsonwebtoken.SignatureAlgorithm; + +import java.util.HashMap; +import java.util.Map; + +/** + * JjwtTokenResolverConfig + * + * @author Zihlu Wang + */ +public final class JjwtTokenResolverConfig implements TokenResolverConfig { + + private JjwtTokenResolverConfig() {} + + private static final Map SUPPORTED_ALGORITHMS = new HashMap<>() {{ + put(TokenAlgorithm.HS256, SignatureAlgorithm.HS256); + put(TokenAlgorithm.HS384, SignatureAlgorithm.HS384); + put(TokenAlgorithm.HS512, SignatureAlgorithm.HS512); + }}; + + private static JjwtTokenResolverConfig instance; + + public static JjwtTokenResolverConfig getInstance() { + if (instance == null) { + instance = new JjwtTokenResolverConfig(); + } + + 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 + */ + @Override + public SignatureAlgorithm getAlgorithm(TokenAlgorithm algorithm) { + if (!SUPPORTED_ALGORITHMS.containsKey(algorithm)) { + throw new UnsupportedAlgorithmException(""" + The request algorithm is not supported by our system yet. Please change to supported ones. + """); + } + return SUPPORTED_ALGORITHMS.get(algorithm); + } +} diff --git a/simple-jwt-spring-boot-starter/pom.xml b/simple-jwt-spring-boot-starter/pom.xml index a4f73a8..95cd204 100644 --- a/simple-jwt-spring-boot-starter/pom.xml +++ b/simple-jwt-spring-boot-starter/pom.xml @@ -40,12 +40,42 @@ simple-jwt-facade + + com.auth0 + java-jwt + provided + + cn.org.codecrafters simple-jwt-authzero provided + + io.jsonwebtoken + jjwt-api + provided + + + + io.jsonwebtoken + jjwt-impl + provided + + + + io.jsonwebtoken + jjwt-jackson + provided + + + + cn.org.codecrafters + simple-jwt-jjwt + provided + + org.springframework.boot spring-boot-autoconfigure diff --git a/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/SimpleJwtAutoConfiguration.java b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java similarity index 85% rename from simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/SimpleJwtAutoConfiguration.java rename to simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java index 1be5b2a..f08ef03 100644 --- a/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/SimpleJwtAutoConfiguration.java +++ b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java @@ -34,17 +34,15 @@ import org.springframework.context.annotation.Bean; /** *

* SimpleJwtAutoConfiguration is responsible for automatically configuring the - * Simple JWT library when used in a Spring Boot application. It provides - * default settings and configurations to ensure that the library works - * smoothly without requiring manual configuration. - * + * Simple JWT library with {@code com.auth0:java-jwt} when used in a Spring + * Boot application. It provides default settings and configurations to ensure + * that the library works smoothly without requiring manual configuration. * *

* This auto-configuration class sets up the necessary beans and components * required for JWT generation and validation. It automatically creates and * configures the {@link TokenResolver} bean based on the available options and * properties. - * * *

* Developers using the Simple JWT library with Spring Boot do not need to @@ -61,7 +59,9 @@ import org.springframework.context.annotation.Bean; @Slf4j @AutoConfiguration @EnableConfigurationProperties(value = {SimpleJwtProperties.class}) -public class SimpleJwtAutoConfiguration { +@ConditionalOnClass({DecodedJWT.class, AuthzeroTokenResolver.class}) +@ConditionalOnMissingBean({TokenResolver.class}) +public class AuthzeroTokenResolverAutoConfiguration { /** * The GuidCreator instance to be used for generating JWT IDs (JTI). @@ -81,7 +81,7 @@ public class SimpleJwtAutoConfiguration { * @param simpleJwtProperties the SimpleJwtProperties instance */ @Autowired - public SimpleJwtAutoConfiguration(SimpleJwtProperties simpleJwtProperties, GuidCreator jtiCreator) { + public AuthzeroTokenResolverAutoConfiguration(SimpleJwtProperties simpleJwtProperties, GuidCreator jtiCreator) { this.jtiCreator = jtiCreator; this.simpleJwtProperties = simpleJwtProperties; } @@ -96,14 +96,14 @@ public class SimpleJwtAutoConfiguration { * @return the {@link TokenResolver} instance */ @Bean - @ConditionalOnClass({DecodedJWT.class, AuthzeroTokenResolver.class}) - @ConditionalOnMissingBean({TokenResolver.class}) @ConditionalOnBean(value = {GuidCreator.class}, name = "jtiCreator") public TokenResolver tokenResolver() { - return new AuthzeroTokenResolver(this.jtiCreator, + return new AuthzeroTokenResolver( + jtiCreator, simpleJwtProperties.algorithm(), simpleJwtProperties.issuer(), - simpleJwtProperties.secret()); + simpleJwtProperties.secret() + ); } } diff --git a/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/JjwtTokenResolverAutoConfiguration.java b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/JjwtTokenResolverAutoConfiguration.java new file mode 100644 index 0000000..f0fbd12 --- /dev/null +++ b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/JjwtTokenResolverAutoConfiguration.java @@ -0,0 +1,111 @@ +/* + * 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.autoconfiguration; + +import cn.org.codecrafters.guid.GuidCreator; +import cn.org.codecrafters.simplejwt.TokenResolver; +import cn.org.codecrafters.simplejwt.authzero.AuthzeroTokenResolver; +import cn.org.codecrafters.simplejwt.autoconfiguration.properties.SimpleJwtProperties; +import cn.org.codecrafters.simplejwt.jjwt.JjwtTokenResolver; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +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; + +/** + *

+ * JjwtTokenResolverAutoConfiguration is responsible for automatically + * configuring the Simple JWT library with {@code io.jsonwebtoken:jjwt-api} + * when used in a Spring Boot application. It provides default settings and + * configurations to ensure that the library works smoothly without requiring + * manual configuration. + * + *

+ * This auto-configuration class sets up the necessary beans and components + * required for JWT generation and validation. It automatically creates and + * configures the {@link TokenResolver} bean based on the available options and + * properties. + * + *

+ * Developers using the Simple JWT library with Spring Boot do not need to + * explicitly configure the library, as the auto-configuration takes care of + * setting up the necessary components and configurations automatically. + * However, developers still have the flexibility to customize the behavior of + * the library by providing their own configurations and properties. + * + * @author Zihlu Wang + * @version 1.0.0 + * @since 1.0.0 + */ +@Slf4j +@AutoConfiguration +@EnableConfigurationProperties(value = {SimpleJwtProperties.class}) +@ConditionalOnClass({Jws.class, Claims.class, JjwtTokenResolver.class}) +@ConditionalOnMissingBean({TokenResolver.class}) +public class JjwtTokenResolverAutoConfiguration { + + /** + * The GuidCreator instance to be used for generating JWT IDs (JTI). + */ + private final GuidCreator jtiCreator; + + /** + * The {@code SimpleJwtProperties} instance containing the configuration + * properties for Simple JWT. + */ + private final SimpleJwtProperties simpleJwtProperties; + + /** + * Constructs a new {@code SimpleJwtAutoConfiguration} instance with the + * provided SimpleJwtProperties. + * + * @param simpleJwtProperties the SimpleJwtProperties instance + */ + @Autowired + public JjwtTokenResolverAutoConfiguration(SimpleJwtProperties simpleJwtProperties, GuidCreator jtiCreator) { + this.jtiCreator = jtiCreator; + this.simpleJwtProperties = simpleJwtProperties; + } + + /** + * Creates a new {@link TokenResolver} bean using {@link + * JjwtTokenResolver} if no existing {@link TokenResolver} bean is + * found. The {@link JjwtTokenResolver} is configured with the + * provided {@link GuidCreator}, {@code algorithm}, {@code issuer}, and + * {@code secret} properties from {@link SimpleJwtProperties}. + * + * @return the {@link TokenResolver} instance + */ + @Bean + @ConditionalOnBean(value = {GuidCreator.class}, name = "jtiCreator") + public TokenResolver> tokenResolver() { + return new JjwtTokenResolver( + jtiCreator, + simpleJwtProperties.algorithm(), + simpleJwtProperties.issuer(), + simpleJwtProperties.secret() + ); + } + +} diff --git a/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/properties/SimpleJwtProperties.java b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/properties/SimpleJwtProperties.java index 4ae153c..e176893 100644 --- a/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/properties/SimpleJwtProperties.java +++ b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/properties/SimpleJwtProperties.java @@ -17,6 +17,8 @@ package cn.org.codecrafters.simplejwt.autoconfiguration.properties; +import cn.org.codecrafters.simplejwt.SecretCreator; +import cn.org.codecrafters.simplejwt.autoconfiguration.AuthzeroTokenResolverAutoConfiguration; import cn.org.codecrafters.simplejwt.constants.TokenAlgorithm; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -32,7 +34,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; *

* SimpleJwtProperties provides configuration options for the JWT algorithm, * issuer, and secret. The properties are used by the {@link - * cn.org.codecrafters.simplejwt.autoconfiguration.SimpleJwtAutoConfiguration} + * AuthzeroTokenResolverAutoConfiguration} * class to set up the necessary configurations for JWT generation and * validation. * @@ -51,19 +53,23 @@ import org.springframework.boot.context.properties.ConfigurationProperties; public class SimpleJwtProperties { /** - * The algorithm used for JWT generation and validation. + * The algorithm used for JWT generation and validation. Default value is + * {@link TokenAlgorithm#HS256} */ - private TokenAlgorithm algorithm; + private TokenAlgorithm algorithm = TokenAlgorithm.HS256; /** - * The issuer value to be included in the generated JWT. + * The issuer value to be included in the generated JWT. Default value is + * an empty String. */ - private String issuer; + private String issuer = ""; /** - * The secret key used for JWT generation and validation. + * The secret key used for JWT generation and validation. Default value is + * the result of call to {@link + * SecretCreator#createSecret(int, boolean, boolean, boolean)}. */ - private String secret; + private String secret = SecretCreator.createSecret(32, true, true, true); /** * Returns the JWT algorithm configured in the properties. diff --git a/simple-jwt-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/simple-jwt-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 44e131c..5e98bde 100644 --- a/simple-jwt-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/simple-jwt-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,2 +1,3 @@ cn.org.codecrafters.simplejwt.autoconfiguration.GuidAutoConfiguration -cn.org.codecrafters.simplejwt.autoconfiguration.SimpleJwtAutoConfiguration \ No newline at end of file +cn.org.codecrafters.simplejwt.autoconfiguration.AuthzeroTokenResolverAutoConfiguration +cn.org.codecrafters.simplejwt.autoconfiguration.JjwtTokenResolverAutoConfiguration \ No newline at end of file