feat: Upgrade jjwt to 0.12.5

This commit is contained in:
Zihlu Wang
2024-03-12 10:15:24 +08:00
parent 1a194396ff
commit d257fbff82
2 changed files with 75 additions and 43 deletions
@@ -28,17 +28,16 @@ import cn.org.codecrafters.simplejwt.constants.PredefinedKeys;
import cn.org.codecrafters.simplejwt.constants.TokenAlgorithm; import cn.org.codecrafters.simplejwt.constants.TokenAlgorithm;
import cn.org.codecrafters.simplejwt.exceptions.WeakSecretException; import cn.org.codecrafters.simplejwt.exceptions.WeakSecretException;
import cn.org.codecrafters.simplejwt.jjwt.config.JjwtTokenResolverConfig; import cn.org.codecrafters.simplejwt.jjwt.config.JjwtTokenResolverConfig;
import com.fasterxml.jackson.core.type.TypeReference;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecureDigestAlgorithm;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.crypto.SecretKey;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
@@ -46,7 +45,7 @@ import java.util.*;
/** /**
* The {@link JjwtTokenResolver} class is an implementation of the {@link * The {@link JjwtTokenResolver} class is an implementation of the {@link
* cn.org.codecrafters.simplejwt.TokenResolver} interface. It uses the {@code * TokenResolver} interface. It uses the {@code
* io.jsonwebtoken:jjwt} library to handle JSON Web Token (JWT) resolution. * io.jsonwebtoken:jjwt} library to handle JSON Web Token (JWT) resolution.
* This resolver provides functionality to create, extract, verify, and renew * This resolver provides functionality to create, extract, verify, and renew
* JWT tokens using various algorithms and custom payload data. * JWT tokens using various algorithms and custom payload data.
@@ -92,7 +91,7 @@ import java.util.*;
* @see Claims * @see Claims
* @see Jws * @see Jws
* @see Jwts * @see Jwts
* @see SignatureAlgorithm * @see SecureDigestAlgorithm
* @see Keys * @see Keys
* @since 1.0.0 * @since 1.0.0
*/ */
@@ -101,14 +100,21 @@ public class JjwtTokenResolver implements TokenResolver<Jws<Claims>> {
private final GuidCreator<?> jtiCreator; private final GuidCreator<?> jtiCreator;
private final SignatureAlgorithm algorithm; private final SecureDigestAlgorithm<SecretKey, SecretKey> algorithm;
private final String issuer; private final String issuer;
private final Key key; private final SecretKey key;
private final JjwtTokenResolverConfig config = JjwtTokenResolverConfig.getInstance(); private final JjwtTokenResolverConfig config = JjwtTokenResolverConfig.getInstance();
/**
* Create a resolver with specified algorithm, issuer, secret and guid strategy.
*
* @param algorithm specified algorithm
* @param issuer specified issuer
* @param secret specified secret
*/
public JjwtTokenResolver(GuidCreator<?> jtiCreator, TokenAlgorithm algorithm, String issuer, String secret) { public JjwtTokenResolver(GuidCreator<?> jtiCreator, TokenAlgorithm algorithm, String issuer, String secret) {
if (Objects.isNull(secret) || secret.isBlank()) { if (Objects.isNull(secret) || secret.isBlank()) {
throw new IllegalArgumentException("A secret is required to build a JSON Web Token."); throw new IllegalArgumentException("A secret is required to build a JSON Web Token.");
@@ -129,6 +135,13 @@ public class JjwtTokenResolver implements TokenResolver<Jws<Claims>> {
this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
} }
/**
* Create a resolver with specified algorithm, issuer, secret and default guid strategy.
*
* @param algorithm specified algorithm
* @param issuer specified issuer
* @param secret specified secret
*/
public JjwtTokenResolver(TokenAlgorithm algorithm, String issuer, String secret) { public JjwtTokenResolver(TokenAlgorithm algorithm, String issuer, String secret) {
if (secret == null || secret.isBlank()) { if (secret == null || secret.isBlank()) {
throw new IllegalArgumentException("A secret is required to build a JSON Web Token."); throw new IllegalArgumentException("A secret is required to build a JSON Web Token.");
@@ -149,6 +162,13 @@ public class JjwtTokenResolver implements TokenResolver<Jws<Claims>> {
this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
} }
/**
* Create a resolver with specified issuer, secret, default algorithm and guid strategy.
*
* @param issuer specified issuer
* @param secret specified secret
* @see #JjwtTokenResolver(TokenAlgorithm, String, String)
*/
public JjwtTokenResolver(String issuer, String secret) { public JjwtTokenResolver(String issuer, String secret) {
if (secret == null || secret.isBlank()) { if (secret == null || secret.isBlank()) {
throw new IllegalArgumentException("A secret is required to build a JSON Web Token."); throw new IllegalArgumentException("A secret is required to build a JSON Web Token.");
@@ -169,6 +189,12 @@ public class JjwtTokenResolver implements TokenResolver<Jws<Claims>> {
this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
} }
/**
* Create a resolver with specified issuer, random secret string, default algorithm and guid strategy.
*
* @param issuer specified issuer
* @see #JjwtTokenResolver(String, String)
*/
public JjwtTokenResolver(String issuer) { public JjwtTokenResolver(String issuer) {
this.jtiCreator = UUID::randomUUID; this.jtiCreator = UUID::randomUUID;
this.algorithm = config.getAlgorithm(TokenAlgorithm.HS256); this.algorithm = config.getAlgorithm(TokenAlgorithm.HS256);
@@ -176,26 +202,6 @@ public class JjwtTokenResolver implements TokenResolver<Jws<Claims>> {
this.key = Keys.hmacShaKeyFor(SecretCreator.createSecret(32, true, true, true).getBytes(StandardCharsets.UTF_8)); this.key = Keys.hmacShaKeyFor(SecretCreator.createSecret(32, true, true, true).getBytes(StandardCharsets.UTF_8));
} }
private String buildToken(Duration expireAfter, String audience, String subject, Map<String, Object> claims) {
var now = LocalDateTime.now();
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.addClaims(claims);
}
return builder.signWith(key, algorithm)
.compact();
}
/** /**
* Creates a new token with the specified expiration time, subject, and * Creates a new token with the specified expiration time, subject, and
* audience. * audience.
@@ -280,10 +286,10 @@ public class JjwtTokenResolver implements TokenResolver<Jws<Claims>> {
*/ */
@Override @Override
public Jws<Claims> resolve(String token) { public Jws<Claims> resolve(String token) {
return Jwts.parserBuilder() return Jwts.parser()
.setSigningKey(key) .verifyWith(key)
.build() .build()
.parseClaimsJws(token); .parseSignedClaims(token);
} }
/** /**
@@ -300,7 +306,7 @@ public class JjwtTokenResolver implements TokenResolver<Jws<Claims>> {
public <T extends TokenPayload> T extract(String token, Class<T> targetType) { public <T extends TokenPayload> T extract(String token, Class<T> targetType) {
var resolvedToken = resolve(token); var resolvedToken = resolve(token);
var claims = resolvedToken.getBody(); var claims = resolvedToken.getPayload();
try { try {
var bean = targetType.getConstructor().newInstance(); var bean = targetType.getConstructor().newInstance();
@@ -351,9 +357,9 @@ public class JjwtTokenResolver implements TokenResolver<Jws<Claims>> {
@Override @Override
public String renew(String oldToken, Duration expireAfter) { public String renew(String oldToken, Duration expireAfter) {
var resolvedToken = resolve(oldToken); var resolvedToken = resolve(oldToken);
var tokenPayloads = resolvedToken.getBody(); var tokenPayloads = resolvedToken.getPayload();
var audience = tokenPayloads.getAudience(); var audience = tokenPayloads.getAudience().toArray(new String[]{})[0];
var subject = tokenPayloads.getSubject(); var subject = tokenPayloads.getSubject();
PredefinedKeys.KEYS.forEach(tokenPayloads::remove); PredefinedKeys.KEYS.forEach(tokenPayloads::remove);
@@ -372,8 +378,8 @@ public class JjwtTokenResolver implements TokenResolver<Jws<Claims>> {
*/ */
@Override @Override
public String renew(String oldToken, Duration expireAfter, Map<String, Object> payload) { public String renew(String oldToken, Duration expireAfter, Map<String, Object> payload) {
var resolvedTokenClaims = resolve(oldToken).getBody(); var resolvedTokenClaims = resolve(oldToken).getPayload();
var audience = resolvedTokenClaims.getAudience(); var audience = resolvedTokenClaims.getAudience().toArray(new String[]{})[0];
var subject = resolvedTokenClaims.getSubject(); var subject = resolvedTokenClaims.getSubject();
return createToken(expireAfter, audience, subject, payload); return createToken(expireAfter, audience, subject, payload);
@@ -404,8 +410,8 @@ public class JjwtTokenResolver implements TokenResolver<Jws<Claims>> {
*/ */
@Override @Override
public <T extends TokenPayload> String renew(String oldToken, Duration expireAfter, T payload) { public <T extends TokenPayload> String renew(String oldToken, Duration expireAfter, T payload) {
var resolvedTokenClaims = resolve(oldToken).getBody(); var resolvedTokenClaims = resolve(oldToken).getPayload();
var audience = resolvedTokenClaims.getAudience(); var audience = resolvedTokenClaims.getAudience().toArray(new String[]{})[0];
var subject = resolvedTokenClaims.getSubject(); var subject = resolvedTokenClaims.getSubject();
return createToken(expireAfter, audience, subject, payload); return createToken(expireAfter, audience, subject, payload);
@@ -424,4 +430,26 @@ public class JjwtTokenResolver implements TokenResolver<Jws<Claims>> {
public <T extends TokenPayload> String renew(String oldToken, T payload) { public <T extends TokenPayload> String renew(String oldToken, T payload) {
return renew(oldToken, Duration.ofMinutes(30), payload); return renew(oldToken, Duration.ofMinutes(30), payload);
} }
private String buildToken(Duration expireAfter, String audience, String subject, Map<String, Object> claims) {
var now = LocalDateTime.now();
var builder = Jwts.builder()
.header().add("typ", "JWT")
.and()
.issuedAt(Date.from(now.atZone(ZoneId.systemDefault()).toInstant()))
.notBefore(Date.from(now.atZone(ZoneId.systemDefault()).toInstant()))
.expiration(Date.from(now.plus(expireAfter).atZone(ZoneId.systemDefault()).toInstant()))
.subject(subject)
.issuer(this.issuer)
.audience().add(audience)
.and()
.id(jtiCreator.nextId().toString());
if (claims != null && !claims.isEmpty()) {
builder.claims(claims);
}
return builder.signWith(key, algorithm)
.compact();
}
} }
@@ -22,8 +22,12 @@ import cn.org.codecrafters.simplejwt.config.TokenResolverConfig;
import cn.org.codecrafters.simplejwt.constants.TokenAlgorithm; import cn.org.codecrafters.simplejwt.constants.TokenAlgorithm;
import cn.org.codecrafters.simplejwt.exceptions.UnsupportedAlgorithmException; import cn.org.codecrafters.simplejwt.exceptions.UnsupportedAlgorithmException;
import cn.org.codecrafters.simplejwt.jjwt.JjwtTokenResolver; import cn.org.codecrafters.simplejwt.jjwt.JjwtTokenResolver;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.MacAlgorithm;
import io.jsonwebtoken.security.SecureDigestAlgorithm;
import javax.crypto.SecretKey;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -57,15 +61,15 @@ import java.util.Map;
* @version 1.1.1 * @version 1.1.1
* @since 1.0.0 * @since 1.0.0
*/ */
public final class JjwtTokenResolverConfig implements TokenResolverConfig<SignatureAlgorithm> { public final class JjwtTokenResolverConfig implements TokenResolverConfig<SecureDigestAlgorithm<SecretKey, SecretKey>> {
private JjwtTokenResolverConfig() { private JjwtTokenResolverConfig() {
} }
private static final Map<TokenAlgorithm, SignatureAlgorithm> SUPPORTED_ALGORITHMS = new HashMap<>() {{ private static final Map<TokenAlgorithm, SecureDigestAlgorithm<SecretKey, SecretKey>> SUPPORTED_ALGORITHMS = new HashMap<>() {{
put(TokenAlgorithm.HS256, SignatureAlgorithm.HS256); put(TokenAlgorithm.HS256, Jwts.SIG.HS256);
put(TokenAlgorithm.HS384, SignatureAlgorithm.HS384); put(TokenAlgorithm.HS384, Jwts.SIG.HS384);
put(TokenAlgorithm.HS512, SignatureAlgorithm.HS512); put(TokenAlgorithm.HS512, Jwts.SIG.HS512);
}}; }};
private static JjwtTokenResolverConfig instance; private static JjwtTokenResolverConfig instance;
@@ -95,7 +99,7 @@ public final class JjwtTokenResolverConfig implements TokenResolverConfig<Signat
* TokenAlgorithm} * TokenAlgorithm}
*/ */
@Override @Override
public SignatureAlgorithm getAlgorithm(TokenAlgorithm algorithm) { public SecureDigestAlgorithm<SecretKey, SecretKey> getAlgorithm(TokenAlgorithm algorithm) {
if (!SUPPORTED_ALGORITHMS.containsKey(algorithm)) { if (!SUPPORTED_ALGORITHMS.containsKey(algorithm)) {
throw new UnsupportedAlgorithmException(""" throw new UnsupportedAlgorithmException("""
The request algorithm is not supported by our system yet. Please change to supported ones."""); The request algorithm is not supported by our system yet. Please change to supported ones.""");