|
| 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 | +} |
0 commit comments