feat(simple-jwt-jjwt): Complete the implementation with io.jsonwebtoken:jjwt-api

This commit is contained in:
Zihlu Wang
2023-08-04 19:45:23 +08:00
parent 9405908964
commit 6557b65d8a
11 changed files with 693 additions and 36 deletions
+47
View File
@@ -22,6 +22,7 @@
<module>guid</module> <module>guid</module>
<module>simple-jwt-facade</module> <module>simple-jwt-facade</module>
<module>simple-jwt-authzero</module> <module>simple-jwt-authzero</module>
<module>simple-jwt-jjwt</module>
<module>simple-jwt-spring-boot-starter</module> <module>simple-jwt-spring-boot-starter</module>
</modules> </modules>
@@ -70,7 +71,9 @@
<junit.version>5.10.0</junit.version> <junit.version>5.10.0</junit.version>
<slf4j-api.version>2.0.7</slf4j-api.version> <slf4j-api.version>2.0.7</slf4j-api.version>
<lombok.version>1.18.28</lombok.version> <lombok.version>1.18.28</lombok.version>
<jackson.version>2.15.2</jackson.version>
<auth0-jwt.version>4.4.0</auth0-jwt.version> <auth0-jwt.version>4.4.0</auth0-jwt.version>
<jjwt-jwt.version>0.11.5</jjwt-jwt.version>
<spring.version>x.x.x</spring.version> <spring.version>x.x.x</spring.version>
<spring-boot.version>3.1.0</spring-boot.version> <spring-boot.version>3.1.0</spring-boot.version>
@@ -103,12 +106,44 @@
<version>${lombok.version}</version> <version>${lombok.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.auth0</groupId> <groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId> <artifactId>java-jwt</artifactId>
<version>${auth0-jwt.version}</version> <version>${auth0-jwt.version}</version>
</dependency> </dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt-jwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt-jwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt-jwt.version}</version>
<scope>runtime</scope>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency> <dependency>
<groupId>cn.org.codecrafters</groupId> <groupId>cn.org.codecrafters</groupId>
<artifactId>devkit-core</artifactId> <artifactId>devkit-core</artifactId>
@@ -121,6 +156,12 @@
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>cn.org.codecrafters</groupId>
<artifactId>devkit-utils</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>cn.org.codecrafters</groupId> <groupId>cn.org.codecrafters</groupId>
<artifactId>simple-jwt-facade</artifactId> <artifactId>simple-jwt-facade</artifactId>
@@ -133,6 +174,12 @@
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>cn.org.codecrafters</groupId>
<artifactId>simple-jwt-jjwt</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring --> <!-- Spring -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@@ -23,6 +23,7 @@ import cn.org.codecrafters.simplejwt.TokenPayload;
import cn.org.codecrafters.simplejwt.TokenResolver; import cn.org.codecrafters.simplejwt.TokenResolver;
import cn.org.codecrafters.simplejwt.annotations.ExcludeFromPayload; import cn.org.codecrafters.simplejwt.annotations.ExcludeFromPayload;
import cn.org.codecrafters.simplejwt.authzero.config.AuthzeroTokenResolverConfig; import cn.org.codecrafters.simplejwt.authzero.config.AuthzeroTokenResolverConfig;
import cn.org.codecrafters.simplejwt.config.TokenResolverConfig;
import cn.org.codecrafters.simplejwt.constants.TokenAlgorithm; import cn.org.codecrafters.simplejwt.constants.TokenAlgorithm;
import com.auth0.jwt.JWT; import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator; import com.auth0.jwt.JWTCreator;
@@ -124,6 +125,8 @@ public class AuthzeroTokenResolver implements TokenResolver<DecodedJWT> {
*/ */
private final JWTVerifier verifier; private final JWTVerifier verifier;
private final AuthzeroTokenResolverConfig config = AuthzeroTokenResolverConfig.getInstance();
/** /**
* Creates a new instance of AuthzeroTokenResolver with the provided * Creates a new instance of AuthzeroTokenResolver with the provided
* configurations. * configurations.
@@ -146,7 +149,7 @@ public class AuthzeroTokenResolver implements TokenResolver<DecodedJWT> {
} }
this.jtiCreator = jtiCreator; this.jtiCreator = jtiCreator;
this.algorithm = AuthzeroTokenResolverConfig.getInstance() this.algorithm = config
.getAlgorithm(algorithm) .getAlgorithm(algorithm)
.apply(secret); .apply(secret);
this.issuer = issuer; this.issuer = issuer;
@@ -172,7 +175,7 @@ public class AuthzeroTokenResolver implements TokenResolver<DecodedJWT> {
} }
this.jtiCreator = (GuidCreator<UUID>) UUID::randomUUID; this.jtiCreator = (GuidCreator<UUID>) UUID::randomUUID;
this.algorithm = AuthzeroTokenResolverConfig.getInstance() this.algorithm = config
.getAlgorithm(algorithm) .getAlgorithm(algorithm)
.apply(secret); .apply(secret);
this.issuer = issuer; this.issuer = issuer;
@@ -197,7 +200,7 @@ public class AuthzeroTokenResolver implements TokenResolver<DecodedJWT> {
} }
this.jtiCreator = (GuidCreator<UUID>) UUID::randomUUID; this.jtiCreator = (GuidCreator<UUID>) UUID::randomUUID;
this.algorithm = AuthzeroTokenResolverConfig.getInstance() this.algorithm = config
.getAlgorithm(TokenAlgorithm.HS256) .getAlgorithm(TokenAlgorithm.HS256)
.apply(secret); .apply(secret);
this.issuer = issuer; this.issuer = issuer;
@@ -214,7 +217,7 @@ public class AuthzeroTokenResolver implements TokenResolver<DecodedJWT> {
var secret = SecretCreator.createSecret(32, true, true, true); var secret = SecretCreator.createSecret(32, true, true, true);
this.jtiCreator = (GuidCreator<UUID>) UUID::randomUUID; this.jtiCreator = (GuidCreator<UUID>) UUID::randomUUID;
this.algorithm = AuthzeroTokenResolverConfig.getInstance() this.algorithm = config
.getAlgorithm(TokenAlgorithm.HS256) .getAlgorithm(TokenAlgorithm.HS256)
.apply(secret); .apply(secret);
this.issuer = issuer; this.issuer = issuer;
@@ -17,6 +17,7 @@
package cn.org.codecrafters.simplejwt; package cn.org.codecrafters.simplejwt;
import java.lang.reflect.InvocationTargetException;
import java.time.Duration; import java.time.Duration;
import java.util.Map; import java.util.Map;
@@ -25,7 +26,7 @@ import java.util.Map;
* The {@code TokenResolver} interface defines methods for creating, * The {@code TokenResolver} interface defines methods for creating,
* extracting, and renewing tokens, particularly JSON Web Tokens (JWTs). It * extracting, and renewing tokens, particularly JSON Web Tokens (JWTs). It
* provides a set of methods to generate tokens with various payload * 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.
* *
* <p> * <p>
* <b>Token Creation:</b> * <b>Token Creation:</b>
@@ -72,10 +73,10 @@ public interface TokenResolver<ResolvedTokenType> {
* @param expireAfter the duration after which the token will expire * @param expireAfter the duration after which the token will expire
* @param subject the subject of the token * @param subject the subject of the token
* @param audience the audience for which the token is intended * @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} * @return the generated token as a {@code String}
*/ */
String createToken(Duration expireAfter, String audience, String subject, Map<String, Object> payloads); String createToken(Duration expireAfter, String audience, String subject, Map<String, Object> payload);
/** /**
* Creates a new token with the specified expiration time, subject, * Creates a new token with the specified expiration time, subject,
@@ -113,16 +114,6 @@ public interface TokenResolver<ResolvedTokenType> {
*/ */
<T extends TokenPayload> T extract(String token, Class<T> targetType); <T extends TokenPayload> T extract(String token, Class<T> 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<String, Object> payload);
/** /**
* Renews the given expired token with the specified custom payload data. * Renews the given expired token with the specified custom payload data.
* *
@@ -134,6 +125,16 @@ public interface TokenResolver<ResolvedTokenType> {
*/ */
String renew(String oldToken, Duration expireAfter, Map<String, Object> payload); String renew(String oldToken, Duration expireAfter, Map<String, Object> 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<String, Object> payload);
/** /**
* Renews the given expired token with the specified strongly-typed * Renews the given expired token with the specified strongly-typed
* payload data. * payload data.
+69
View File
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.org.codecrafters</groupId>
<artifactId>jdevkit</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>simple-jwt-jjwt</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>cn.org.codecrafters</groupId>
<artifactId>devkit-utils</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
</dependency>
<dependency>
<groupId>cn.org.codecrafters</groupId>
<artifactId>simple-jwt-facade</artifactId>
</dependency>
</dependencies>
</project>
@@ -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<Jws<Claims>> {
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<String, Object> 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<String, Object> 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 <T extends TokenPayload> 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<Claims> 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 extends TokenPayload> T extract(String token, Class<T> 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<String, Object> 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<String, Object> 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 <T extends TokenPayload> 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 <T extends TokenPayload> String renew(String oldToken, T payload) {
return renew(oldToken, Duration.ofMinutes(30), payload);
}
}
@@ -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<SignatureAlgorithm> {
private JjwtTokenResolverConfig() {}
private static final Map<TokenAlgorithm, SignatureAlgorithm> 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.
* <p>
* 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);
}
}
+30
View File
@@ -40,12 +40,42 @@
<artifactId>simple-jwt-facade</artifactId> <artifactId>simple-jwt-facade</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>cn.org.codecrafters</groupId> <groupId>cn.org.codecrafters</groupId>
<artifactId>simple-jwt-authzero</artifactId> <artifactId>simple-jwt-authzero</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cn.org.codecrafters</groupId>
<artifactId>simple-jwt-jjwt</artifactId>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId> <artifactId>spring-boot-autoconfigure</artifactId>
@@ -34,17 +34,15 @@ import org.springframework.context.annotation.Bean;
/** /**
* <p> * <p>
* SimpleJwtAutoConfiguration is responsible for automatically configuring the * SimpleJwtAutoConfiguration is responsible for automatically configuring the
* Simple JWT library when used in a Spring Boot application. It provides * Simple JWT library with {@code com.auth0:java-jwt} when used in a Spring
* default settings and configurations to ensure that the library works * Boot application. It provides default settings and configurations to ensure
* smoothly without requiring manual configuration. * that the library works smoothly without requiring manual configuration.
*
* *
* <p> * <p>
* This auto-configuration class sets up the necessary beans and components * This auto-configuration class sets up the necessary beans and components
* required for JWT generation and validation. It automatically creates and * required for JWT generation and validation. It automatically creates and
* configures the {@link TokenResolver} bean based on the available options and * configures the {@link TokenResolver} bean based on the available options and
* properties. * properties.
*
* *
* <p> * <p>
* Developers using the Simple JWT library with Spring Boot do not need to * Developers using the Simple JWT library with Spring Boot do not need to
@@ -61,7 +59,9 @@ import org.springframework.context.annotation.Bean;
@Slf4j @Slf4j
@AutoConfiguration @AutoConfiguration
@EnableConfigurationProperties(value = {SimpleJwtProperties.class}) @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). * The GuidCreator instance to be used for generating JWT IDs (JTI).
@@ -81,7 +81,7 @@ public class SimpleJwtAutoConfiguration {
* @param simpleJwtProperties the SimpleJwtProperties instance * @param simpleJwtProperties the SimpleJwtProperties instance
*/ */
@Autowired @Autowired
public SimpleJwtAutoConfiguration(SimpleJwtProperties simpleJwtProperties, GuidCreator<?> jtiCreator) { public AuthzeroTokenResolverAutoConfiguration(SimpleJwtProperties simpleJwtProperties, GuidCreator<?> jtiCreator) {
this.jtiCreator = jtiCreator; this.jtiCreator = jtiCreator;
this.simpleJwtProperties = simpleJwtProperties; this.simpleJwtProperties = simpleJwtProperties;
} }
@@ -96,14 +96,14 @@ public class SimpleJwtAutoConfiguration {
* @return the {@link TokenResolver} instance * @return the {@link TokenResolver} instance
*/ */
@Bean @Bean
@ConditionalOnClass({DecodedJWT.class, AuthzeroTokenResolver.class})
@ConditionalOnMissingBean({TokenResolver.class})
@ConditionalOnBean(value = {GuidCreator.class}, name = "jtiCreator") @ConditionalOnBean(value = {GuidCreator.class}, name = "jtiCreator")
public TokenResolver<DecodedJWT> tokenResolver() { public TokenResolver<DecodedJWT> tokenResolver() {
return new AuthzeroTokenResolver(this.jtiCreator, return new AuthzeroTokenResolver(
jtiCreator,
simpleJwtProperties.algorithm(), simpleJwtProperties.algorithm(),
simpleJwtProperties.issuer(), simpleJwtProperties.issuer(),
simpleJwtProperties.secret()); simpleJwtProperties.secret()
);
} }
} }
@@ -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;
/**
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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<Jws<Claims>> tokenResolver() {
return new JjwtTokenResolver(
jtiCreator,
simpleJwtProperties.algorithm(),
simpleJwtProperties.issuer(),
simpleJwtProperties.secret()
);
}
}
@@ -17,6 +17,8 @@
package cn.org.codecrafters.simplejwt.autoconfiguration.properties; 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 cn.org.codecrafters.simplejwt.constants.TokenAlgorithm;
import lombok.Data; import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -32,7 +34,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* <p> * <p>
* SimpleJwtProperties provides configuration options for the JWT algorithm, * SimpleJwtProperties provides configuration options for the JWT algorithm,
* issuer, and secret. The properties are used by the {@link * 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 * class to set up the necessary configurations for JWT generation and
* validation. * validation.
* *
@@ -51,19 +53,23 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
public class SimpleJwtProperties { 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. * Returns the JWT algorithm configured in the properties.
@@ -1,2 +1,3 @@
cn.org.codecrafters.simplejwt.autoconfiguration.GuidAutoConfiguration cn.org.codecrafters.simplejwt.autoconfiguration.GuidAutoConfiguration
cn.org.codecrafters.simplejwt.autoconfiguration.SimpleJwtAutoConfiguration cn.org.codecrafters.simplejwt.autoconfiguration.AuthzeroTokenResolverAutoConfiguration
cn.org.codecrafters.simplejwt.autoconfiguration.JjwtTokenResolverAutoConfiguration