Skip to content

Commit b7a9119

Browse files
author
Zihlu Wang
authored
Merge pull request #20 from CodeCraftersCN/dev/v1.2.0
Upgrade to v1.2.0.
2 parents 853e05a + 66c2c4c commit b7a9119

File tree

16 files changed

+353
-90
lines changed

16 files changed

+353
-90
lines changed

devkit-core/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<parent>
2424
<groupId>cn.org.codecrafters</groupId>
2525
<artifactId>jdevkit</artifactId>
26-
<version>1.1.2</version>
26+
<version>1.2.0</version>
2727
</parent>
2828

2929
<artifactId>devkit-core</artifactId>

devkit-utils/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>cn.org.codecrafters</groupId>
88
<artifactId>jdevkit</artifactId>
9-
<version>1.1.2</version>
9+
<version>1.2.0</version>
1010
</parent>
1111

1212
<artifactId>devkit-utils</artifactId>

guid/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<parent>
2424
<groupId>cn.org.codecrafters</groupId>
2525
<artifactId>jdevkit</artifactId>
26-
<version>1.1.2</version>
26+
<version>1.2.0</version>
2727
</parent>
2828

2929
<artifactId>guid</artifactId>

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
<groupId>cn.org.codecrafters</groupId>
3131
<artifactId>jdevkit</artifactId>
32-
<version>1.1.2</version>
32+
<version>1.2.0</version>
3333
<inceptionYear>2023</inceptionYear>
3434

3535
<packaging>pom</packaging>

property-guard-spring-boot-starter/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<parent>
2424
<groupId>cn.org.codecrafters</groupId>
2525
<artifactId>jdevkit</artifactId>
26-
<version>1.1.2</version>
26+
<version>1.2.0</version>
2727
</parent>
2828

2929
<artifactId>property-guard-spring-boot-starter</artifactId>

simple-jwt-authzero/pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<parent>
2424
<groupId>cn.org.codecrafters</groupId>
2525
<artifactId>jdevkit</artifactId>
26-
<version>1.1.2</version>
26+
<version>1.2.0</version>
2727
</parent>
2828

2929
<artifactId>simple-jwt-authzero</artifactId>
@@ -40,6 +40,11 @@
4040
<artifactId>simple-jwt-facade</artifactId>
4141
</dependency>
4242

43+
<dependency>
44+
<groupId>com.fasterxml.jackson.core</groupId>
45+
<artifactId>jackson-databind</artifactId>
46+
</dependency>
47+
4348
<dependency>
4449
<groupId>com.auth0</groupId>
4550
<artifactId>java-jwt</artifactId>

simple-jwt-authzero/src/main/java/cn/org/codecrafters/simplejwt/authzero/AuthzeroTokenResolver.java

Lines changed: 115 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,32 @@
1717

1818
package cn.org.codecrafters.simplejwt.authzero;
1919

20+
import cn.org.codecrafters.devkit.utils.Base64Util;
2021
import cn.org.codecrafters.guid.GuidCreator;
2122
import cn.org.codecrafters.simplejwt.SecretCreator;
2223
import cn.org.codecrafters.simplejwt.TokenPayload;
2324
import cn.org.codecrafters.simplejwt.TokenResolver;
2425
import cn.org.codecrafters.simplejwt.annotations.ExcludeFromPayload;
26+
import cn.org.codecrafters.simplejwt.annotations.TokenEnum;
2527
import cn.org.codecrafters.simplejwt.authzero.config.AuthzeroTokenResolverConfig;
2628
import cn.org.codecrafters.simplejwt.config.TokenResolverConfig;
29+
import cn.org.codecrafters.simplejwt.constants.PredefinedKeys;
2730
import cn.org.codecrafters.simplejwt.constants.TokenAlgorithm;
2831
import com.auth0.jwt.JWT;
2932
import com.auth0.jwt.JWTCreator;
3033
import com.auth0.jwt.algorithms.Algorithm;
34+
import com.auth0.jwt.interfaces.Claim;
3135
import com.auth0.jwt.interfaces.DecodedJWT;
3236
import com.auth0.jwt.interfaces.JWTVerifier;
37+
import com.fasterxml.jackson.core.JsonProcessingException;
38+
import com.fasterxml.jackson.core.type.TypeReference;
39+
import com.fasterxml.jackson.databind.JsonMappingException;
40+
import com.fasterxml.jackson.databind.JsonNode;
41+
import com.fasterxml.jackson.databind.ObjectMapper;
42+
import com.fasterxml.jackson.databind.node.ObjectNode;
3343
import lombok.extern.slf4j.Slf4j;
3444

