-
Notifications
You must be signed in to change notification settings - Fork 741
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Restructured JwtBuilderUtil based on testing
When I went to run tests on the reflection code I ran into a number of errors. A few were straighfoward fixes but not all. The issue requiring the biggest change as that some JWT suite classes were being loaded as the JwtBuilderUtil class was being loaded, or at least before the buildJwt() method started executing. I had to move the JWT calls into a nested class to delay the loading of JWT suite classes until inside a try-catch where I could handle it as expected.
- Loading branch information
1 parent
de68272
commit 9b02348
Showing
2 changed files
with
113 additions
and
89 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
198 changes: 111 additions & 87 deletions
198
src/main/java/org/kohsuke/github/extras/authorization/JwtBuilderUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,127 +1,151 @@ | ||
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.Method; | ||
import java.security.Key; | ||
import java.security.PrivateKey; | ||
import java.time.Instant; | ||
import java.util.Date; | ||
import java.util.logging.Logger; | ||
|
||
import io.jsonwebtoken.JwtBuilder; | ||
import io.jsonwebtoken.Jwts; | ||
import io.jsonwebtoken.SignatureAlgorithm; | ||
import io.jsonwebtoken.jackson.io.JacksonSerializer; | ||
|
||
/** | ||
* 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 again, once we | ||
* are sure, we do no longer need to support pre-0.12.x versions of jjwt. | ||
* 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()); | ||
|
||
/** | ||
* Get a method from an object. | ||
* Build a JWT. | ||
* | ||
* @param obj object | ||
* @param method method name | ||
* @param params parameters of the method | ||
* @return method | ||
* @throws NoSuchMethodException if the method does not exist | ||
* @param issuedAt | ||
* issued at | ||
* @param expiration | ||
* expiration | ||
* @param applicationId | ||
* application id | ||
* @param privateKey | ||
* private key | ||
* @return JWT | ||
*/ | ||
private static Method getMethod(Object obj, String method, Class<?>... params) throws NoSuchMethodException { | ||
Class<?> type = obj.getClass(); | ||
return type.getMethod(method, params); | ||
} | ||
static String buildJwt(Instant issuedAt, Instant expiration, String applicationId, PrivateKey privateKey) { | ||
|
||
/** | ||
* Check if an object has a method. | ||
* | ||
* @param obj object | ||
* @param method method name | ||
* @param params parameters of the method | ||
* @return true if the method exists | ||
*/ | ||
private static boolean hasMethod(Object obj, String method, Class<?>... params) { | ||
try { | ||
return JwtBuilderUtil.getMethod(obj, method, params) != null; | ||
} catch (NoSuchMethodException e) { | ||
return false; | ||
return DefaultBuilderImpl.buildJwt(issuedAt, expiration, applicationId, privateKey); | ||
} catch (NoSuchMethodError | NoClassDefFoundError e) { | ||
LOGGER.info( | ||
"You are using an outdated version of the io.jsonwebtoken:jjwt-* suite. v0.12.x or later is recommended."); | ||
} | ||
|
||
// older jjwt library versions | ||
try { | ||
return ReflectionBuilderImpl.buildJwt(issuedAt, expiration, applicationId, privateKey); | ||
} catch (SecurityException | 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); | ||
} | ||
} | ||
|
||
/** | ||
* Build a JWT. | ||
* A class to isolate loading of JWT classes allowing us to catch and handle linkage errors. | ||
* | ||
* @param issuedAt issued at | ||
* @param expiration expiration | ||
* @param applicationId application id | ||
* @param privateKey private key | ||
* @return JWT | ||
* 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. | ||
*/ | ||
static String buildJwt(Instant issuedAt, Instant expiration, String applicationId, PrivateKey privateKey) { | ||
JwtBuilder jwtBuilder = Jwts.builder(); | ||
if (JwtBuilderUtil.hasMethod(jwtBuilder, "issuedAt", Date.class)) { | ||
private static class DefaultBuilderImpl { | ||
|
||
/** | ||
* 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 | ||
*/ | ||
private static 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, Jwts.SIG.RS256); | ||
return jwtBuilder.json(new JacksonSerializer<>()).compact(); | ||
} | ||
|
||
LOGGER.warning( | ||
"You are using an outdated version of the io.jsonwebtoken:jjwt-* suite. Please consider an update."); | ||
|
||
// older jjwt library versions | ||
try { | ||
return JwtBuilderUtil.buildByReflection(jwtBuilder, issuedAt, expiration, applicationId, privateKey); | ||
} catch (ReflectiveOperationException e) { | ||
throw new JwtReflectiveBuilderException( | ||
"Exception building a JWT with reflective access to outdated versions of the io.jsonwebtoken:jjwt-* suite. Please consider an update.", | ||
e); | ||
.signWith(privateKey, rs256) | ||
.json(new JacksonSerializer<>()); | ||
return jwtBuilder.compact(); | ||
} | ||
} | ||
|
||
/** | ||
* This method builds a JWT using older (pre 0.12.x) versions of jjwt library by | ||
* leveraging reflection. | ||
* | ||
* @param jwtBuilder builder object | ||
* @param issuedAt issued at | ||
* @param expiration expiration | ||
* @param applicationId application id | ||
* @param privateKey private key | ||
* @return JWT | ||
* @throws ReflectiveOperationException if reflection fails | ||
* A class to encapsulate building a JWT using reflection. | ||
*/ | ||
private static String buildByReflection(JwtBuilder jwtBuilder, Instant issuedAt, Instant expiration, | ||
String applicationId, | ||
PrivateKey privateKey) throws ReflectiveOperationException { | ||
|
||
Object builderObj = jwtBuilder; | ||
|
||
Method setIssuedAtMethod = JwtBuilderUtil.getMethod(builderObj, "setIssuedAt", Date.class); | ||
builderObj = setIssuedAtMethod.invoke(builderObj, Date.from(issuedAt)); | ||
|
||
Method setExpirationMethod = JwtBuilderUtil.getMethod(builderObj, "setExpiration", Date.class); | ||
builderObj = setExpirationMethod.invoke(builderObj, Date.from(expiration)); | ||
|
||
Method setIssuerMethod = JwtBuilderUtil.getMethod(builderObj, "setIssuer", String.class); | ||
builderObj = setIssuerMethod.invoke(builderObj, applicationId); | ||
|
||
Method signWithMethod = JwtBuilderUtil.getMethod(builderObj, "signWith", PrivateKey.class, | ||
SignatureAlgorithm.class); | ||
builderObj = signWithMethod.invoke(builderObj, privateKey, SignatureAlgorithm.RS256); | ||
|
||
Method serializeToJsonMethod = JwtBuilderUtil.getMethod(builderObj, "serializeToJsonWith", | ||
JacksonSerializer.class); | ||
builderObj = serializeToJsonMethod.invoke(builderObj, new JacksonSerializer<>()); | ||
private static class ReflectionBuilderImpl { | ||
/** | ||
* 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 | ||
* @throws ReflectiveOperationException | ||
* if reflection fails | ||
*/ | ||
private static String buildJwt(Instant issuedAt, | ||
Instant expiration, | ||
String applicationId, | ||
PrivateKey privateKey) throws ReflectiveOperationException { | ||
|
||
JwtBuilder jwtBuilder = Jwts.builder(); | ||
Class<?> jwtReflectionClass = jwtBuilder.getClass(); | ||
|
||
Method setIssuedAtMethod = jwtReflectionClass.getMethod("setIssuedAt", Date.class); | ||
Method setIssuerMethod = jwtReflectionClass.getMethod("setIssuer", String.class); | ||
Method setExpirationMethod = jwtReflectionClass.getMethod("setExpiration", Date.class); | ||
Class<?> signatureAlgorithmClass = Class.forName("io.jsonwebtoken.SignatureAlgorithm"); | ||
Enum<?> rs256SignatureAlgorithm = createEnumInstance(signatureAlgorithmClass, "RS256"); | ||
Method signWithMethod = jwtReflectionClass.getMethod("signWith", Key.class, signatureAlgorithmClass); | ||
Method serializeToJsonMethod = jwtReflectionClass.getMethod("serializeToJsonWith", Serializer.class); | ||
|
||
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(); | ||
} | ||
|
||
JwtBuilder resultBuilder = (JwtBuilder) builderObj; | ||
return resultBuilder.compact(); | ||
@SuppressWarnings("unchecked") | ||
private static <T extends Enum<T>> T createEnumInstance(Class<?> type, String name) { | ||
return Enum.valueOf((Class<T>) type, name); | ||
} | ||
} | ||
} |