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

Commit 70eafdf

Browse files
committed
Implemented jwt util and set java version to 11
1 parent 0dd0d7e commit 70eafdf

6 files changed

Lines changed: 300 additions & 1 deletion

File tree

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: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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+
public Pair<DecodedJWT, VerificationResult> verify(String token) {
57+
if (token.toLowerCase().startsWith("bearer")) token = token.substring(7);
58+
59+
DecodedJWT jwt;
60+
61+
// Check whether the token is even valid
62+
try {
63+
jwt = JWT.decode(token);
64+
} catch (JWTDecodeException ex) {
65+
return Pair.of(null, VerificationResult.INVALID);
66+
} catch (Exception ex) {
67+
return Pair.of(null, VerificationResult.UNKNOWN);
68+
}
69+
70+
if (jwt.getExpiresAt().before(Date.from(Instant.now()))) return Pair.of(jwt, VerificationResult.EXPIRED);
71+
72+
try {
73+
jwt = verifier.verify(jwt);
74+
} catch (JWTVerificationException ex) {
75+
return Pair.of(jwt, VerificationResult.FAILED);
76+
} catch (Exception ex) {
77+
return Pair.of(null, VerificationResult.UNKNOWN);
78+
}
79+
80+
if (jwt.getClaim("nonce").isNull()) return Pair.of(jwt, VerificationResult.FAILED);
81+
82+
return Pair.of(jwt, VerificationResult.SUCCESS);
83+
}
84+
85+
public String sign(JWTCreator.Builder builder) {
86+
return builder.sign(algorithm);
87+
}
88+
89+
/**
90+
* Creates a default jwt which can be used as a CSRF or refresh token - or whatever u want.
91+
*
92+
* @param subject the subject (user) of this token
93+
* @param expirationTime the amount of <i>expirationTimeUnit</i> the token shall be valid
94+
* @param expirationTimeUnit the time unit used for <i>expirationTime</i>; Use Calendar.XY for this
95+
* @param audience the audience the token is meant for
96+
*
97+
* @return a jwt containing default values
98+
*/
99+
public String create(String subject, int expirationTime, int expirationTimeUnit, String... audience) {
100+
return sign(createBuilder(subject, expirationTime, expirationTimeUnit, audience));
101+
}
102+
103+
/**
104+
* Creates a default jwt which can be used as a CSRF or refresh token - or whatever u want.
105+
*
106+
* To be compliant with most standards, you should set the subject (the user).
107+
*
108+
* @param expirationTime the amount of <i>expirationTimeUnit</i> the token shall be valid
109+
* @param expirationTimeUnit the time unit used for <i>expirationTime</i>; Use Calendar.XY for this
110+
* @param audience the audience the token is meant for
111+
*
112+
* @return a jwt containing default values
113+
*/
114+
public String create(int expirationTime, int expirationTimeUnit, String... audience) {
115+
return sign(createBuilder(expirationTime, expirationTimeUnit, audience));
116+
}
117+
118+
/**
119+
* Creates a default jwt which can be used as a CSRF or refresh token - or whatever u want.
120+
*
121+
* @param subject the subject (user) of this token
122+
* @param expirationTime the amount of <i>expirationTimeUnit</i> the token shall be valid
123+
* @param expirationTimeUnit the time unit used for <i>expirationTime</i>; Use Calendar.XY for this
124+
* @param audience the audience the token is meant for
125+
*
126+
* @return a {@link com.auth0.jwt.JWTCreator.Builder} instance containing default values
127+
*/
128+
public JWTCreator.Builder createBuilder(String subject, int expirationTime, int expirationTimeUnit, String... audience) {
129+
return createBuilder(expirationTime, expirationTimeUnit, audience).withSubject(subject);
130+
}
131+
132+
/**
133+
* Creates a default jwt which can be used as a CSRF or refresh token - or whatever u want.
134+
*
135+
* To be compliant with most standards, you should set the subject (the user).
136+
*
137+
* @param expirationTime the amount of <i>expirationTimeUnit</i> the token shall be valid
138+
* @param expirationTimeUnit the time unit used for <i>expirationTime</i>; Use Calendar.XY for this
139+
* @param audience the audience the token is meant for
140+
*
141+
* @return a {@link com.auth0.jwt.JWTCreator.Builder} instance containing default values
142+
*/
143+
public JWTCreator.Builder createBuilder(int expirationTime, int expirationTimeUnit, String... audience) {
144+
Date today = Date.from(Instant.now());
145+
146+
Calendar expirationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
147+
expirationDate.setTime(today);
148+
expirationDate.add(expirationTimeUnit, expirationTime);
149+
150+
// Audiences
151+
List<String> audienceList = new ArrayList<>(Arrays.asList(audience));
152+
audienceList.addAll(defaultAudience);
153+
154+
byte[] rawNonce = new byte[128];
155+
RANDOM.nextBytes(rawNonce);
156+
String nonce = Base64.getEncoder().encodeToString(rawNonce);
157+
158+
return JWT.create()
159+
.withIssuer(issuer)
160+
.withAudience(audienceList.toArray(String[]::new))
161+
.withIssuedAt(today)
162+
.withClaim("nonce", nonce)
163+
.withExpiresAt(expirationDate.getTime());
164+
}
165+
166+
public enum VerificationResult {
167+
SUCCESS,
168+
FAILED,
169+
INVALID,
170+
EXPIRED,
171+
UNKNOWN
172+
}
173+
174+
}
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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.gewia.common.util;
2+
3+
import lombok.Data;
4+
5+
@Data(staticConstructor = "of")
6+
public class Pair<A, B> {
7+
private final A left;
8+
private final B right;
9+
}

0 commit comments

Comments
 (0)