45+
import java.lang.reflect.Field;
3546
import java.lang.reflect.InvocationTargetException;
3647
import java.time.Duration;
3748
import java.time.LocalDateTime;
@@ -114,21 +125,27 @@ public class AuthzeroTokenResolver implements TokenResolver<DecodedJWT> {
114125
*/
115126
private final JWTVerifier verifier;
116127

128+
/**
129+
* Jackson JSON handler.
130+
*/
131+
private final ObjectMapper objectMapper;
132+
117133
private final AuthzeroTokenResolverConfig config = AuthzeroTokenResolverConfig.getInstance();
118134

119135
/**
120136
* Creates a new instance of {@code AuthzeroTokenResolver} with the
121137
* provided configurations.
122138
*
123-
* @param jtiCreator the {@link GuidCreator} used for generating unique
124-
* identifiers for "jti" claim in JWT tokens
125-
* @param algorithm the algorithm used for signing and verifying JWT
126-
* tokens
127-
* @param issuer the issuer claim value to be included in JWT tokens
128-
* @param secret the secret used for HMAC-based algorithms (HS256,
129-
* HS384, HS512) for token signing and verification
139+
* @param jtiCreator the {@link GuidCreator} used for generating unique
140+
* identifiers for "jti" claim in JWT tokens
141+
* @param algorithm the algorithm used for signing and verifying JWT
142+
* tokens
143+
* @param issuer the issuer claim value to be included in JWT tokens
144+
* @param secret the secret used for HMAC-based algorithms (HS256,
145+
* HS384, HS512) for token signing and verification
146+
* @param objectMapper JSON handler
130147
*/
131-
public AuthzeroTokenResolver(GuidCreator<?> jtiCreator, TokenAlgorithm algorithm, String issuer, String secret) {
148+
public AuthzeroTokenResolver(GuidCreator<?> jtiCreator, TokenAlgorithm algorithm, String issuer, String secret, ObjectMapper objectMapper) {
132149
if (secret == null || secret.isBlank()) {
133150
throw new IllegalArgumentException("A secret is required to build a JSON Web Token.");
134151
}
@@ -143,6 +160,21 @@ public AuthzeroTokenResolver(GuidCreator<?> jtiCreator, TokenAlgorithm algorithm
143160
.apply(secret);
144161
this.issuer = issuer;
145162
this.verifier = JWT.require(this.algorithm).build();
163+
this.objectMapper = objectMapper;
164+
}
165+
166+
/**
167+
* Creates a new instance of {@link AuthzeroTokenResolver} with the
168+
* provided configurations and a simple UUID GuidCreator.
169+
*
170+
* @param algorithm the algorithm used for signing and verifying JWT tokens
171+
* @param issuer the issuer claim value to be included in JWT tokens
172+
* @param secret the secret used for HMAC-based algorithms (HS256,
173+
* HS384, HS512) for token signing and verification
174+
* @param objectMapper Jackson Databind JSON Handler
175+
*/
176+
public AuthzeroTokenResolver(TokenAlgorithm algorithm, String issuer, String secret, ObjectMapper objectMapper) {
177+
this(UUID::randomUUID, algorithm, issuer, secret, objectMapper);
146178
}
147179

148180
/**
@@ -155,20 +187,7 @@ public AuthzeroTokenResolver(GuidCreator<?> jtiCreator, TokenAlgorithm algorithm
155187
* HS384, HS512) for token signing and verification
156188
*/
157189
public AuthzeroTokenResolver(TokenAlgorithm algorithm, String issuer, String secret) {
158-
if (secret == null || secret.isBlank()) {
159-
throw new IllegalArgumentException("A secret is required to build a JSON Web Token.");
160-
}
161-
162-
if (secret.length() <= 32) {
163-
log.warn("The provided secret which owns {} characters is too weak. Please consider replacing it with a stronger one.", secret.length());
164-
}
165-
166-
this.jtiCreator = UUID::randomUUID;
167-
this.algorithm = config
168-
.getAlgorithm(algorithm)
169-
.apply(secret);
170-
this.issuer = issuer;
171-
this.verifier = JWT.require(this.algorithm).build();
190+
this(UUID::randomUUID, algorithm, issuer, secret, new ObjectMapper());
172191
}
173192

174193
/**
@@ -181,20 +200,7 @@ public AuthzeroTokenResolver(TokenAlgorithm algorithm, String issuer, String sec
181200
* HS384, HS512) for token signing and verification
182201
*/
183202
public AuthzeroTokenResolver(String issuer, String secret) {
184-
if (secret == null || secret.isBlank()) {
185-
throw new IllegalArgumentException("A secret is required to build a JSON Web Token.");
186-
}
187-
188-
if (secret.length() <= 32) {
189-
log.warn("The provided secret which owns {} characters is too weak. Please consider replacing it with a stronger one.", secret.length());
190-
}
191-
192-
this.jtiCreator = UUID::randomUUID;
193-
this.algorithm = config
194-
.getAlgorithm(TokenAlgorithm.HS256)
195-
.apply(secret);
196-
this.issuer = issuer;
197-
this.verifier = JWT.require(this.algorithm).build();
203+
this(UUID::randomUUID, TokenAlgorithm.HS256, issuer, secret, new ObjectMapper());
198204
}
199205

200206
/**
@@ -213,6 +219,7 @@ public AuthzeroTokenResolver(String issuer) {
213219
.apply(secret);
214220
this.issuer = issuer;
215221
this.verifier = JWT.require(this.algorithm).build();
222+
this.objectMapper = new ObjectMapper();
216223

217224
log.info("The secret has been set to {}.", secret);
218225
}
@@ -370,16 +377,28 @@ public <T extends TokenPayload> String createToken(Duration expireAfter, String
370377
var fields = payloadClass.getDeclaredFields();
371378

372379
for (var field : fields) {
373-
// Skip the fields which are annotated with ExcludeFromPayload
374-
if (field.isAnnotationPresent(ExcludeFromPayload.class))
375-
continue;
376-
377380
try {
378-
field.setAccessible(true);
381+
var fieldName = field.getName();
382+
// Skip the fields which are annotated with ExcludeFromPayload
383+
if (field.isAnnotationPresent(ExcludeFromPayload.class))
384+
continue;
385+
386+
Object invokeObj = payload;
387+
var getter = payloadClass.getDeclaredMethod("get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1));
388+
if (field.isAnnotationPresent(TokenEnum.class)) {
389+
var tokenEnum = field.getAnnotation(TokenEnum.class);
390+
invokeObj = getter.invoke(payload);
391+
getter = field.getType().getDeclaredMethod("get" + tokenEnum.propertyName().substring(0, 1).toUpperCase() + tokenEnum.propertyName().substring(1));
392+
}
393+
379394
// Build Claims
380-
addClaim(builder, field.getName(), field.get(payload));
395+
addClaim(builder, fieldName, getter.invoke(invokeObj));
381396
} catch (IllegalAccessException e) {
382397
log.error("Cannot access field {}!", field.getName());
398+
} catch (NoSuchMethodException e) {
399+
log.error("Unable to find setter according to given field name.", e);
400+
} catch (InvocationTargetException e) {
401+
log.info("Cannot invoke method.", e);
383402
}
384403
}
385404

@@ -408,43 +427,66 @@ public DecodedJWT resolve(String token) {
408427
*/
409428
@Override
410429
public <T extends TokenPayload> T extract(String token, Class<T> targetType) {
411-
// Get claims from token.
412-
var claims = resolve(token).getClaims();
413-
414430
try {
431+
// Get claims from token.
432+
var payloads = objectMapper.readValue(Base64Util.decode(resolve(token).getPayload()), new MapTypeReference());
415433
// Get the no-argument constructor to create an instance.
416-
T bean = targetType.getConstructor().newInstance();
434+
var bean = targetType.getConstructor().newInstance();
417435

418-
var fields = targetType.getDeclaredFields();
419-
for (var field : fields) {
420-
// Ignore the field annotated with @ExcludeFromPayload.
421-
if (field.isAnnotationPresent(ExcludeFromPayload.class))
436+
for (var entry : payloads.entrySet()) {
437+
// Jump all JWT pre-defined properties and the fields that are annotated to be excluded.
438+
if (PredefinedKeys.KEYS.contains(entry.getKey()) || targetType.getDeclaredField(entry.getKey()).isAnnotationPresent(ExcludeFromPayload.class))
422439
continue;
423440

424-
// Get the name of this field.
425-
var fieldName = field.getName();
426-
427-
// Prevent this class is annotated @Slf4j or added logger.
428-
if ("log".equalsIgnoreCase(fieldName) || "logger".equalsIgnoreCase(fieldName))
429-
continue;
441+
var field = targetType.getDeclaredField(entry.getKey());
442+
var setter = targetType.getDeclaredMethod("set" + entry.getKey().substring(0, 1).toUpperCase() + entry.getKey().substring(1), field.getType());
443+
var fieldValue = entry.getValue();
444+
if (field.isAnnotationPresent(TokenEnum.class)) {
445+
var annotation = field.getAnnotation(TokenEnum.class);
446+
var enumStaticLoader = field.getType().getDeclaredMethod("loadBy" + annotation.propertyName().substring(0, 1).toUpperCase() + annotation.propertyName().substring(1), annotation.dataType().getMappedClass());
447+
fieldValue = enumStaticLoader.invoke(null, fieldValue);
448+
}
430449

431-
// Get the value of this field.
432-
var fieldValue = Optional.ofNullable(claims.get(fieldName))
433-
.map(claim -> claim.as(field.getType()))
434-
.orElse(null);
435-
if (fieldValue != null) {
436-
// Set the field value by invoking the setter method.
437-
var setter = targetType.getDeclaredMethod("set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1), fieldValue.getClass());
450+
if (setter.canAccess(bean)) {
438451
setter.invoke(bean, fieldValue);
452+
} else {
453+
log.error("Setter for field {} can't be accessed.", entry.getKey());
439454
}
440455
}
441-
442456
return bean;
443-
} catch (NoSuchMethodException e) {
444-
log.error("Unable to find a no-argument constructor declaration for class {}.", targetType.getCanonicalName());
445-
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
446-
log.error("Unable to create a new instance of class {}.", targetType.getCanonicalName());
457+
} catch (JsonProcessingException e) {
458+
log.error("Unable to read payload as a Map<String, Object>.", e);
459+
} catch (InvocationTargetException | InstantiationException | IllegalAccessException |
460+
NoSuchMethodException e) {
461+
log.error("Unable to load the constructor or setter.", e);
462+
} catch (NoSuchFieldException e) {
463+
log.error("Unable to load the field.", e);
464+
}
465+
return null;
466+
}
467+
468+
/**
469+
* Re-generate a new token with the payload in the old one.
470+
*
471+
* @param oldToken the old token
472+
* @param expireAfter how long the new token can be valid for
473+
* @return re-generated token with the payload in the old one or
474+
* {@code null} if an {@link JsonProcessingException} occurred.
475+
*/
476+
@Override
477+
public String renew(String oldToken, Duration expireAfter) {
478+
var resolved = resolve(oldToken);
479+
480+
try {
481+
var payload = objectMapper.readValue(Base64Util.decode(resolved.getPayload()), ObjectNode.class);
482+
payload.remove(PredefinedKeys.KEYS);
483+
484+
var payloadMap = objectMapper.convertValue(payload, new MapTypeReference());
485+
return createToken(expireAfter, resolved.getAudience().get(0), resolved.getSubject(), payloadMap);
486+
} catch (JsonProcessingException e) {
487+
log.error("Cannot read payload content, error details:", e);
447488
}
489+
448490
return null;
449491
}
450492

@@ -509,4 +551,9 @@ public <T extends TokenPayload> String renew(String oldToken, Duration expireAft
509551
public <T extends TokenPayload> String renew(String oldToken, T payload) {
510552
return renew(oldToken, Duration.ofMinutes(30), payload);
511553
}
554+
555+
private static class MapTypeReference extends TypeReference<Map<String, Object>> {
556+
MapTypeReference() {
557+
}
558+
}
512559
}

simple-jwt-facade/pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<parent>
2424
<groupId>cn.org.codecrafters</groupId>
2525
<artifactId>jdevkit</artifactId>
26-
<version>1.1.2</version>
26+
<version>1.2.0</version>
2727
</parent>
2828

2929
<artifactId>simple-jwt-facade</artifactId>
@@ -40,6 +40,11 @@
4040
<artifactId>devkit-core</artifactId>
4141
</dependency>
4242

43+
<dependency>
44+
<groupId>cn.org.codecrafters</groupId>
45+
<artifactId>devkit-utils</artifactId>
46+
</dependency>
47+
4348
<dependency>
4449
<groupId>cn.org.codecrafters</groupId>
4550
<artifactId>guid</artifactId>

0 commit comments

Comments
 (0)