Skip to content
This repository has been archived by the owner on May 22, 2021. It is now read-only.

Commit

Permalink
Merge pull request #12 from E-Edu/issue/2
Browse files Browse the repository at this point in the history
Implemented jwt util and set java version to 11
  • Loading branch information
steve-hb authored May 7, 2020
2 parents 0951432 + a7d97c1 commit b2afd38
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ jobs:
- name: Setup java
uses: actions/setup-java@v1
with:
java-version: 1.8
java-version: 1.11
- run: mvn clean install -Dcheckstyle.skip -q
2 changes: 1 addition & 1 deletion .github/workflows/checkstyle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ jobs:
- name: Setup java
uses: actions/setup-java@v1
with:
java-version: 1.8
java-version: 1.11
- run: mvn checkstyle:check -q
30 changes: 30 additions & 0 deletions auth/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>gewia-common</artifactId>
<groupId>com.gewia.common</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>auth</artifactId>

<dependencies>
<dependency>
<groupId>com.gewia.common</groupId>
<artifactId>util</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.2</version>
<scope>compile</scope>
</dependency>
</dependencies>

</project>
179 changes: 179 additions & 0 deletions auth/src/main/java/com/gewia/common/auth/jwt/JwtUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package com.gewia.common.auth.jwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.gewia.common.util.Pair;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

public class JwtUtil {

private static final SecureRandom RANDOM = new SecureRandom();

private final String issuer;
private final List<String> defaultAudience;

private final Algorithm algorithm;
private final JWTVerifier verifier;

/**
* Constructor.
*
* @param issuer the organization/person/domain issuing the JWTs
* @param defaultAudience the default audience for every JWT
* @param secret the secret used to sign the JWT
*/
public JwtUtil(String issuer, List<String> defaultAudience, String secret) {
this.issuer = issuer;
this.defaultAudience = defaultAudience;

this.algorithm = Algorithm.HMAC512(secret);
this.verifier = JWT.require(algorithm)
.withIssuer(this.issuer)
.withAudience(this.defaultAudience.toArray(String[]::new))
.build();
}

/**
* Verifies the given <i>token</i> and returns a pair including the decoded token and the result.
*
* @param token the raw json web token
*
* @return a pair including the decoded token and the result
*/
@SuppressWarnings("IllegalCatch")
public Pair<DecodedJWT, VerificationResult> verify(String token) {
if (token.toLowerCase().startsWith("bearer")) token = token.substring(7);

DecodedJWT jwt;

// Check whether the token is even valid
try {
jwt = JWT.decode(token);
} catch (JWTDecodeException ex) {
return Pair.of(null, VerificationResult.INVALID);
} catch (Exception ex) {
return Pair.of(null, VerificationResult.UNKNOWN);
}

if (jwt.getExpiresAt().before(Date.from(Instant.now()))) return Pair.of(jwt, VerificationResult.EXPIRED);

try {
jwt = verifier.verify(jwt);
} catch (JWTVerificationException ex) {
return Pair.of(jwt, VerificationResult.FAILED);
} catch (Exception ex) {
return Pair.of(null, VerificationResult.UNKNOWN);
}

if (jwt.getClaim("nonce").isNull()) return Pair.of(jwt, VerificationResult.FAILED);

return Pair.of(jwt, VerificationResult.SUCCESS);
}

public String sign(JWTCreator.Builder builder) {
return builder.sign(algorithm);
}

/**
* Creates a default jwt which can be used as a CSRF or refresh token - or whatever u want.
*
* @param subject the subject (user) of this token
* @param expirationTime the amount of <i>expirationTimeUnit</i> the token shall be valid
* @param expirationTimeUnit the time unit used for <i>expirationTime</i>; Use Calendar.XY for this
* @param audience the audience the token is meant for
*
* @return a jwt containing default values
*/
public String create(String subject, int expirationTime, int expirationTimeUnit, String... audience) {
return sign(createBuilder(subject, expirationTime, expirationTimeUnit, audience));
}

/**
* Creates a default jwt which can be used as a CSRF or refresh token - or whatever u want.
*
* <p>
* To be compliant with most standards, you should set the subject (the user).
* </p>
*
* @param expirationTime the amount of <i>expirationTimeUnit</i> the token shall be valid
* @param expirationTimeUnit the time unit used for <i>expirationTime</i>; Use Calendar.XY for this
* @param audience the audience the token is meant for
*
* @return a jwt containing default values
*/
public String create(int expirationTime, int expirationTimeUnit, String... audience) {
return sign(createBuilder(expirationTime, expirationTimeUnit, audience));
}

/**
* Creates a default jwt which can be used as a CSRF or refresh token - or whatever u want.
*
* @param subject the subject (user) of this token
* @param expirationTime the amount of <i>expirationTimeUnit</i> the token shall be valid
* @param expirationTimeUnit the time unit used for <i>expirationTime</i>; Use Calendar.XY for this
* @param audience the audience the token is meant for
*
* @return a {@link com.auth0.jwt.JWTCreator.Builder} instance containing default values
*/
public JWTCreator.Builder createBuilder(String subject, int expirationTime, int expirationTimeUnit, String... audience) {
return createBuilder(expirationTime, expirationTimeUnit, audience).withSubject(subject);
}

/**
* Creates a default jwt which can be used as a CSRF or refresh token - or whatever u want.
*
* <p>
* To be compliant with most standards, you should set the subject (the user).
* </p>
*
* @param expirationTime the amount of <i>expirationTimeUnit</i> the token shall be valid
* @param expirationTimeUnit the time unit used for <i>expirationTime</i>; Use Calendar.XY for this
* @param audience the audience the token is meant for
*
* @return a {@link com.auth0.jwt.JWTCreator.Builder} instance containing default values
*/
public JWTCreator.Builder createBuilder(int expirationTime, int expirationTimeUnit, String... audience) {
Date today = Date.from(Instant.now());

Calendar expirationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
expirationDate.setTime(today);
expirationDate.add(expirationTimeUnit, expirationTime);

// Audiences
List<String> audienceList = new ArrayList<>(Arrays.asList(audience));
audienceList.addAll(defaultAudience);

byte[] rawNonce = new byte[128];
RANDOM.nextBytes(rawNonce);
String nonce = Base64.getEncoder().encodeToString(rawNonce);

return JWT.create()
.withIssuer(issuer)
.withAudience(audienceList.toArray(String[]::new))
.withIssuedAt(today)
.withClaim("nonce", nonce)
.withExpiresAt(expirationDate.getTime());
}

public enum VerificationResult {
SUCCESS,
FAILED,
INVALID,
EXPIRED,
UNKNOWN
}

}
69 changes: 69 additions & 0 deletions auth/src/test/java/com/gewia/common/auth/jwt/JwtUtilTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.gewia.common.auth.jwt;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.gewia.common.util.Pair;
import org.junit.Assert;
import org.junit.Test;
import java.util.Calendar;
import java.util.Collections;

