This repository has been archived by the owner on May 22, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12 from E-Edu/issue/2
Implemented jwt util and set java version to 11
- Loading branch information
Showing
8 changed files
with
309 additions
and
3 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
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
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 |
---|---|---|
@@ -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
179
auth/src/main/java/com/gewia/common/auth/jwt/JwtUtil.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 |
---|---|---|
@@ -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
69
auth/src/test/java/com/gewia/common/auth/jwt/JwtUtilTest.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 |
---|---|---|
@@ -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); | ||
} | ||
|
||
} |
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
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 |
---|---|---|
@@ -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> |
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 |
---|---|---|
@@ -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; | ||
|
||
} |