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

Commit b2afd38

Browse files
authored
Merge pull request #12 from E-Edu/issue/2
Implemented jwt util and set java version to 11
2 parents 0951432 + a7d97c1 commit b2afd38

8 files changed

Lines changed: 309 additions & 3 deletions

File tree

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ jobs:
1111
- name: Setup java
1212
uses: actions/setup-java@v1
1313
with:
14-
java-version: 1.8
14+
java-version: 1.11
1515
- run: mvn clean install -Dcheckstyle.skip -q

.github/workflows/checkstyle.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ jobs:
1111
- name: Setup java
1212
uses: actions/setup-java@v1
1313
with:
14-
java-version: 1.8
14+
java-version: 1.11
1515
- run: mvn checkstyle:check -q

auth/pom.xml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>gewia-common</artifactId>
7+
<groupId>com.gewia.common</groupId>
8+
<version>1.0</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>auth</artifactId>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>com.gewia.common</groupId>
17+
<artifactId>util</artifactId>
18+
<version>${project.parent.version}</version>
19+
<scope>compile</scope>
20+
</dependency>
21+
22+
<dependency>
23+
<groupId>com.auth0</groupId>
24+
<artifactId>java-jwt</artifactId>
25+
<version>3.10.2</version>
26+
<scope>compile</scope>
27+
</dependency>
28+
</dependencies>
29+
30+
</project>
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package com.gewia.common.auth.jwt;
2+
3+
import com.auth0.jwt.JWT;
4+
import com.auth0.jwt.JWTCreator;
5+
import com.auth0.jwt.JWTVerifier;
6+
import com.auth0.jwt.algorithms.Algorithm;
7+
import com.auth0.jwt.exceptions.JWTDecodeException;
8+
import com.auth0.jwt.exceptions.JWTVerificationException;
9+
import com.auth0.jwt.interfaces.DecodedJWT;
10+
import com.gewia.common.util.Pair;
11+
import java.security.SecureRandom;
12+
import java.time.Instant;
13+
import java.util.ArrayList;
14+
import java.util.Arrays;
15+
import java.util.Base64;
16+
import java.util.Calendar;
17+
import java.util.Date;
18+
import java.util.List;
19+
import java.util.TimeZone;
20+
21+
public class JwtUtil {
22+
23+
private static final SecureRandom RANDOM = new SecureRandom();
24+
25+
private final String issuer;
26+
private final List<String> defaultAudience;
27+
28+
private final Algorithm algorithm;
29+
private final JWTVerifier verifier;
30+
31+
/**
32+
* Constructor.
33+
*
34+
* @param issuer the organization/person/domain issuing the JWTs
35+
* @param defaultAudience the default audience for every JWT
36+
* @param secret the secret used to sign the JWT
37+
*/
38+
public JwtUtil(String issuer, List<String> defaultAudience, String secret) {
39+
this.issuer = issuer;
40+
this.defaultAudience = defaultAudience;
41+
42+
this.algorithm = Algorithm.HMAC512(secret);
43+
this.verifier = JWT.require(algorithm)
44+
.withIssuer(this.issuer)
45+
.withAudience(this.defaultAudience.toArray(String[]::new))
46+
.build();
47+
}
48+
49+
/**
50+
* Verifies the given <i>token</i> and returns a pair including the decoded token and the result.
51+
*
52+
* @param token the raw json web token
53+
*
54+
* @return a pair including the decoded token and the result
55+
*/
56+
@SuppressWarnings("IllegalCatch")
57+
public Pair<DecodedJWT, VerificationResult> verify(String token) {
58+
if (token.toLowerCase().startsWith("bearer")) token = token.substring(7);
59+
60+
DecodedJWT jwt;
61+
62+
// Check whether the token is even valid
63+
try {
64+
jwt = JWT.decode(token);
65+
} catch (JWTDecodeException ex) {
66+
return Pair.of(null, VerificationResult.INVALID);
67+
} catch (Exception ex) {
68+
return Pair.of(null, VerificationResult.UNKNOWN);
69+
}
70+
71+
if (jwt.getExpiresAt().before(Date.from(Instant.now()))) return Pair.of(jwt, VerificationResult.EXPIRED);
72+
73+
try {
74+
jwt = verifier.verify(jwt);
75+
} catch (JWTVerificationException ex) {
76+
return Pair.of(jwt, VerificationResult.FAILED);
77+
} catch (Exception ex) {
78+
return Pair.of(null, VerificationResult.UNKNOWN);
79+
}
80+
81+
if (jwt.getClaim("nonce").isNull()) return Pair.of(jwt, VerificationResult.FAILED);
82+
83+
return Pair.of(jwt, VerificationResult.SUCCESS);
84+
}
85+
86+
public String sign(JWTCreator.Builder builder) {
87+
return builder.sign(algorithm);
88+
}
89+
90+
/**
91+
* Creates a default jwt which can be used as a CSRF or refresh token - or whatever u want.
92+
*
93+
* @param subject the subject (user) of this token
94+
* @param expirationTime the amount of <i>expirationTimeUnit</i> the token shall be valid
95+
* @param expirationTimeUnit the time unit used for <i>expirationTime</i>; Use Calendar.XY for this
96+
* @param audience the audience the token is meant for
97+
*
98+
* @return a jwt containing default values
99+
*/
100+
public String create(String subject, int expirationTime, int expirationTimeUnit, String... audience) {
101+
return sign(createBuilder(subject, expirationTime, expirationTimeUnit, audience));
102+
}
103+
104+
/**
105+
* Creates a default jwt which can be used as a CSRF or refresh token - or whatever u want.
106+
*
107+
* <p>
108+
* To be compliant with most standards, you should set the subject (the user).
109+
* </p>
110+
*
111+
* @param expirationTime the amount of <i>expirationTimeUnit</i> the token shall be valid
112+
* @param expirationTimeUnit the time unit used for <i>expirationTime</i>; Use Calendar.XY for this
113+
* @param audience the audience the token is meant for
114+
*
115+
* @return a jwt containing default values
116+
*/
117+
public String create(int expirationTime, int expirationTimeUnit, String... audience) {
118+
return sign(createBuilder(expirationTime, expirationTimeUnit, audience));
119+
}
120+
121+
/**
122+
* Creates a default jwt which can be used as a CSRF or refresh token - or whatever u want.
123+
*
124+
* @param subject the subject (user) of this token
125+
* @param expirationTime the amount of <i>expirationTimeUnit</i> the token shall be valid
126+
* @param expirationTimeUnit the time unit used for <i>expirationTime</i>; Use Calendar.XY for this
127+
* @param audience the audience the token is meant for
128+
*
129+
* @return a {@link com.auth0.jwt.JWTCreator.Builder} instance containing default values
130+
*/
131+
public JWTCreator.Builder createBuilder(String subject, int expirationTime, int expirationTimeUnit, String... audience) {
132+
return createBuilder(expirationTime, expirationTimeUnit, audience).withSubject(subject);
133+
}
134+
135+
/**
136+
* Creates a default jwt which can be used as a CSRF or refresh token - or whatever u want.
137+
*
138+
* <p>
139+
* To be compliant with most standards, you should set the subject (the user).
140+
* </p>
141+
*
142+
* @param expirationTime the amount of <i>expirationTimeUnit</i> the token shall be valid
143+
* @param expirationTimeUnit the time unit used for <i>expirationTime</i>; Use Calendar.XY for this
144+
* @param audience the audience the token is meant for
145+
*
146+
* @return a {@link com.auth0.jwt.JWTCreator.Builder} instance containing default values
147+
*/
148+
public JWTCreator.Builder createBuilder(int expirationTime, int expirationTimeUnit, String... audience) {
149+
Date today = Date.from(Instant.now());
150+
151+
Calendar expirationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
152+
expirationDate.setTime(today);
153+
expirationDate.add(expirationTimeUnit, expirationTime);
154+
155+
// Audiences
156+
List<String> audienceList = new ArrayList<>(Arrays.asList(audience));
157+
audienceList.addAll(defaultAudience);
158+
159+
byte[] rawNonce = new byte[128];
160+
RANDOM.nextBytes(rawNonce);
161+
String nonce = Base64.getEncoder().encodeToString(rawNonce);
162+
163+
return JWT.create()
164+
.withIssuer(issuer)
165+
.withAudience(audienceList.toArray(String[]::new))
166+
.withIssuedAt(today)
167+
.withClaim("nonce", nonce)
168+
.withExpiresAt(expirationDate.getTime());
169+
}
170+
171+
public enum VerificationResult {
172+
SUCCESS,
173+
FAILED,
174+
INVALID,
175+
EXPIRED,
176+
UNKNOWN
177+
}
178+
179+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.gewia.common.auth.jwt;
2+
3+
import com.auth0.jwt.interfaces.DecodedJWT;
4+
import com.gewia.common.util.Pair;
5+
import org.junit.Assert;
6+
import org.junit.Test;
7+
import java.util.Calendar;
8+
import java.util.Collections;
9+
10+
public class JwtUtilTest {
11+
12+
@Test
13+
public void testSuccessfulCreation() {
14+
JwtUtil util = new JwtUtil("testIssuer", Collections.singletonList("testAudience"), "SECRET");
15+
16+
String jwt = util.create("testSubject", 5, Calendar.MINUTE);
17+
Assert.assertNotNull(jwt);
18+
Assert.assertFalse(jwt.isBlank());
19+
20+
Pair<DecodedJWT, JwtUtil.VerificationResult> result = util.verify(jwt);
21+
Assert.assertSame(result.getRight(), JwtUtil.VerificationResult.SUCCESS);
22+
}
23+
24+
@Test
25+
public void testManipulatedJWT() {
26+
JwtUtil originalUtil = new JwtUtil("testIssuer", Collections.singletonList("testAudience"), "SECRET");
27+
JwtUtil attackerUtil = new JwtUtil("testIssuer", Collections.singletonList("testAudience"), "SECRET2");
28+
29+
String jwt = attackerUtil.create("victim", 5, Calendar.YEAR);
30+
31+
Pair<DecodedJWT, JwtUtil.VerificationResult> result = originalUtil.verify(jwt);
32+
Assert.assertSame(result.getRight(), JwtUtil.VerificationResult.FAILED);
33+
}
34+
35+
@Test
36+
public void testInvalidSignatureJWT() {
37+
JwtUtil util = new JwtUtil("testIssuer", Collections.singletonList("testAudience"), "SECRET");
38+
39+
String jwt = util.create("testSubject", 5, Calendar.MINUTE);
40+
jwt += "makeThisSignatureInvalid";
41+
42+
Pair<DecodedJWT, JwtUtil.VerificationResult> result = util.verify(jwt);
43+
Assert.assertSame(result.getRight(), JwtUtil.VerificationResult.UNKNOWN); //Base64 decoding exception
44+
}
45+
46+
@Test
47+
public void testInvalidHeaderJWT() {
48+
JwtUtil util = new JwtUtil("testIssuer", Collections.singletonList("testAudience"), "SECRET");
49+
50+
String jwt = util.create("testSubject", 5, Calendar.MINUTE);
51+
jwt = "makeThisSignatureInvalid" + jwt;
52+
53+
Pair<DecodedJWT, JwtUtil.VerificationResult> result = util.verify(jwt);
54+
Assert.assertSame(result.getRight(), JwtUtil.VerificationResult.INVALID);
55+
}
56+
57+
@Test
58+
public void testExpiredJWT() throws InterruptedException {
59+
JwtUtil util = new JwtUtil("testIssuer", Collections.singletonList("testAudience"), "SECRET");
60+
61+
String jwt = util.create("testSubject", 1, Calendar.MILLISECOND);
62+
63+
Thread.sleep(2L);
64+
65+
Pair<DecodedJWT, JwtUtil.VerificationResult> result = util.verify(jwt);
66+
Assert.assertSame(result.getRight(), JwtUtil.VerificationResult.EXPIRED);
67+
}
68+
69+
}

pom.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111

1212
<modules>
1313
<module>scope</module>
14+
<module>auth</module>
15+
<module>util</module>
1416
</modules>
1517

1618
<properties>
17-
<java.version>8</java.version>
19+
<java.version>11</java.version>
1820
<maven.compiler.source>${java.version}</maven.compiler.source>
1921
<maven.compiler.target>${java.version}</maven.compiler.target>
2022
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

util/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>gewia-common</artifactId>
7+
<groupId>com.gewia.common</groupId>
8+
<version>1.0</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>util</artifactId>
13+
14+
15+
</project>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.gewia.common.util;
2+
3+
import lombok.Data;
4+
5+
@Data(staticConstructor = "of")
6+
public class Pair<A, B> {
7+
8+
private final A left;
9+
private final B right;
10+
11+
}

0 commit comments

Comments
 (0)