public class JwtUtilTest {

@Test
public void testSuccessfulCreation() {
JwtUtil util = new JwtUtil("testIssuer", Collections.singletonList("testAudience"), "SECRET");

String jwt = util.create("testSubject", 5, Calendar.MINUTE);
Assert.assertNotNull(jwt);
Assert.assertFalse(jwt.isBlank());

Pair<DecodedJWT, JwtUtil.VerificationResult> result = util.verify(jwt);
Assert.assertSame(result.getRight(), JwtUtil.VerificationResult.SUCCESS);
}

@Test
public void testManipulatedJWT() {
JwtUtil originalUtil = new JwtUtil("testIssuer", Collections.singletonList("testAudience"), "SECRET");
JwtUtil attackerUtil = new JwtUtil("testIssuer", Collections.singletonList("testAudience"), "SECRET2");

String jwt = attackerUtil.create("victim", 5, Calendar.YEAR);

Pair<DecodedJWT, JwtUtil.VerificationResult> result = originalUtil.verify(jwt);
Assert.assertSame(result.getRight(), JwtUtil.VerificationResult.FAILED);
}

@Test
public void testInvalidSignatureJWT() {
JwtUtil util = new JwtUtil("testIssuer", Collections.singletonList("testAudience"), "SECRET");

String jwt = util.create("testSubject", 5, Calendar.MINUTE);
jwt += "makeThisSignatureInvalid";

Pair<DecodedJWT, JwtUtil.VerificationResult> result = util.verify(jwt);
Assert.assertSame(result.getRight(), JwtUtil.VerificationResult.UNKNOWN); //Base64 decoding exception
}

@Test
public void testInvalidHeaderJWT() {
JwtUtil util = new JwtUtil("testIssuer", Collections.singletonList("testAudience"), "SECRET");

String jwt = util.create("testSubject", 5, Calendar.MINUTE);
jwt = "makeThisSignatureInvalid" + jwt;

Pair<DecodedJWT, JwtUtil.VerificationResult> result = util.verify(jwt);
Assert.assertSame(result.getRight(), JwtUtil.VerificationResult.INVALID);
}

@Test
public void testExpiredJWT() throws InterruptedException {
JwtUtil util = new JwtUtil("testIssuer", Collections.singletonList("testAudience"), "SECRET");

String jwt = util.create("testSubject", 1, Calendar.MILLISECOND);

Thread.sleep(2L);

Pair<DecodedJWT, JwtUtil.VerificationResult> result = util.verify(jwt);
Assert.assertSame(result.getRight(), JwtUtil.VerificationResult.EXPIRED);
}

}
4 changes: 3 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@

<modules>
<module>scope</module>
<module>auth</module>
<module>util</module>
</modules>

<properties>
<java.version>8</java.version>
<java.version>11</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down
15 changes: 15 additions & 0 deletions util/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>gewia-common</artifactId>
<groupId>com.gewia.common</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>util</artifactId>


</project>
11 changes: 11 additions & 0 deletions util/src/main/java/com/gewia/common/util/Pair.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.gewia.common.util;

import lombok.Data;

@Data(staticConstructor = "of")
public class Pair<A, B> {

private final A left;
private final B right;

}

0 comments on commit b2afd38

Please sign in to comment.