JwtBuilderUtil.java
package org.kohsuke.github.extras.authorization;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Serializer;
import io.jsonwebtoken.jackson.io.JacksonSerializer;
import io.jsonwebtoken.security.SignatureAlgorithm;
import org.kohsuke.github.GHException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Key;
import java.security.PrivateKey;
import java.time.Instant;
import java.util.Date;
import java.util.logging.Logger;
/**
* This is a util to build a JWT.
*
* <p>
* This class is used to build a JWT using the jjwt library. It uses reflection to support older versions of jjwt. The
* class may be removed once we are sure we no longer need to support pre-0.12.x versions of jjwt.
* </p>
*/
final class JwtBuilderUtil {
private static final Logger LOGGER = Logger.getLogger(JwtBuilderUtil.class.getName());
private static IJwtBuilder builder;
/**
* Build a JWT.
*
* @param issuedAt
* issued at
* @param expiration
* expiration
* @param applicationId
* application id
* @param privateKey
* private key
* @return JWT
*/
static String buildJwt(Instant issuedAt, Instant expiration, String applicationId, PrivateKey privateKey) {
if (builder == null) {
createBuilderImpl(issuedAt, expiration, applicationId, privateKey);
}
return builder.buildJwt(issuedAt, expiration, applicationId, privateKey);
}
private static void createBuilderImpl(Instant issuedAt,
Instant expiration,
String applicationId,
PrivateKey privateKey) {
// Figure out which builder to use and cache it. We don't worry about thread safety here because we're fine if
// the builder is assigned multiple times. The end result will be the same.
try {
builder = new DefaultBuilderImpl();
} catch (NoSuchMethodError | NoClassDefFoundError e) {
LOGGER.warning(
"You are using an outdated version of the io.jsonwebtoken:jjwt-* suite. v0.12.x or later is recommended.");
try {
ReflectionBuilderImpl reflectionBuider = new ReflectionBuilderImpl();
// Build a JWT to eagerly check for any reflection errors.
reflectionBuider.buildJwtWithReflection(issuedAt, expiration, applicationId, privateKey);
builder = reflectionBuider;
} catch (ReflectiveOperationException re) {
throw new GHException(
"Could not build JWT using reflection on io.jsonwebtoken:jjwt-* suite."
+ "The minimum supported version is v0.11.x, v0.12.x or later is recommended.",
re);
}
}
}
/**
* IJwtBuilder interface to isolate loading of JWT classes allowing us to catch and handle linkage errors.
*/
interface IJwtBuilder {
/**
* Build a JWT.
*
* @param issuedAt
* issued at
* @param expiration
* expiration
* @param applicationId
* application id
* @param privateKey
* private key
* @return JWT
*/
String buildJwt(Instant issuedAt, Instant expiration, String applicationId, PrivateKey privateKey);
}
/**
* A class to isolate loading of JWT classes allowing us to catch and handle linkage errors.
*
* Without this class, JwtBuilderUtil.buildJwt() immediately throws NoClassDefFoundError when called. With this
* class the error is thrown when DefaultBuilder.build() is called allowing us to catch and handle it.
*/
private static final class DefaultBuilderImpl implements IJwtBuilder {
/**
* This method builds a JWT using 0.12.x or later versions of jjwt library
*
* @param issuedAt
* issued at
* @param expiration
* expiration
* @param applicationId
* application id
* @param privateKey
* private key
* @return JWT
*/
public String buildJwt(Instant issuedAt, Instant expiration, String applicationId, PrivateKey privateKey) {
// io.jsonwebtoken.security.SignatureAlgorithm is not present in v0.11.x and below.
// Trying to call a method that uses it causes "NoClassDefFoundError" if v0.11.x is being used.
SignatureAlgorithm rs256 = Jwts.SIG.RS256;
JwtBuilder jwtBuilder = Jwts.builder();
jwtBuilder = jwtBuilder.issuedAt(Date.from(issuedAt))
.expiration(Date.from(expiration))
.issuer(applicationId)
.signWith(privateKey, rs256)
.json(new JacksonSerializer<>());
return jwtBuilder.compact();
}
}
/**
* A class to encapsulate building a JWT using reflection.
*/
private static final class ReflectionBuilderImpl implements IJwtBuilder {
private Method setIssuedAtMethod;
private Method setExpirationMethod;
private Method setIssuerMethod;
private Enum<?> rs256SignatureAlgorithm;
private Method signWithMethod;
private Method serializeToJsonMethod;
ReflectionBuilderImpl() throws ReflectiveOperationException {
JwtBuilder jwtBuilder = Jwts.builder();
Class<?> jwtReflectionClass = jwtBuilder.getClass();
setIssuedAtMethod = jwtReflectionClass.getMethod("setIssuedAt", Date.class);
setIssuerMethod = jwtReflectionClass.getMethod("setIssuer", String.class);
setExpirationMethod = jwtReflectionClass.getMethod("setExpiration", Date.class);
Class<?> signatureAlgorithmClass = Class.forName("io.jsonwebtoken.SignatureAlgorithm");
rs256SignatureAlgorithm = createEnumInstance(signatureAlgorithmClass, "RS256");
signWithMethod = jwtReflectionClass.getMethod("signWith", Key.class, signatureAlgorithmClass);
serializeToJsonMethod = jwtReflectionClass.getMethod("serializeToJsonWith", Serializer.class);
}
/**
* This method builds a JWT using older (pre 0.12.x) versions of jjwt library by leveraging reflection.
*
* @param issuedAt
* issued at
* @param expiration
* expiration
* @param applicationId
* application id
* @param privateKey
* private key
* @return JWTBuilder
*/
public String buildJwt(Instant issuedAt, Instant expiration, String applicationId, PrivateKey privateKey) {
try {
return buildJwtWithReflection(issuedAt, expiration, applicationId, privateKey);
} catch (ReflectiveOperationException e) {
// This should never happen. Reflection errors should have been caught during initialization.
throw new GHException("Reflection errors during JWT creation should have been checked already.", e);
}
}
private String buildJwtWithReflection(Instant issuedAt,
Instant expiration,
String applicationId,
PrivateKey privateKey) throws IllegalAccessException, InvocationTargetException {
JwtBuilder jwtBuilder = Jwts.builder();
Object builderObj = jwtBuilder;
builderObj = setIssuedAtMethod.invoke(builderObj, Date.from(issuedAt));
builderObj = setExpirationMethod.invoke(builderObj, Date.from(expiration));
builderObj = setIssuerMethod.invoke(builderObj, applicationId);
builderObj = signWithMethod.invoke(builderObj, privateKey, rs256SignatureAlgorithm);
builderObj = serializeToJsonMethod.invoke(builderObj, new JacksonSerializer<>());
return ((JwtBuilder) builderObj).compact();
}
@SuppressWarnings("unchecked")
private static <T extends Enum<T>> T createEnumInstance(Class<?> type, String name) {
return Enum.valueOf((Class<T>) type, name);
}
}
}