feat(simple-jwt-authzero): finished authzero implementation
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
<module>guid</module>
|
<module>guid</module>
|
||||||
<module>dev-utils</module>
|
<module>dev-utils</module>
|
||||||
<module>simple-jwt-facade</module>
|
<module>simple-jwt-facade</module>
|
||||||
|
<module>simple-jwt-authzero</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<developers>
|
<developers>
|
||||||
@@ -110,6 +111,18 @@
|
|||||||
<artifactId>devkit-core</artifactId>
|
<artifactId>devkit-core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.org.codecrafters</groupId>
|
||||||
|
<artifactId>guid</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.org.codecrafters</groupId>
|
||||||
|
<artifactId>simple-jwt-facade</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?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-authzero</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>guid</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.org.codecrafters</groupId>
|
||||||
|
<artifactId>simple-jwt-facade</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.auth0</groupId>
|
||||||
|
<artifactId>java-jwt</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
+461
@@ -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.
|
||||||
|
* <p>
|
||||||
|
* <b>Dependencies:</b>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* <b>Usage:</b>
|
||||||
|
* To use the {@code AuthzeroTokenResolver}, first, create an instance of this
|
||||||
|
* class:
|
||||||
|
* <pre>{@code
|
||||||
|
* TokenResolver<DecodedJWT> tokenResolver =
|
||||||
|
* new AuthzeroTokenResolver(TokenAlgorithm.HS256,
|
||||||
|
* "Token Subject",
|
||||||
|
* "Token Issuer",
|
||||||
|
* "Token Secret");
|
||||||
|
* }</pre>
|
||||||
|
* <p>
|
||||||
|
* Then, you can utilize the various methods provided by this resolver to
|
||||||
|
* handle JWT tokens:
|
||||||
|
*
|
||||||
|
* <pre>{@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);
|
||||||
|
* }</pre>
|
||||||
|
* <p>
|
||||||
|
* <b>Note:</b>
|
||||||
|
* 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<DecodedJWT> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>) 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.
|
||||||
|
* <p>
|
||||||
|
* 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<String, Object> claims) {
|
||||||
|
if (Objects.nonNull(claims)) {
|
||||||
|
for (var e : claims.entrySet()) {
|
||||||
|
addClaim(builder, e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finish creating a token.
|
||||||
|
* <p>
|
||||||
|
* 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<String, Object> 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 <T extends TokenPayload> 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 extends TokenPayload> T extract(String token, Class<T> 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<String, Object> 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<String, Object> 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 <T extends TokenPayload> 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 <T> 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 <T extends TokenPayload> String renew(String oldToken, T payload) {
|
||||||
|
return renew(oldToken, Duration.ofMinutes(30), payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
+139
@@ -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.
|
||||||
|
* <p>
|
||||||
|
* 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).
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <b>Algorithm Mapping:</b>
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <b>Note:</b>
|
||||||
|
* 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<Function<String, Algorithm>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new instance of AuthzeroTokenResolverConfig.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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<TokenAlgorithm, Function<String, Algorithm>> SUPPORTED_ALGORITHMS = new HashMap<>() {{
|
||||||
|
put(TokenAlgorithm.HS256, Algorithm::HMAC256);
|
||||||
|
put(TokenAlgorithm.HS384, Algorithm::HMAC384);
|
||||||
|
put(TokenAlgorithm.HS512, Algorithm::HMAC512);
|
||||||
|
}};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the instance of AuthzeroTokenResolverConfig.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <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
|
||||||
|
* @throws UnsupportedAlgorithmException if the given {@code algorithm} is
|
||||||
|
* not supported by this
|
||||||
|
* implementation
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Function<String, Algorithm> getFunction(TokenAlgorithm algorithm) {
|
||||||
|
return Optional.of(SUPPORTED_ALGORITHMS).map((entry) -> entry.get(algorithm))
|
||||||
|
.orElseThrow(() -> new UnsupportedAlgorithmException("The specified algorithm is not supported yet."));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} : %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="STDOUT-C" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%date{yyyy-MM-dd HH:mm:ss} [%thread] %highlight(%-5level) %cyan(%logger{50}) : %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="STDOUT-C"/>
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
+40
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user