From 1395fbdccdffedc774877c46062e69631f4914cc Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 16:11:36 -0300 Subject: [PATCH 01/69] First commit. --- .gitignore | 165 ++++++++++++++++++ LICENSE.md | 20 +++ README.md | 48 +++++ pom.xml | 33 ++++ src/main/java/com/auth0/jwt/JWTVerifier.java | 153 ++++++++++++++++ .../java/com/auth0/jwt/JWTVerifierTest.java | 151 ++++++++++++++++ 6 files changed, 570 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/com/auth0/jwt/JWTVerifier.java create mode 100644 src/test/java/com/auth0/jwt/JWTVerifierTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..54b2dc3f --- /dev/null +++ b/.gitignore @@ -0,0 +1,165 @@ +node_modules + +# Ignore Java and IntelliJ IDEA stuff +.idea +target +*.iml + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +packages + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + + +#LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac desktop service store files +.DS_Store diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..5a27aaff --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 AUTH10 LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..67995a11 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# Java JWT + +An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html). + +This was developed against draft-ietf-oauth-json-web-token-08 + +### Usage + +```java + public class Application { + public static void main (String [] args) { + Map decodedPayload = new JWTVerifier("secret", "audience").verify("my-token"); + + // Get custom fields from decoded Payload + System.out.println(decodedPayload.get("name")); + } + } +`` + +### FAQ + + +#### Why another JSON Web Token implementation for Java? +We think that current JWT implementations are either too complex or not enough tested. We want something simple with the right number of abstractions. + +## License + +The MIT License (MIT) + +Copyright (c) 2013 AUTH10 LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..b126c993 --- /dev/null +++ b/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + com.auth0 + java-jwt + 0.1-SNAPSHOT + + + + + com.fasterxml.jackson.core + jackson-databind + 2.0.0 + + + commons-codec + commons-codec + 1.4 + + + + junit + junit + 4.11 + test + + + + + \ No newline at end of file diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java new file mode 100644 index 00000000..8f8e1f1f --- /dev/null +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -0,0 +1,153 @@ +package com.auth0.jwt; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * JWT Java Implementation + *

+ * Adapted from https://bitbucket.org/lluisfaja/javajwt/wiki/Home + * See JWTVerifier.java + */ +public class JWTVerifier { + + private final String secret; + private final String audience; + private final String issuer; + private final Base64 decoder; + + private final ObjectMapper mapper; + + private Map algorithms; + + public JWTVerifier(String secret, String audience, String issuer) { + if (secret == null || "".equals(secret)) { + throw new IllegalArgumentException("Secret cannot be null or empty"); + } + + decoder = new Base64(true); + mapper = new ObjectMapper(); + + algorithms = new HashMap(); + algorithms.put("HS256", "HmacSHA256"); + algorithms.put("HS384", "HmacSHA384"); + algorithms.put("HS512", "HmacSHA512"); + + this.secret = secret; + this.audience = audience; + this.issuer = issuer; + } + + public JWTVerifier(String secret, String audience) { + this(secret, audience, null); + } + + public JWTVerifier(String secret) { + this(secret, null, null); + } + + /** + * Performs JWT validation + * + * @param token token to verify + * @throws SignatureException when signature is invalid + * @throws IllegalStateException when token's structure, expiration, issuer or audience are invalid + */ + public Map verify(String token) + throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, + IOException, SignatureException { + if (token == null || "".equals(token)) { + throw new IllegalStateException("token not set"); + } + + String[] pieces = token.split("\\."); + + // check number of segments + if (pieces.length != 3) { + throw new IllegalStateException("Wrong number of segments: " + pieces.length); + } + + // get JWTHeader JSON object. Extract algorithm + Map jwtHeader = decodeAndParse(pieces[0]); + + String algorithm = getAlgorithm(jwtHeader); + + // get JWTClaims JSON object + Map jwtPayload = decodeAndParse(pieces[1]); + + // check signature + verifySignature(pieces, algorithm); + + // additional JWTClaims checks + verifyExpiration(jwtPayload); + verifyIssuer(jwtPayload); + verifyAudience(jwtPayload); + + return jwtPayload; + } + + void verifySignature(String[] pieces, String algorithm) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + Mac hmac = Mac.getInstance(algorithm); + hmac.init(new SecretKeySpec(decoder.decodeBase64(secret), algorithm)); + byte[] sig = hmac.doFinal(new StringBuilder(pieces[0]).append(".").append(pieces[1]).toString().getBytes()); + + if (!Arrays.equals(sig, decoder.decodeBase64(pieces[2]))) { + throw new SignatureException("signature verification failed"); + } + } + + void verifyExpiration(Map jwtClaims) { + long expiration = Long.parseLong(jwtClaims.get("exp")); + if (expiration != 0 && System.currentTimeMillis() / 1000L >= expiration) { + throw new IllegalStateException("jwt expired"); + } + } + + void verifyIssuer(Map jwtClaims) { + String issuerFromToken = jwtClaims.get("iss"); + + if (issuerFromToken != null && issuer != null && !issuer.equals(issuerFromToken)) { + throw new IllegalStateException("jwt issuer invalid"); + } + } + + void verifyAudience(Map jwtClaims) { + String audienceFromToken = jwtClaims.get("aud"); + + if (audienceFromToken != null && !audience.equals(audienceFromToken)) { + throw new IllegalStateException("jwt audience invalid"); + } + } + + String getAlgorithm(Map jwtHeader) { + String algorithmName = jwtHeader.get("alg"); + + if (jwtHeader.get("alg") == null) { + throw new IllegalStateException("algorithm not set"); + } + + if (algorithms.get(algorithmName) == null) { + throw new IllegalStateException("unsupported algorithm"); + } + + return algorithms.get(algorithmName); + } + + Map decodeAndParse(String b64String) throws IOException { + String jsonString = new String(decoder.decodeBase64(b64String), "UTF-8"); + TypeReference> typeRef = new TypeReference< HashMap >() {}; + Map jwtHeader = mapper.readValue(jsonString, typeRef); + return jwtHeader; + } +} \ No newline at end of file diff --git a/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/src/test/java/com/auth0/jwt/JWTVerifierTest.java new file mode 100644 index 00000000..dd1e031f --- /dev/null +++ b/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -0,0 +1,151 @@ +package com.auth0.jwt; + +import org.apache.commons.codec.binary.Base64; +import org.junit.Test; + +import java.security.SignatureException; +import java.util.Collections; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class JWTVerifierTest { + + @Test(expected = IllegalArgumentException.class) + public void constructorShouldFailOnNullSecret() { + new JWTVerifier(null); + } + + @Test(expected = IllegalArgumentException.class) + public void constructorShouldFailOnEmptySecret() { + new JWTVerifier(""); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailOn1Segments() throws Exception { + new JWTVerifier("such secret").verify("crypto"); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailOn2Segments() throws Exception { + new JWTVerifier("such secret").verify("much.crypto"); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailOn4Segments() throws Exception { + new JWTVerifier("such secret").verify("much.crypto.so.token"); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailOnEmptyStringToken() throws Exception { + new JWTVerifier("such secret").verify(""); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailOnNullToken() throws Exception { + new JWTVerifier("such secret").verify(null); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailIfAlgorithmIsNotSetOnToken() throws Exception { + new JWTVerifier("such secret").getAlgorithm(Collections.emptyMap()); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailIfAlgorithmIsNotSupported() throws Exception { + new JWTVerifier("such secret").getAlgorithm(Collections.singletonMap("alg", "doge-crypt")); + } + + @Test + public void shouldWorkIfAlgorithmIsSupported() throws Exception { + new JWTVerifier("such secret").getAlgorithm(Collections.singletonMap("alg", "HS256")); + } + + @Test(expected = SignatureException.class) + public void shouldFailOnInvalidSignature() throws Exception { + final String jws = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" + + "." + + "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt" + + "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ" + + "." + + "suchsignature_plzvalidate_zomgtokens"; + String secret = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"; + new JWTVerifier(secret, "audience").verifySignature(jws.split("\\."), "HmacSHA256"); + } + + @Test + public void shouldVerifySignature() throws Exception { + final String jws = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" + + "." + + "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt" + + "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ" + + "." + + "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; + final String secret = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"; + new JWTVerifier(secret, "audience") + .verifySignature(jws.split("\\."), "HmacSHA256"); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailWhenExpired1SecondAgo() throws Exception { + new JWTVerifier("such secret").verifyExpiration( + Collections.singletonMap("exp", Long.toString(System.currentTimeMillis() / 1000L - 1L))); + } + + @Test + public void shouldVerifyExpiration() throws Exception { + new JWTVerifier("such secret").verifyExpiration( + Collections.singletonMap("exp", Long.toString(System.currentTimeMillis() / 1000L + 50L))); + } + + @Test + public void shouldVerifyIssuer() throws Exception { + new JWTVerifier("such secret", "amaze audience", "very issuer") + .verifyIssuer(Collections.singletonMap("iss", "very issuer")); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailIssuer() throws Exception { + new JWTVerifier("such secret", "amaze audience", "very issuer") + .verifyIssuer(Collections.singletonMap("iss", "wow")); + } + + @Test + public void shouldVerifyIssuerWhenNotFoundInClaimsSet() throws Exception { + new JWTVerifier("such secret", "amaze audience", "very issuer") + .verifyIssuer(Collections.emptyMap()); + } + + @Test + public void shouldVerifyAudience() throws Exception { + new JWTVerifier("such secret", "amaze audience") + .verifyAudience(Collections.singletonMap("aud", "amaze audience")); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailAudience() throws Exception { + new JWTVerifier("such secret", "amaze audience") + .verifyAudience(Collections.singletonMap("aud", "wow")); + } + + @Test + public void shouldVerifyAudienceWhenNotFoundInClaimsSet() throws Exception { + new JWTVerifier("such secret", "amaze audience") + .verifyAudience(Collections.emptyMap()); + } + + @Test + public void decodeAndParse() throws Exception { + final Base64 encoder = new Base64(true); + final String encodedJSON = new String(encoder.encode("{\"some\": \"json\", \"number\": 123}".getBytes())); + final JWTVerifier jwtVerifier = new JWTVerifier("secret", "audience"); + + final Map decodedJSON = jwtVerifier.decodeAndParse(encodedJSON); + + assertEquals("json", decodedJSON.get("some")); + assertEquals(null, decodedJSON.get("unexisting_property")); + assertEquals("123", decodedJSON.get("number")); + } + + +} From e36522a977b40a24c682d5d1090fbd0578b6d816 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 17:12:58 -0200 Subject: [PATCH 02/69] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 67995a11..3743bc2c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html). -This was developed against draft-ietf-oauth-json-web-token-08 +This was developed against `draft-ietf-oauth-json-web-token-08`. ### Usage @@ -15,7 +15,7 @@ This was developed against draft-ietf-oauth-json-web-token-08 System.out.println(decodedPayload.get("name")); } } -`` +``` ### FAQ From 2e234db0b940c790f1f17cb86becce558c740ccc Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 17:15:25 -0200 Subject: [PATCH 03/69] Update README.md --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3743bc2c..04244eda 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,16 @@ This was developed against `draft-ietf-oauth-json-web-token-08`. ```java public class Application { public static void main (String [] args) { - Map decodedPayload = new JWTVerifier("secret", "audience").verify("my-token"); - - // Get custom fields from decoded Payload - System.out.println(decodedPayload.get("name")); + try { + Map decodedPayload = new JWTVerifier("secret", "audience").verify("my-token"); + + // Get custom fields from decoded Payload + System.out.println(decodedPayload.get("name")); + } catch (SignatureException signatureException) { + System.err.println("Invalid signature!"); + } catch (IllegalStateException illegalStateException) { + System.err.println("Invalid Token! " + illegalStateException); + } } } ``` From 25959995a9dfb03f6fa303ea6eb7ef9b7454b19d Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 17:15:47 -0200 Subject: [PATCH 04/69] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 04244eda..5ef74416 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,8 @@ This was developed against `draft-ietf-oauth-json-web-token-08`. public class Application { public static void main (String [] args) { try { - Map decodedPayload = new JWTVerifier("secret", "audience").verify("my-token"); + Map decodedPayload = + new JWTVerifier("secret", "audience").verify("my-token"); // Get custom fields from decoded Payload System.out.println(decodedPayload.get("name")); From ef353340662460015ca6957174aefa3e3b1082a9 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 16:59:11 -0300 Subject: [PATCH 05/69] Making verify method return Map --- README.md | 2 +- src/main/java/com/auth0/jwt/JWTVerifier.java | 29 +++++++-------- .../java/com/auth0/jwt/JWTVerifierTest.java | 36 +++++++++++-------- 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 5ef74416..c1c05d63 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This was developed against `draft-ietf-oauth-json-web-token-08`. public class Application { public static void main (String [] args) { try { - Map decodedPayload = + Map decodedPayload = new JWTVerifier("secret", "audience").verify("my-token"); // Get custom fields from decoded Payload diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 8f8e1f1f..7ecb3758 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -1,6 +1,7 @@ package com.auth0.jwt; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.binary.Base64; @@ -79,12 +80,12 @@ public Map verify(String token) } // get JWTHeader JSON object. Extract algorithm - Map jwtHeader = decodeAndParse(pieces[0]); + JsonNode jwtHeader = decodeAndParse(pieces[0]); String algorithm = getAlgorithm(jwtHeader); // get JWTClaims JSON object - Map jwtPayload = decodeAndParse(pieces[1]); + JsonNode jwtPayload = decodeAndParse(pieces[1]); // check signature verifySignature(pieces, algorithm); @@ -94,7 +95,7 @@ public Map verify(String token) verifyIssuer(jwtPayload); verifyAudience(jwtPayload); - return jwtPayload; + return mapper.treeToValue(jwtPayload, Map.class); } void verifySignature(String[] pieces, String algorithm) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { @@ -107,31 +108,32 @@ void verifySignature(String[] pieces, String algorithm) throws NoSuchAlgorithmEx } } - void verifyExpiration(Map jwtClaims) { - long expiration = Long.parseLong(jwtClaims.get("exp")); + void verifyExpiration(JsonNode jwtClaims) { + final long expiration = jwtClaims.has("exp") ? jwtClaims.get("exp").asLong(0) : 0; + if (expiration != 0 && System.currentTimeMillis() / 1000L >= expiration) { throw new IllegalStateException("jwt expired"); } } - void verifyIssuer(Map jwtClaims) { - String issuerFromToken = jwtClaims.get("iss"); + void verifyIssuer(JsonNode jwtClaims) { + final String issuerFromToken = jwtClaims.has("iss") ? jwtClaims.get("iss").asText() : null; if (issuerFromToken != null && issuer != null && !issuer.equals(issuerFromToken)) { throw new IllegalStateException("jwt issuer invalid"); } } - void verifyAudience(Map jwtClaims) { - String audienceFromToken = jwtClaims.get("aud"); + void verifyAudience(JsonNode jwtClaims) { + final String audienceFromToken = jwtClaims.has("aud") ? jwtClaims.get("aud").asText() : null; if (audienceFromToken != null && !audience.equals(audienceFromToken)) { throw new IllegalStateException("jwt audience invalid"); } } - String getAlgorithm(Map jwtHeader) { - String algorithmName = jwtHeader.get("alg"); + String getAlgorithm(JsonNode jwtHeader) { + final String algorithmName = jwtHeader.has("alg") ? jwtHeader.get("alg").asText() : null; if (jwtHeader.get("alg") == null) { throw new IllegalStateException("algorithm not set"); @@ -144,10 +146,9 @@ String getAlgorithm(Map jwtHeader) { return algorithms.get(algorithmName); } - Map decodeAndParse(String b64String) throws IOException { + JsonNode decodeAndParse(String b64String) throws IOException { String jsonString = new String(decoder.decodeBase64(b64String), "UTF-8"); - TypeReference> typeRef = new TypeReference< HashMap >() {}; - Map jwtHeader = mapper.readValue(jsonString, typeRef); + JsonNode jwtHeader = mapper.readValue(jsonString, JsonNode.class); return jwtHeader; } } \ No newline at end of file diff --git a/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/src/test/java/com/auth0/jwt/JWTVerifierTest.java index dd1e031f..fa8b3205 100644 --- a/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -1,5 +1,8 @@ package com.auth0.jwt; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.codec.binary.Base64; import org.junit.Test; @@ -48,17 +51,17 @@ public void shouldFailOnNullToken() throws Exception { @Test(expected = IllegalStateException.class) public void shouldFailIfAlgorithmIsNotSetOnToken() throws Exception { - new JWTVerifier("such secret").getAlgorithm(Collections.emptyMap()); + new JWTVerifier("such secret").getAlgorithm(JsonNodeFactory.instance.objectNode()); } @Test(expected = IllegalStateException.class) public void shouldFailIfAlgorithmIsNotSupported() throws Exception { - new JWTVerifier("such secret").getAlgorithm(Collections.singletonMap("alg", "doge-crypt")); + new JWTVerifier("such secret").getAlgorithm(createSingletonJSONNode("alg", "doge-crypt")); } @Test public void shouldWorkIfAlgorithmIsSupported() throws Exception { - new JWTVerifier("such secret").getAlgorithm(Collections.singletonMap("alg", "HS256")); + new JWTVerifier("such secret").getAlgorithm(createSingletonJSONNode("alg", "HS256")); } @Test(expected = SignatureException.class) @@ -89,49 +92,49 @@ public void shouldVerifySignature() throws Exception { @Test(expected = IllegalStateException.class) public void shouldFailWhenExpired1SecondAgo() throws Exception { new JWTVerifier("such secret").verifyExpiration( - Collections.singletonMap("exp", Long.toString(System.currentTimeMillis() / 1000L - 1L))); + createSingletonJSONNode("exp", Long.toString(System.currentTimeMillis() / 1000L - 1L))); } @Test public void shouldVerifyExpiration() throws Exception { new JWTVerifier("such secret").verifyExpiration( - Collections.singletonMap("exp", Long.toString(System.currentTimeMillis() / 1000L + 50L))); + createSingletonJSONNode("exp", Long.toString(System.currentTimeMillis() / 1000L + 50L))); } @Test public void shouldVerifyIssuer() throws Exception { new JWTVerifier("such secret", "amaze audience", "very issuer") - .verifyIssuer(Collections.singletonMap("iss", "very issuer")); + .verifyIssuer(createSingletonJSONNode("iss", "very issuer")); } @Test(expected = IllegalStateException.class) public void shouldFailIssuer() throws Exception { new JWTVerifier("such secret", "amaze audience", "very issuer") - .verifyIssuer(Collections.singletonMap("iss", "wow")); + .verifyIssuer(createSingletonJSONNode("iss", "wow")); } @Test public void shouldVerifyIssuerWhenNotFoundInClaimsSet() throws Exception { new JWTVerifier("such secret", "amaze audience", "very issuer") - .verifyIssuer(Collections.emptyMap()); + .verifyIssuer(JsonNodeFactory.instance.objectNode()); } @Test public void shouldVerifyAudience() throws Exception { new JWTVerifier("such secret", "amaze audience") - .verifyAudience(Collections.singletonMap("aud", "amaze audience")); + .verifyAudience(createSingletonJSONNode("aud", "amaze audience")); } @Test(expected = IllegalStateException.class) public void shouldFailAudience() throws Exception { new JWTVerifier("such secret", "amaze audience") - .verifyAudience(Collections.singletonMap("aud", "wow")); + .verifyAudience(createSingletonJSONNode("aud", "wow")); } @Test public void shouldVerifyAudienceWhenNotFoundInClaimsSet() throws Exception { new JWTVerifier("such secret", "amaze audience") - .verifyAudience(Collections.emptyMap()); + .verifyAudience(JsonNodeFactory.instance.objectNode()); } @Test @@ -140,12 +143,17 @@ public void decodeAndParse() throws Exception { final String encodedJSON = new String(encoder.encode("{\"some\": \"json\", \"number\": 123}".getBytes())); final JWTVerifier jwtVerifier = new JWTVerifier("secret", "audience"); - final Map decodedJSON = jwtVerifier.decodeAndParse(encodedJSON); + final JsonNode decodedJSON = jwtVerifier.decodeAndParse(encodedJSON); - assertEquals("json", decodedJSON.get("some")); + assertEquals("json", decodedJSON.get("some").asText()); assertEquals(null, decodedJSON.get("unexisting_property")); - assertEquals("123", decodedJSON.get("number")); + assertEquals("123", decodedJSON.get("number").asText()); } + public static JsonNode createSingletonJSONNode(String key, String value) { + final ObjectNode jsonNodes = JsonNodeFactory.instance.objectNode(); + jsonNodes.put(key, value); + return jsonNodes; + } } From 1d2ac94ed069b937fddf2e982e24fe0a1f57cc90 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 18:58:59 -0200 Subject: [PATCH 06/69] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index c1c05d63..340f3f24 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # Java JWT -An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html). - -This was developed against `draft-ietf-oauth-json-web-token-08`. +An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) developed against `draft-ietf-oauth-json-web-token-08`. ### Usage From b7e79dc8711c7d293deae22b22d2867b09c0407e Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 18:05:03 -0300 Subject: [PATCH 07/69] Making changes to pom.xml for release. --- pom.xml | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/pom.xml b/pom.xml index b126c993..ef60a518 100644 --- a/pom.xml +++ b/pom.xml @@ -4,10 +4,48 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + org.sonatype.oss + oss-parent + 9 + + com.auth0 java-jwt 0.1-SNAPSHOT + Java JWT + Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. + http://www.jwt.io + + + 1.6 + + + + + The MIT License + http://www.opensource.org/licenses/mit-license.php + repo + + + + + + Alberto Pose + pose + + Developer + + + + + + https://github.com/auth0/java-jwt + scm:git:git@github.com:auth0/java-jwt.git + scm:git:git@github.com:auth0/java-jwt.git + + @@ -29,5 +67,18 @@ + + + + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + UTF-8 + + + + \ No newline at end of file From efe0630b87a3d240a93851fc8e760e6c432c0c43 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 18:05:58 -0300 Subject: [PATCH 08/69] Adding Maven Coordinates section. --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 340f3f24..6840c908 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,19 @@ An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-o } ``` +#### Maven coordinates? + +Yes, here you are: + +```xml + + com.auth0 + java-jwt + 0.1 + +``` + + ### FAQ From 90b0f883329acac7616bff05f858618938e368e8 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 18:08:49 -0300 Subject: [PATCH 09/69] [maven-release-plugin] prepare release java-jwt-0.1 --- pom.xml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index ef60a518..21b3bb2f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,5 @@ - + 4.0.0 @@ -12,7 +10,7 @@ com.auth0 java-jwt - 0.1-SNAPSHOT + 0.1 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 72f8707f7a649aa3070f3ab828f53daff1d7a989 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 7 Mar 2014 18:08:56 -0300 Subject: [PATCH 10/69] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 21b3bb2f..efa6c134 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.auth0 java-jwt - 0.1 + 0.2-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 90c7313a96b7c284de6e4d9bc7a70cef3633506e Mon Sep 17 00:00:00 2001 From: Matias Woloski Date: Fri, 7 Mar 2014 19:13:34 -0200 Subject: [PATCH 11/69] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 6840c908..2ed3675b 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,9 @@ Yes, here you are: ``` +### Credits + +Most of the code have been written by Luis Faja . We just wrapped it on a nicer interface and published it to maven. We'll be adding support for signing and other algorithms in the future. ### FAQ From 65af61b0fbae3f542adbdef471387f982c7751ac Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sat, 8 Mar 2014 11:27:17 -0300 Subject: [PATCH 12/69] Oops, changing wrong verify method signature. --- src/main/java/com/auth0/jwt/JWTVerifier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 7ecb3758..140291df 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -65,7 +65,7 @@ public JWTVerifier(String secret) { * @throws SignatureException when signature is invalid * @throws IllegalStateException when token's structure, expiration, issuer or audience are invalid */ - public Map verify(String token) + public Map verify(String token) throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, IOException, SignatureException { if (token == null || "".equals(token)) { From 9035adcb11d1f56b46a4c53723cf3b2b941818db Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sat, 8 Mar 2014 11:27:57 -0300 Subject: [PATCH 13/69] [maven-release-plugin] prepare release java-jwt-0.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index efa6c134..fda1bf84 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.auth0 java-jwt - 0.2-SNAPSHOT + 0.2 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From e2c8d541bf001617ef3e9a5034452eec47b1553a Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sat, 8 Mar 2014 11:28:03 -0300 Subject: [PATCH 14/69] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fda1bf84..db2b48a2 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.auth0 java-jwt - 0.2 + 0.3-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 722f939f718e446f15843517ecba8859f88245e8 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sat, 8 Mar 2014 12:37:57 -0200 Subject: [PATCH 15/69] Version bump --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ed3675b..e467584a 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Yes, here you are: com.auth0 java-jwt - 0.1 + 0.2 ``` From d97997761f3b189d331d5533e89526631aadf763 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sun, 9 Mar 2014 15:13:51 -0200 Subject: [PATCH 16/69] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e467584a..fa341a44 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ We think that current JWT implementations are either too complex or not enough t The MIT License (MIT) -Copyright (c) 2013 AUTH10 LLC +Copyright (c) 2014 AUTH10 LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 9d4da17549079bf341e39b27e319bf95bfb0e3b0 Mon Sep 17 00:00:00 2001 From: Matias Woloski Date: Mon, 10 Mar 2014 12:55:17 -0200 Subject: [PATCH 17/69] Update LICENSE.md --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index 5a27aaff..c1bf8861 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 AUTH10 LLC +Copyright (c) 2013 Auth0, Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From 18e771412b27bbc3397b49af09f5d6915c0fdf27 Mon Sep 17 00:00:00 2001 From: Matias Woloski Date: Mon, 10 Mar 2014 12:55:44 -0200 Subject: [PATCH 18/69] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa341a44..a0e21a31 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ We think that current JWT implementations are either too complex or not enough t The MIT License (MIT) -Copyright (c) 2014 AUTH10 LLC +Copyright (c) 2014 Auth0, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From a7391e46a2db396adc0ca30dae1a88c7fa7862df Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Mon, 10 Mar 2014 13:15:55 -0200 Subject: [PATCH 19/69] Update LICENSE.md --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index c1bf8861..8258c89c 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 Auth0, Inc +Copyright (c) 2014 Auth0, Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From 17f70e9c7ea031e5f6641cadc65c4b0780a5045b Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sat, 3 May 2014 10:37:32 -0300 Subject: [PATCH 20/69] Update README.md --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a0e21a31..14404e70 100644 --- a/README.md +++ b/README.md @@ -5,21 +5,21 @@ An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-o ### Usage ```java - public class Application { - public static void main (String [] args) { - try { - Map decodedPayload = - new JWTVerifier("secret", "audience").verify("my-token"); +public class Application { + public static void main (String [] args) { + try { + Map decodedPayload = + new JWTVerifier("secret", "audience").verify("my-token"); - // Get custom fields from decoded Payload - System.out.println(decodedPayload.get("name")); - } catch (SignatureException signatureException) { - System.err.println("Invalid signature!"); - } catch (IllegalStateException illegalStateException) { - System.err.println("Invalid Token! " + illegalStateException); - } + // Get custom fields from decoded Payload + System.out.println(decodedPayload.get("name")); + } catch (SignatureException signatureException) { + System.err.println("Invalid signature!"); + } catch (IllegalStateException illegalStateException) { + System.err.println("Invalid Token! " + illegalStateException); } } +} ``` #### Maven coordinates? From e4271d01ec13f1f0a5a4ce57569687720b008836 Mon Sep 17 00:00:00 2001 From: Michele Orsi Date: Sun, 22 Jun 2014 01:16:45 +0200 Subject: [PATCH 21/69] updated with package provided in auth0/java-jwt#1 by @woloski --- .gitignore | 4 + pom.xml | 159 +++++++++++------- src/main/java/com/auth0/jwt/Algorithm.java | 15 ++ src/main/java/com/auth0/jwt/ClaimSet.java | 14 ++ src/main/java/com/auth0/jwt/JwtProxy.java | 8 + src/main/java/com/auth0/jwt/JwtSigner.java | 141 ++++++++++++++++ .../java/com/auth0/jwt/PayloadHandler.java | 10 ++ src/main/java/com/auth0/jwt/TestHarness.java | 31 ++++ src/main/java/com/auth0/jwt/User.java | 29 ++++ .../auth0/jwt/impl/BasicPayloadHandler.java | 18 ++ .../java/com/auth0/jwt/impl/JwtProxyImpl.java | 55 ++++++ 11 files changed, 424 insertions(+), 60 deletions(-) create mode 100644 src/main/java/com/auth0/jwt/Algorithm.java create mode 100644 src/main/java/com/auth0/jwt/ClaimSet.java create mode 100644 src/main/java/com/auth0/jwt/JwtProxy.java create mode 100644 src/main/java/com/auth0/jwt/JwtSigner.java create mode 100644 src/main/java/com/auth0/jwt/PayloadHandler.java create mode 100644 src/main/java/com/auth0/jwt/TestHarness.java create mode 100644 src/main/java/com/auth0/jwt/User.java create mode 100644 src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java create mode 100644 src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java diff --git a/.gitignore b/.gitignore index 54b2dc3f..2e9d0dd1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ node_modules +# Ignore Eclipse stuff +.project +.settings + # Ignore Java and IntelliJ IDEA stuff .idea target diff --git a/pom.xml b/pom.xml index db2b48a2..c06db800 100644 --- a/pom.xml +++ b/pom.xml @@ -1,50 +1,13 @@ - - - 4.0.0 + + 4.0.0 + com.auth0 + java-jwt-signer + 1.0 + jar + JWT Signer - Project Object Model - - org.sonatype.oss - oss-parent - 9 - - - com.auth0 - java-jwt - 0.3-SNAPSHOT - - Java JWT - Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. - http://www.jwt.io - - - 1.6 - - - - - The MIT License - http://www.opensource.org/licenses/mit-license.php - repo - - - - - - Alberto Pose - pose - - Developer - - - - - - https://github.com/auth0/java-jwt - scm:git:git@github.com:auth0/java-jwt.git - scm:git:git@github.com:auth0/java-jwt.git - - - + com.fasterxml.jackson.core @@ -65,18 +28,94 @@ - - - - maven-compiler-plugin - 3.1 - - ${java.version} - ${java.version} - UTF-8 - - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.7 + 1.7 + + + + org.apache.maven.plugins + maven-eclipse-plugin + + + org.codehaus.mojo + properties-maven-plugin + 1.0-alpha-2 + + + initialize + + read-project-properties + + + true + + ${basedir}/build.properties + ${basedir}/build.local.properties + + + + + + + + src/main/java + + + + src/main/resources + true + + **/*.xml + **/*.properties + + + - \ No newline at end of file + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + + org.codehaus.mojo + + + properties-maven-plugin + + + [1.0-alpha-2,) + + + + read-project-properties + + + + + + false + + + + + + + + + + + diff --git a/src/main/java/com/auth0/jwt/Algorithm.java b/src/main/java/com/auth0/jwt/Algorithm.java new file mode 100644 index 00000000..be135d35 --- /dev/null +++ b/src/main/java/com/auth0/jwt/Algorithm.java @@ -0,0 +1,15 @@ +package com.auth0.jwt; + +public enum Algorithm { + HS256("HmacSHA256"), HS384("HmacSHA384"), HS512("HmacSHA512"), RS256("RS256"), RS384("RS384"), RS512("RS512"); + + private Algorithm(String value) { + this.value = value; + } + + private String value; + + public String getValue() { + return value; + } +} diff --git a/src/main/java/com/auth0/jwt/ClaimSet.java b/src/main/java/com/auth0/jwt/ClaimSet.java new file mode 100644 index 00000000..8c401684 --- /dev/null +++ b/src/main/java/com/auth0/jwt/ClaimSet.java @@ -0,0 +1,14 @@ +package com.auth0.jwt; + +public class ClaimSet { + + private int exp; + + public int getExp() { + return exp; + } + + public void setExp(int exp) { + this.exp = (int)(System.currentTimeMillis() / 1000L) + exp; + } +} diff --git a/src/main/java/com/auth0/jwt/JwtProxy.java b/src/main/java/com/auth0/jwt/JwtProxy.java new file mode 100644 index 00000000..da7c7f3d --- /dev/null +++ b/src/main/java/com/auth0/jwt/JwtProxy.java @@ -0,0 +1,8 @@ +package com.auth0.jwt; + +public interface JwtProxy { + + void setPayloadHandler(PayloadHandler payloadHandler); + String encode(Algorithm algorithm, Object obj, String secret, ClaimSet claimSet) throws Exception; + Object decode(Algorithm algorithm, String token, String secret) throws Exception; +} diff --git a/src/main/java/com/auth0/jwt/JwtSigner.java b/src/main/java/com/auth0/jwt/JwtSigner.java new file mode 100644 index 00000000..18123e0f --- /dev/null +++ b/src/main/java/com/auth0/jwt/JwtSigner.java @@ -0,0 +1,141 @@ +package com.auth0.jwt; + +import java.util.ArrayList; +import java.util.List; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.naming.OperationNotSupportedException; + +import org.apache.commons.codec.binary.Base64; + +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * JwtSigner implementation based on the Ruby implementation from http://jwt.io + * No support for RSA encryption at present + */ +public class JwtSigner { + + /** + * Generate a JSON web token based on a payload, secret key and claim set + */ + public String encode(Algorithm algorithm, String payload, String payloadId, String key, + ClaimSet claimSet) throws Exception { + + List segments = new ArrayList(); + + segments.add(encodedHeader(algorithm)); + segments.add(encodedPayload(payload, payloadId, claimSet)); + segments.add(encodedSignature(join(segments, "."), key, algorithm)); + + return join(segments, "."); + } + + /** + * Generate the header part of a JSON web token + */ + private String encodedHeader(Algorithm algorithm) + throws Exception { + + if (algorithm == null) { // default the algorithm if not specified + algorithm = Algorithm.HS256; + } + + // create the header + ObjectNode header = JsonNodeFactory.instance.objectNode(); + header.put("type", "JWT"); + header.put("alg", algorithm.name()); + + return base64UrlEncode(header.toString().getBytes()); + } + + /** + * Generate the JSON web token payload, merging it with the claim set + */ + private String encodedPayload(String payload, String payloadId, ClaimSet claimSet) throws Exception { + + ObjectNode _claimSet = JsonNodeFactory.instance.objectNode(); + ObjectNode _payload = JsonNodeFactory.instance.objectNode();; + + _payload.put(payloadId, payload); + + if(claimSet != null) { + if(claimSet.getExp() > 0) { + _claimSet.put("exp", claimSet.getExp()); + } + _payload.putAll(_claimSet); + } + + return base64UrlEncode(_payload.toString().getBytes()); + } + + /** + * Sign the header and payload + */ + private String encodedSignature(String signingInput, String key, + Algorithm algorithm) throws Exception { + + byte[] signature = sign(algorithm, signingInput, key); + return base64UrlEncode(signature); + } + + /** + * Safe URL encode a byte array to a String + */ + private String base64UrlEncode(byte[] str) throws Exception { + + return new String(Base64.encodeBase64URLSafe(str)); + } + + /** + * Switch the signing algorithm based on input, RSA not supported + */ + private byte[] sign(Algorithm algorithm, String msg, String key) + throws Exception { + + switch (algorithm) { + case HS256: + case HS384: + case HS512: + return signHmac(algorithm, msg, key); + case RS256: + case RS384: + case RS512: + default: + throw new OperationNotSupportedException( + "Unsupported signing method"); + } + } + + /** + * Sign an input string using HMAC and return the encrypted bytes + */ + private byte[] signHmac(Algorithm algorithm, String msg, String key) + throws Exception { + + Mac mac = Mac.getInstance(algorithm.getValue()); + mac.init(new SecretKeySpec(key.getBytes(), algorithm.getValue())); + return mac.doFinal(msg.getBytes()); + } + + /** + * Mimick the ruby array.join function + */ + private String join(List input, String on) { + + int size = input.size(); + int count = 1; + StringBuilder joined = new StringBuilder(); + for (String string : input) { + joined.append(string); + if (count < size) { + joined.append(on); + } + count++; + } + + return joined.toString(); + } +} diff --git a/src/main/java/com/auth0/jwt/PayloadHandler.java b/src/main/java/com/auth0/jwt/PayloadHandler.java new file mode 100644 index 00000000..24fd8eca --- /dev/null +++ b/src/main/java/com/auth0/jwt/PayloadHandler.java @@ -0,0 +1,10 @@ +package com.auth0.jwt; + +/** + * Abstraction to allow custom payload handling e.g. in the event the payload needs to be encrypted + */ +public interface PayloadHandler { + + String encoding(Object payload) throws Exception; + Object decoding(String payload) throws Exception; +} diff --git a/src/main/java/com/auth0/jwt/TestHarness.java b/src/main/java/com/auth0/jwt/TestHarness.java new file mode 100644 index 00000000..a7f5da9e --- /dev/null +++ b/src/main/java/com/auth0/jwt/TestHarness.java @@ -0,0 +1,31 @@ +package com.auth0.jwt; + +import com.auth0.jwt.impl.BasicPayloadHandler; +import com.auth0.jwt.impl.JwtProxyImpl; + +/** + * Test harness for JwtProxy + */ +public class TestHarness { + + public static void main(String[] args) throws Exception { + + final String secret = "This is a secret"; + final Algorithm algorithm = Algorithm.HS256; + + User user = new User(); + user.setUsername("jwt"); + user.setPassword("mypassword"); + + JwtProxy proxy = new JwtProxyImpl(); + proxy.setPayloadHandler(new BasicPayloadHandler()); + + ClaimSet claimSet = new ClaimSet(); + claimSet.setExp(24 * 60 * 60); // expire in 24 hours + String token = proxy.encode(algorithm, user, secret, claimSet); + System.out.println(token); + + Object payload = proxy.decode(algorithm, token, secret); + System.out.println(payload); + } +} diff --git a/src/main/java/com/auth0/jwt/User.java b/src/main/java/com/auth0/jwt/User.java new file mode 100644 index 00000000..dc50b2b5 --- /dev/null +++ b/src/main/java/com/auth0/jwt/User.java @@ -0,0 +1,29 @@ +package com.auth0.jwt; + +/** + * Sample object for serialization + */ +public class User { + + private String username; + private String password; + + public User() { + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java b/src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java new file mode 100644 index 00000000..e198a28a --- /dev/null +++ b/src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java @@ -0,0 +1,18 @@ +package com.auth0.jwt.impl; + +import com.auth0.jwt.PayloadHandler; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Basic implementation of a payload handler which serializes the payload to a String, and echoes it for deserialization + */ +public final class BasicPayloadHandler implements PayloadHandler { + + public String encoding(Object payload) throws Exception { + return new ObjectMapper().writeValueAsString(payload); + } + + public Object decoding(String payload) throws Exception { + return payload; + } +} diff --git a/src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java b/src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java new file mode 100644 index 00000000..ba0e7a6b --- /dev/null +++ b/src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java @@ -0,0 +1,55 @@ +package com.auth0.jwt.impl; + +import java.util.Map; + +import org.apache.commons.codec.binary.Base64; + +import com.auth0.jwt.Algorithm; +import com.auth0.jwt.ClaimSet; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.JwtProxy; +import com.auth0.jwt.JwtSigner; +import com.auth0.jwt.PayloadHandler; + +/** + * JwtProxy implementation + */ +public class JwtProxyImpl implements JwtProxy { + + // the payload identifier in the JSON object + private static final String PAYLOAD_ID = "payload"; + private PayloadHandler payloadHandler; + + public void setPayloadHandler(PayloadHandler payloadHandler) { + this.payloadHandler = payloadHandler; + } + + public PayloadHandler getPayloadHandler() { + return payloadHandler; + } + + /** + * Create a JSON web token by serializing a java object + */ + public String encode(Algorithm algorithm, Object obj, String secret, + ClaimSet claimSet) throws Exception { + + JwtSigner jwtSigner = new JwtSigner(); + String payload = getPayloadHandler().encoding(obj); + + return jwtSigner.encode(algorithm, payload, PAYLOAD_ID, secret, claimSet); + } + + /** + * Verify a JSON web token and return the object serialized in the JSON payload + */ + public Object decode(Algorithm algorithm, String token, String secret) + throws Exception { + + JWTVerifier jwtVerifier = new JWTVerifier(Base64.encodeBase64String(secret.getBytes())); + Map verify = jwtVerifier.verify(token); + String payload = (String) verify.get(PAYLOAD_ID); + + return getPayloadHandler().decoding(payload); + } +} From 168d288a3c139dc809aa9a462203082734c24d8e Mon Sep 17 00:00:00 2001 From: Michele Orsi Date: Sun, 22 Jun 2014 01:22:32 +0200 Subject: [PATCH 22/69] took some infos from previous pom --- pom.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pom.xml b/pom.xml index c06db800..5590bd93 100644 --- a/pom.xml +++ b/pom.xml @@ -6,6 +6,24 @@ 1.0 jar JWT Signer - Project Object Model + + + org.sonatype.oss + oss-parent + 9 + + + + + The MIT License + http://www.opensource.org/licenses/mit-license.php + repo + + + + Java JWT + Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. + http://www.jwt.io From efe867863bab18476f78d06924378754e39d5377 Mon Sep 17 00:00:00 2001 From: Michele Orsi Date: Sun, 22 Jun 2014 02:10:34 +0200 Subject: [PATCH 23/69] moved TestHarness to the test folder --- pom.xml | 3 +-- src/{main => test}/java/com/auth0/jwt/TestHarness.java | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) rename src/{main => test}/java/com/auth0/jwt/TestHarness.java (90%) diff --git a/pom.xml b/pom.xml index 5590bd93..97eea9bd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ java-jwt-signer 1.0 jar - JWT Signer - Project Object Model + Java JWT org.sonatype.oss @@ -21,7 +21,6 @@ - Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. http://www.jwt.io diff --git a/src/main/java/com/auth0/jwt/TestHarness.java b/src/test/java/com/auth0/jwt/TestHarness.java similarity index 90% rename from src/main/java/com/auth0/jwt/TestHarness.java rename to src/test/java/com/auth0/jwt/TestHarness.java index a7f5da9e..b48e0596 100644 --- a/src/main/java/com/auth0/jwt/TestHarness.java +++ b/src/test/java/com/auth0/jwt/TestHarness.java @@ -2,13 +2,15 @@ import com.auth0.jwt.impl.BasicPayloadHandler; import com.auth0.jwt.impl.JwtProxyImpl; +import org.junit.Test; /** * Test harness for JwtProxy */ public class TestHarness { - public static void main(String[] args) throws Exception { + @Test + public void testHarness() throws Exception { final String secret = "This is a secret"; final Algorithm algorithm = Algorithm.HS256; From a0fa787cdec51e728f310c87933aa325ed386c95 Mon Sep 17 00:00:00 2001 From: Michele Orsi Date: Sun, 22 Jun 2014 03:26:54 +0200 Subject: [PATCH 24/69] removed System.out.println occurencies and moved User to test package --- src/test/java/com/auth0/jwt/TestHarness.java | 4 ++-- src/{main => test}/java/com/auth0/jwt/User.java | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename src/{main => test}/java/com/auth0/jwt/User.java (100%) diff --git a/src/test/java/com/auth0/jwt/TestHarness.java b/src/test/java/com/auth0/jwt/TestHarness.java index b48e0596..9024dc50 100644 --- a/src/test/java/com/auth0/jwt/TestHarness.java +++ b/src/test/java/com/auth0/jwt/TestHarness.java @@ -1,5 +1,6 @@ package com.auth0.jwt; +import static org.junit.Assert.*; import com.auth0.jwt.impl.BasicPayloadHandler; import com.auth0.jwt.impl.JwtProxyImpl; import org.junit.Test; @@ -25,9 +26,8 @@ public void testHarness() throws Exception { ClaimSet claimSet = new ClaimSet(); claimSet.setExp(24 * 60 * 60); // expire in 24 hours String token = proxy.encode(algorithm, user, secret, claimSet); - System.out.println(token); Object payload = proxy.decode(algorithm, token, secret); - System.out.println(payload); + assertEquals("{\"username\":\"jwt\",\"password\":\"mypassword\"}",payload); } } diff --git a/src/main/java/com/auth0/jwt/User.java b/src/test/java/com/auth0/jwt/User.java similarity index 100% rename from src/main/java/com/auth0/jwt/User.java rename to src/test/java/com/auth0/jwt/User.java From 2024530eafa16e6fefa5a9d0eefc43e7b1f6bcf3 Mon Sep 17 00:00:00 2001 From: Michele Orsi Date: Mon, 23 Jun 2014 01:26:06 +0200 Subject: [PATCH 25/69] removed double ';' and renamed variable with '_' --- src/main/java/com/auth0/jwt/JwtSigner.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JwtSigner.java b/src/main/java/com/auth0/jwt/JwtSigner.java index 18123e0f..5c1a9694 100644 --- a/src/main/java/com/auth0/jwt/JwtSigner.java +++ b/src/main/java/com/auth0/jwt/JwtSigner.java @@ -56,19 +56,19 @@ private String encodedHeader(Algorithm algorithm) */ private String encodedPayload(String payload, String payloadId, ClaimSet claimSet) throws Exception { - ObjectNode _claimSet = JsonNodeFactory.instance.objectNode(); - ObjectNode _payload = JsonNodeFactory.instance.objectNode();; + ObjectNode localClaimSet = JsonNodeFactory.instance.objectNode(); + ObjectNode localPayload = JsonNodeFactory.instance.objectNode(); - _payload.put(payloadId, payload); + localPayload.put(payloadId, payload); if(claimSet != null) { if(claimSet.getExp() > 0) { - _claimSet.put("exp", claimSet.getExp()); + localClaimSet.put("exp", claimSet.getExp()); } - _payload.putAll(_claimSet); + localPayload.putAll(localClaimSet); } - return base64UrlEncode(_payload.toString().getBytes()); + return base64UrlEncode(localPayload.toString().getBytes()); } /** From 26fc6841cb135f54115640d8f2accde69878d51e Mon Sep 17 00:00:00 2001 From: Cristian Douce & Alberto Pose Date: Mon, 23 Jun 2014 10:08:17 -0300 Subject: [PATCH 26/69] pom.xml cleanup --- pom.xml | 132 +++++++++++++++----------------------------------------- 1 file changed, 34 insertions(+), 98 deletions(-) diff --git a/pom.xml b/pom.xml index 97eea9bd..1c5c98a1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,13 +1,13 @@ - 4.0.0 - com.auth0 - java-jwt-signer - 1.0 - jar - Java JWT - - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 + com.auth0 + java-jwt-signer + 1.0 + jar + Java JWT + + org.sonatype.oss oss-parent 9 @@ -24,7 +24,7 @@ Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. http://www.jwt.io - + com.fasterxml.jackson.core @@ -45,94 +45,30 @@ - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.7 - 1.7 - - - - org.apache.maven.plugins - maven-eclipse-plugin - - - org.codehaus.mojo - properties-maven-plugin - 1.0-alpha-2 - - - initialize - - read-project-properties - - - true - - ${basedir}/build.properties - ${basedir}/build.local.properties - - - - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.6 + 1.6 + + + - src/main/java + src/main/java - - - src/main/resources - true - - **/*.xml - **/*.properties - - - - - - - - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - - org.codehaus.mojo - - - properties-maven-plugin - - - [1.0-alpha-2,) - - - - read-project-properties - - - - - - false - - - - - - - - - - + + + src/main/resources + true + + **/*.xml + **/*.properties + + + + From 08f358587b939f7e6ba15a3bf0ff9cedde2bb7de Mon Sep 17 00:00:00 2001 From: Cristian Douce & Alberto Pose Date: Mon, 23 Jun 2014 10:08:39 -0300 Subject: [PATCH 27/69] pom.xml cleanup --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 1c5c98a1..aef582fc 100644 --- a/pom.xml +++ b/pom.xml @@ -6,13 +6,13 @@ 1.0 jar Java JWT - + org.sonatype.oss oss-parent 9 - + The MIT License @@ -20,7 +20,7 @@ repo - + Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. http://www.jwt.io From 4ef8a8f9c8956feb7a83292718c83f5106a13cb7 Mon Sep 17 00:00:00 2001 From: Cristian Douce & Alberto Pose Date: Mon, 23 Jun 2014 10:16:13 -0300 Subject: [PATCH 28/69] More pom.xml cleanup. --- pom.xml | 108 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 58 insertions(+), 50 deletions(-) diff --git a/pom.xml b/pom.xml index aef582fc..e8738458 100644 --- a/pom.xml +++ b/pom.xml @@ -1,49 +1,69 @@ 4.0.0 + + + org.sonatype.oss + oss-parent + 9 + + com.auth0 - java-jwt-signer - 1.0 - jar + java-jwt + 0.3-SNAPSHOT + Java JWT + Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. + http://www.jwt.io - - org.sonatype.oss - oss-parent - 9 - + + 1.6 + + + + + The MIT License + http://www.opensource.org/licenses/mit-license.php + repo + + - - - The MIT License - http://www.opensource.org/licenses/mit-license.php - repo - - + + + Alberto Pose + pose + + Developer + + + - Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. - http://www.jwt.io + + https://github.com/auth0/java-jwt + scm:git:git@github.com:auth0/java-jwt.git + scm:git:git@github.com:auth0/java-jwt.git + - - - com.fasterxml.jackson.core - jackson-databind - 2.0.0 - - - commons-codec - commons-codec - 1.4 - + + + com.fasterxml.jackson.core + jackson-databind + 2.0.0 + + + commons-codec + commons-codec + 1.4 + - - junit - junit - 4.11 - test - - + + junit + junit + 4.11 + test + + @@ -52,23 +72,11 @@ maven-compiler-plugin 3.1 - 1.6 - 1.6 + ${java.version} + ${java.version} + UTF-8 - - src/main/java - - - - src/main/resources - true - - **/*.xml - **/*.properties - - - From 4b661fe4a0410e58768084d181ca780919380b34 Mon Sep 17 00:00:00 2001 From: Cristian Douce & Alberto Pose Date: Mon, 23 Jun 2014 10:20:46 -0300 Subject: [PATCH 29/69] Changing tabs to spaces. --- src/test/java/com/auth0/jwt/TestHarness.java | 40 ++++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/test/java/com/auth0/jwt/TestHarness.java b/src/test/java/com/auth0/jwt/TestHarness.java index 9024dc50..8a314d7c 100644 --- a/src/test/java/com/auth0/jwt/TestHarness.java +++ b/src/test/java/com/auth0/jwt/TestHarness.java @@ -10,24 +10,24 @@ */ public class TestHarness { - @Test - public void testHarness() throws Exception { - - final String secret = "This is a secret"; - final Algorithm algorithm = Algorithm.HS256; - - User user = new User(); - user.setUsername("jwt"); - user.setPassword("mypassword"); - - JwtProxy proxy = new JwtProxyImpl(); - proxy.setPayloadHandler(new BasicPayloadHandler()); - - ClaimSet claimSet = new ClaimSet(); - claimSet.setExp(24 * 60 * 60); // expire in 24 hours - String token = proxy.encode(algorithm, user, secret, claimSet); - - Object payload = proxy.decode(algorithm, token, secret); - assertEquals("{\"username\":\"jwt\",\"password\":\"mypassword\"}",payload); - } + @Test + public void testHarness() throws Exception { + + final String secret = "This is a secret"; + final Algorithm algorithm = Algorithm.HS256; + + User user = new User(); + user.setUsername("jwt"); + user.setPassword("mypassword"); + + JwtProxy proxy = new JwtProxyImpl(); + proxy.setPayloadHandler(new BasicPayloadHandler()); + + ClaimSet claimSet = new ClaimSet(); + claimSet.setExp(24 * 60 * 60); // expire in 24 hours + String token = proxy.encode(algorithm, user, secret, claimSet); + + Object payload = proxy.decode(algorithm, token, secret); + assertEquals("{\"username\":\"jwt\",\"password\":\"mypassword\"}",payload); + } } From b63fe8febf919cbebfe78b49e3c2610ab617befe Mon Sep 17 00:00:00 2001 From: Cristian Douce & Alberto Pose Date: Mon, 23 Jun 2014 10:22:50 -0300 Subject: [PATCH 30/69] [maven-release-plugin] prepare release java-jwt-0.3 --- pom.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index e8738458..74494e8f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,4 @@ - + 4.0.0 @@ -10,7 +9,7 @@ com.auth0 java-jwt - 0.3-SNAPSHOT + 0.3 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 6d1c80e58a30118585f36a53578e73b9cde8f3a3 Mon Sep 17 00:00:00 2001 From: Cristian Douce & Alberto Pose Date: Mon, 23 Jun 2014 10:22:58 -0300 Subject: [PATCH 31/69] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 74494e8f..9c2066ba 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 0.3 + 0.4-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 7a2dc2cdef5a06accd789e094af8ec03a0b3db35 Mon Sep 17 00:00:00 2001 From: Dani Date: Tue, 26 Aug 2014 13:52:23 +0200 Subject: [PATCH 32/69] Update README.md update on library version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 14404e70..8b3b62ff 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Yes, here you are: com.auth0 java-jwt - 0.2 + 0.3 ``` From 98a3c556a36b672367417d7df689be02c70cf21f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuli=20K=C3=A4rkk=C3=A4inen?= Date: Fri, 5 Sep 2014 13:12:33 +0300 Subject: [PATCH 33/69] Support array-valued 'aud' Also don't break if user hasn't specified non-null audience. --- src/main/java/com/auth0/jwt/JWTVerifier.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 140291df..191f01f7 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -1,6 +1,5 @@ package com.auth0.jwt; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.binary.Base64; @@ -125,11 +124,21 @@ void verifyIssuer(JsonNode jwtClaims) { } void verifyAudience(JsonNode jwtClaims) { - final String audienceFromToken = jwtClaims.has("aud") ? jwtClaims.get("aud").asText() : null; - - if (audienceFromToken != null && !audience.equals(audienceFromToken)) { - throw new IllegalStateException("jwt audience invalid"); + if (audience == null) + return; + JsonNode audNode = jwtClaims.get("aud"); + if (audNode == null) + return; + if (audNode.isArray()) { + for (JsonNode jsonNode : audNode) { + if (audience.equals(jsonNode.textValue())) + return; + } + } else if (audNode.isTextual()) { + if (audience.equals(audNode.textValue())) + return; } + throw new IllegalStateException("jwt audience invalid"); } String getAlgorithm(JsonNode jwtHeader) { From 62ad8b9b631ca67352cda4ccb7ad4a3eeded2f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuli=20K=C3=A4rkk=C3=A4inen?= Date: Fri, 5 Sep 2014 13:31:37 +0300 Subject: [PATCH 34/69] Add tests for array-valued audience verification --- .../java/com/auth0/jwt/JWTVerifierTest.java | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/src/test/java/com/auth0/jwt/JWTVerifierTest.java index fa8b3205..3079fe8f 100644 --- a/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -1,14 +1,15 @@ package com.auth0.jwt; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; + import org.apache.commons.codec.binary.Base64; import org.junit.Test; import java.security.SignatureException; -import java.util.Collections; -import java.util.Map; import static org.junit.Assert.assertEquals; @@ -137,6 +138,26 @@ public void shouldVerifyAudienceWhenNotFoundInClaimsSet() throws Exception { .verifyAudience(JsonNodeFactory.instance.objectNode()); } + @Test + public void shouldVerifyNullAudience() throws Exception { + new JWTVerifier("such secret") + .verifyAudience(createSingletonJSONNode("aud", "wow")); + } + + @Test + public void shouldVerifyArrayAudience() throws Exception { + new JWTVerifier("such secret", "amaze audience") + .verifyAudience(createSingletonJSONNode("aud", + new ObjectMapper().readValue("[ \"foo\", \"amaze audience\" ]", ArrayNode.class))); + } + + @Test(expected = IllegalStateException.class) + public void shouldFailArrayAudience() throws Exception { + new JWTVerifier("such secret", "amaze audience") + .verifyAudience(createSingletonJSONNode("aud", + new ObjectMapper().readValue("[ \"foo\" ]", ArrayNode.class))); + } + @Test public void decodeAndParse() throws Exception { final Base64 encoder = new Base64(true); @@ -156,4 +177,10 @@ public static JsonNode createSingletonJSONNode(String key, String value) { jsonNodes.put(key, value); return jsonNodes; } + + public static JsonNode createSingletonJSONNode(String key, JsonNode value) { + final ObjectNode jsonNodes = JsonNodeFactory.instance.objectNode(); + jsonNodes.put(key, value); + return jsonNodes; + } } From df150445fe6308937528de5e2d19bcdd42f52a51 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 5 Sep 2014 09:18:12 -0300 Subject: [PATCH 35/69] [maven-release-plugin] prepare release java-jwt-0.4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9c2066ba..70f7f267 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 0.4-SNAPSHOT + 0.4 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From f123f3ed8e75eb72732936c317de59d0722bdc1a Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 5 Sep 2014 09:18:18 -0300 Subject: [PATCH 36/69] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 70f7f267..57f38505 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 0.4 + 0.5-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 0bd0679bfc8b2dd26ecbfe1891ef29a4c476dec0 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Mon, 8 Sep 2014 13:40:38 -0300 Subject: [PATCH 37/69] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b3b62ff..d2f744b2 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Yes, here you are: com.auth0 java-jwt - 0.3 + 0.4 ``` From d2230d69bc8b77e093cdd954f908e15fc802b657 Mon Sep 17 00:00:00 2001 From: Jan Stamer Date: Tue, 30 Sep 2014 09:47:26 +0200 Subject: [PATCH 38/69] Repackage maven dependencies to avoid conflicts with Jackson version. --- .gitignore | 3 +++ pom.xml | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/.gitignore b/.gitignore index 2e9d0dd1..0e60b940 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Maven Shaded Jar Artifact +dependency-reduced-pom.xml + node_modules # Ignore Eclipse stuff diff --git a/pom.xml b/pom.xml index 57f38505..5f981d48 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ 1.6 + com.auth0.jwt.internal @@ -76,6 +77,33 @@ UTF-8 + + org.apache.maven.plugins + maven-shade-plugin + 2.2 + + + package + + shade + + + false + true + + + com.fasterxml.jackson + ${repackage.base}.com.fasterxml.jackson + + + org.apache.commons.codec + ${repackage.base}.org.apache.commons.codec + + + + + + From e43647960804f3dd64620eb6506eb4a8f686a7c7 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Tue, 30 Sep 2014 09:01:02 -0300 Subject: [PATCH 39/69] [maven-release-plugin] prepare release java-jwt-0.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5f981d48..7861f28e 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 0.5-SNAPSHOT + 0.5 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 84f52c09bf1c0b312f91fde13eb8b752e8f408df Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Tue, 30 Sep 2014 09:01:16 -0300 Subject: [PATCH 40/69] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7861f28e..51fcc5f6 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 0.5 + 0.6-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 490d465415265aa88c91a9a251ff34d5a61a61f9 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Tue, 30 Sep 2014 09:08:06 -0300 Subject: [PATCH 41/69] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2f744b2..3275f75c 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Yes, here you are: com.auth0 java-jwt - 0.4 + 0.5 ``` From 03f04b51b5bdaf25d7d6666ec240fa7a063d1dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuli=20K=C3=A4rkk=C3=A4inen?= Date: Tue, 16 Sep 2014 19:25:02 +0300 Subject: [PATCH 42/69] Complete change of encode() API and implementation --- .gitignore | 1 + src/main/java/com/auth0/jwt/ClaimSet.java | 14 - src/main/java/com/auth0/jwt/JWTSigner.java | 333 ++++++++++++++++++ src/main/java/com/auth0/jwt/JwtProxy.java | 8 - src/main/java/com/auth0/jwt/JwtSigner.java | 141 -------- .../java/com/auth0/jwt/PayloadHandler.java | 10 - .../auth0/jwt/impl/BasicPayloadHandler.java | 18 - .../java/com/auth0/jwt/impl/JwtProxyImpl.java | 55 --- .../java/com/auth0/jwt/JWTSignerTest.java | 185 ++++++++++ .../java/com/auth0/jwt/RoundtripTest.java | 142 ++++++++ src/test/java/com/auth0/jwt/TestHarness.java | 33 -- 11 files changed, 661 insertions(+), 279 deletions(-) delete mode 100644 src/main/java/com/auth0/jwt/ClaimSet.java create mode 100644 src/main/java/com/auth0/jwt/JWTSigner.java delete mode 100644 src/main/java/com/auth0/jwt/JwtProxy.java delete mode 100644 src/main/java/com/auth0/jwt/JwtSigner.java delete mode 100644 src/main/java/com/auth0/jwt/PayloadHandler.java delete mode 100644 src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java delete mode 100644 src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java create mode 100644 src/test/java/com/auth0/jwt/JWTSignerTest.java create mode 100644 src/test/java/com/auth0/jwt/RoundtripTest.java delete mode 100644 src/test/java/com/auth0/jwt/TestHarness.java diff --git a/.gitignore b/.gitignore index 2e9d0dd1..98b57f51 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ node_modules # Ignore Eclipse stuff .project .settings +.classpath # Ignore Java and IntelliJ IDEA stuff .idea diff --git a/src/main/java/com/auth0/jwt/ClaimSet.java b/src/main/java/com/auth0/jwt/ClaimSet.java deleted file mode 100644 index 8c401684..00000000 --- a/src/main/java/com/auth0/jwt/ClaimSet.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.auth0.jwt; - -public class ClaimSet { - - private int exp; - - public int getExp() { - return exp; - } - - public void setExp(int exp) { - this.exp = (int)(System.currentTimeMillis() / 1000L) + exp; - } -} diff --git a/src/main/java/com/auth0/jwt/JWTSigner.java b/src/main/java/com/auth0/jwt/JWTSigner.java new file mode 100644 index 00000000..55084a12 --- /dev/null +++ b/src/main/java/com/auth0/jwt/JWTSigner.java @@ -0,0 +1,333 @@ +package com.auth0.jwt; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.naming.OperationNotSupportedException; + +import org.apache.commons.codec.binary.Base64; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * JwtSigner implementation based on the Ruby implementation from http://jwt.io + * No support for RSA encryption at present + */ +public class JWTSigner { + private final String secret; + + public JWTSigner(String secret) { + this.secret = secret; + } + + /** + * Generate a JSON Web Token. + * using the default algorithm HMAC SHA-256 ("HS256") + * and no claims automatically set. + * + * @param claims A map of the JWT claims that form the payload. Registered claims + * must be of appropriate Java datatype as following: + *

+ * All claims with a null value are left out the JWT. + * Any claims set automatically as specified in + * the "options" parameter override claims in this map. + * + * @param secret Key to use in signing. Used as-is without Base64 encoding. + * + * @param options Allow choosing the signing algorithm, and automatic setting of some registered claims. + */ + public String sign(Map claims, Options options) { + Algorithm algorithm = Algorithm.HS256; + if (options != null && options.algorithm != null) + algorithm = options.algorithm; + + List segments = new ArrayList(); + try { + segments.add(encodedHeader(algorithm)); + segments.add(encodedPayload(claims, options)); + segments.add(encodedSignature(join(segments, "."), algorithm)); + } catch (Exception e) { + throw (e instanceof RuntimeException) ? (RuntimeException) e : new RuntimeException(e); + } + + return join(segments, "."); + } + + /** + * Generate a JSON Web Token using the default algorithm HMAC SHA-256 ("HS256") + * and no claims automatically set. + * + * @param secret Key to use in signing. Used as-is without Base64 encoding. + * + * For details, see the two parameter variant of this method. + */ + public String sign(Map claims) { + return sign(claims, null); + } + + /** + * Generate the header part of a JSON web token. + */ + private String encodedHeader(Algorithm algorithm) throws UnsupportedEncodingException { + if (algorithm == null) { // default the algorithm if not specified + algorithm = Algorithm.HS256; + } + + // create the header + ObjectNode header = JsonNodeFactory.instance.objectNode(); + header.put("type", "JWT"); + header.put("alg", algorithm.name()); + + return base64UrlEncode(header.toString().getBytes("UTF-8")); + } + + /** + * Generate the JSON web token payload string from the claims. + * @param options + */ + private String encodedPayload(Map _claims, Options options) throws Exception { + Map claims = new HashMap(_claims); + enforceStringOrURI(claims, "iss"); + enforceStringOrURI(claims, "sub"); + enforceStringOrURICollection(claims, "aud"); + enforceIntDate(claims, "exp"); + enforceIntDate(claims, "nbf"); + enforceIntDate(claims, "iat"); + enforceString(claims, "jti"); + + if (options != null) + processPayloadOptions(claims, options); + + String payload = new ObjectMapper().writeValueAsString(claims); + return base64UrlEncode(payload.getBytes("UTF-8")); + } + + private void processPayloadOptions(Map claims, Options options) { + long now = System.currentTimeMillis() / 1000l; + if (options.expirySeconds != null) + claims.put("exp", now + options.expirySeconds); + if (options.notValidBeforeLeeway != null) + claims.put("nbf", now - options.notValidBeforeLeeway); + if (options.isIssuedAt()) + claims.put("iat", now); + if (options.isJwtId()) + claims.put("jti", UUID.randomUUID().toString()); + } + + private void enforceIntDate(Map claims, String claimName) { + Object value = handleNullValue(claims, claimName); + if (value == null) + return; + if (!(value instanceof Number)) { + throw new RuntimeException(String.format("Claim '%s' is invalid: must be an instance of Number", claimName)); + } + long longValue = ((Number) value).longValue(); + if (longValue < 0) + throw new RuntimeException(String.format("Claim '%s' is invalid: must be non-negative", claimName)); + claims.put(claimName, longValue); + } + + private void enforceStringOrURICollection(Map claims, String claimName) { + Object values = handleNullValue(claims, claimName); + if (values == null) + return; + if (values instanceof Collection) { + @SuppressWarnings({ "unchecked" }) + Iterator iterator = ((Collection) values).iterator(); + while (iterator.hasNext()) { + Object value = iterator.next(); + String error = checkStringOrURI(value); + if (error != null) + throw new RuntimeException(String.format("Claim 'aud' element is invalid: %s", error)); + } + } else { + enforceStringOrURI(claims, "aud"); + } + } + + private void enforceStringOrURI(Map claims, String claimName) { + Object value = handleNullValue(claims, claimName); + if (value == null) + return; + String error = checkStringOrURI(value); + if (error != null) + throw new RuntimeException(String.format("Claim '%s' is invalid: %s", claimName, error)); + } + + private void enforceString(Map claims, String claimName) { + Object value = handleNullValue(claims, claimName); + if (value == null) + return; + if (!(value instanceof String)) + throw new RuntimeException(String.format("Claim '%s' is invalid: not a string", claimName)); + } + + private Object handleNullValue(Map claims, String claimName) { + if (! claims.containsKey(claimName)) + return null; + Object value = claims.get(claimName); + if (value == null) { + claims.remove(claimName); + return null; + } + return value; + } + + private String checkStringOrURI(Object value) { + if (!(value instanceof String)) + return "not a string"; + String stringOrUri = (String) value; + if (!stringOrUri.contains(":")) + return null; + try { + new URI(stringOrUri); + } catch (URISyntaxException e) { + return "not a valid URI"; + } + return null; + } + + /** + * Sign the header and payload + */ + private String encodedSignature(String signingInput, Algorithm algorithm) throws Exception { + byte[] signature = sign(algorithm, signingInput, secret); + return base64UrlEncode(signature); + } + + /** + * Safe URL encode a byte array to a String + */ + private String base64UrlEncode(byte[] str) { + return new String(Base64.encodeBase64URLSafe(str)); + } + + /** + * Switch the signing algorithm based on input, RSA not supported + */ + private static byte[] sign(Algorithm algorithm, String msg, String secret) throws Exception { + switch (algorithm) { + case HS256: + case HS384: + case HS512: + return signHmac(algorithm, msg, secret); + case RS256: + case RS384: + case RS512: + default: + throw new OperationNotSupportedException("Unsupported signing method"); + } + } + + /** + * Sign an input string using HMAC and return the encrypted bytes + */ + private static byte[] signHmac(Algorithm algorithm, String msg, String secret) throws Exception { + Mac mac = Mac.getInstance(algorithm.getValue()); + mac.init(new SecretKeySpec(secret.getBytes(), algorithm.getValue())); + return mac.doFinal(msg.getBytes()); + } + + private String join(List input, String on) { + int size = input.size(); + int count = 1; + StringBuilder joined = new StringBuilder(); + for (String string : input) { + joined.append(string); + if (count < size) { + joined.append(on); + } + count++; + } + + return joined.toString(); + } + + /** + * An option object for JWT signing operation. Allow choosing the algorithm, and/or specifying + * claims to be automatically set. + */ + public static class Options { + private Algorithm algorithm; + private Integer expirySeconds; + private Integer notValidBeforeLeeway; + private boolean issuedAt; + private boolean jwtId; + + public Algorithm getAlgorithm() { + return algorithm; + } + /** + * Algorithm to sign JWT with. Default is HS256. + */ + public Options setAlgorithm(Algorithm algorithm) { + this.algorithm = algorithm; + return this; + } + + + public Integer getExpirySeconds() { + return expirySeconds; + } + /** + * Set JWT claim "exp" to current timestamp plus this value. + * Overrides content of claims in sign(). + */ + public Options setExpirySeconds(Integer expirySeconds) { + this.expirySeconds = expirySeconds; + return this; + } + + public Integer getNotValidBeforeLeeway() { + return notValidBeforeLeeway; + } + /** + * Set JWT claim "nbf" to current timestamp minus this value. + * Overrides content of claims in sign(). + */ + public Options setNotValidBeforeLeeway(Integer notValidBeforeLeeway) { + this.notValidBeforeLeeway = notValidBeforeLeeway; + return this; + } + + public boolean isIssuedAt() { + return issuedAt; + } + /** + * Set JWT claim "iat" to current timestamp. Defaults to false. + * Overrides content of claims in sign(). + */ + public Options setIssuedAt(boolean issuedAt) { + this.issuedAt = issuedAt; + return this; + } + + public boolean isJwtId() { + return jwtId; + } + /** + * Set JWT claim "jti" to a pseudo random unique value (type 4 UUID). Defaults to false. + * Overrides content of claims in sign(). + */ + public Options setJwtId(boolean jwtId) { + this.jwtId = jwtId; + return this; + } + } +} diff --git a/src/main/java/com/auth0/jwt/JwtProxy.java b/src/main/java/com/auth0/jwt/JwtProxy.java deleted file mode 100644 index da7c7f3d..00000000 --- a/src/main/java/com/auth0/jwt/JwtProxy.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.auth0.jwt; - -public interface JwtProxy { - - void setPayloadHandler(PayloadHandler payloadHandler); - String encode(Algorithm algorithm, Object obj, String secret, ClaimSet claimSet) throws Exception; - Object decode(Algorithm algorithm, String token, String secret) throws Exception; -} diff --git a/src/main/java/com/auth0/jwt/JwtSigner.java b/src/main/java/com/auth0/jwt/JwtSigner.java deleted file mode 100644 index 5c1a9694..00000000 --- a/src/main/java/com/auth0/jwt/JwtSigner.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.auth0.jwt; - -import java.util.ArrayList; -import java.util.List; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import javax.naming.OperationNotSupportedException; - -import org.apache.commons.codec.binary.Base64; - -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; - -/** - * JwtSigner implementation based on the Ruby implementation from http://jwt.io - * No support for RSA encryption at present - */ -public class JwtSigner { - - /** - * Generate a JSON web token based on a payload, secret key and claim set - */ - public String encode(Algorithm algorithm, String payload, String payloadId, String key, - ClaimSet claimSet) throws Exception { - - List segments = new ArrayList(); - - segments.add(encodedHeader(algorithm)); - segments.add(encodedPayload(payload, payloadId, claimSet)); - segments.add(encodedSignature(join(segments, "."), key, algorithm)); - - return join(segments, "."); - } - - /** - * Generate the header part of a JSON web token - */ - private String encodedHeader(Algorithm algorithm) - throws Exception { - - if (algorithm == null) { // default the algorithm if not specified - algorithm = Algorithm.HS256; - } - - // create the header - ObjectNode header = JsonNodeFactory.instance.objectNode(); - header.put("type", "JWT"); - header.put("alg", algorithm.name()); - - return base64UrlEncode(header.toString().getBytes()); - } - - /** - * Generate the JSON web token payload, merging it with the claim set - */ - private String encodedPayload(String payload, String payloadId, ClaimSet claimSet) throws Exception { - - ObjectNode localClaimSet = JsonNodeFactory.instance.objectNode(); - ObjectNode localPayload = JsonNodeFactory.instance.objectNode(); - - localPayload.put(payloadId, payload); - - if(claimSet != null) { - if(claimSet.getExp() > 0) { - localClaimSet.put("exp", claimSet.getExp()); - } - localPayload.putAll(localClaimSet); - } - - return base64UrlEncode(localPayload.toString().getBytes()); - } - - /** - * Sign the header and payload - */ - private String encodedSignature(String signingInput, String key, - Algorithm algorithm) throws Exception { - - byte[] signature = sign(algorithm, signingInput, key); - return base64UrlEncode(signature); - } - - /** - * Safe URL encode a byte array to a String - */ - private String base64UrlEncode(byte[] str) throws Exception { - - return new String(Base64.encodeBase64URLSafe(str)); - } - - /** - * Switch the signing algorithm based on input, RSA not supported - */ - private byte[] sign(Algorithm algorithm, String msg, String key) - throws Exception { - - switch (algorithm) { - case HS256: - case HS384: - case HS512: - return signHmac(algorithm, msg, key); - case RS256: - case RS384: - case RS512: - default: - throw new OperationNotSupportedException( - "Unsupported signing method"); - } - } - - /** - * Sign an input string using HMAC and return the encrypted bytes - */ - private byte[] signHmac(Algorithm algorithm, String msg, String key) - throws Exception { - - Mac mac = Mac.getInstance(algorithm.getValue()); - mac.init(new SecretKeySpec(key.getBytes(), algorithm.getValue())); - return mac.doFinal(msg.getBytes()); - } - - /** - * Mimick the ruby array.join function - */ - private String join(List input, String on) { - - int size = input.size(); - int count = 1; - StringBuilder joined = new StringBuilder(); - for (String string : input) { - joined.append(string); - if (count < size) { - joined.append(on); - } - count++; - } - - return joined.toString(); - } -} diff --git a/src/main/java/com/auth0/jwt/PayloadHandler.java b/src/main/java/com/auth0/jwt/PayloadHandler.java deleted file mode 100644 index 24fd8eca..00000000 --- a/src/main/java/com/auth0/jwt/PayloadHandler.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.auth0.jwt; - -/** - * Abstraction to allow custom payload handling e.g. in the event the payload needs to be encrypted - */ -public interface PayloadHandler { - - String encoding(Object payload) throws Exception; - Object decoding(String payload) throws Exception; -} diff --git a/src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java b/src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java deleted file mode 100644 index e198a28a..00000000 --- a/src/main/java/com/auth0/jwt/impl/BasicPayloadHandler.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.auth0.jwt.impl; - -import com.auth0.jwt.PayloadHandler; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * Basic implementation of a payload handler which serializes the payload to a String, and echoes it for deserialization - */ -public final class BasicPayloadHandler implements PayloadHandler { - - public String encoding(Object payload) throws Exception { - return new ObjectMapper().writeValueAsString(payload); - } - - public Object decoding(String payload) throws Exception { - return payload; - } -} diff --git a/src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java b/src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java deleted file mode 100644 index ba0e7a6b..00000000 --- a/src/main/java/com/auth0/jwt/impl/JwtProxyImpl.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.auth0.jwt.impl; - -import java.util.Map; - -import org.apache.commons.codec.binary.Base64; - -import com.auth0.jwt.Algorithm; -import com.auth0.jwt.ClaimSet; -import com.auth0.jwt.JWTVerifier; -import com.auth0.jwt.JwtProxy; -import com.auth0.jwt.JwtSigner; -import com.auth0.jwt.PayloadHandler; - -/** - * JwtProxy implementation - */ -public class JwtProxyImpl implements JwtProxy { - - // the payload identifier in the JSON object - private static final String PAYLOAD_ID = "payload"; - private PayloadHandler payloadHandler; - - public void setPayloadHandler(PayloadHandler payloadHandler) { - this.payloadHandler = payloadHandler; - } - - public PayloadHandler getPayloadHandler() { - return payloadHandler; - } - - /** - * Create a JSON web token by serializing a java object - */ - public String encode(Algorithm algorithm, Object obj, String secret, - ClaimSet claimSet) throws Exception { - - JwtSigner jwtSigner = new JwtSigner(); - String payload = getPayloadHandler().encoding(obj); - - return jwtSigner.encode(algorithm, payload, PAYLOAD_ID, secret, claimSet); - } - - /** - * Verify a JSON web token and return the object serialized in the JSON payload - */ - public Object decode(Algorithm algorithm, String token, String secret) - throws Exception { - - JWTVerifier jwtVerifier = new JWTVerifier(Base64.encodeBase64String(secret.getBytes())); - Map verify = jwtVerifier.verify(token); - String payload = (String) verify.get(PAYLOAD_ID); - - return getPayloadHandler().decoding(payload); - } -} diff --git a/src/test/java/com/auth0/jwt/JWTSignerTest.java b/src/test/java/com/auth0/jwt/JWTSignerTest.java new file mode 100644 index 00000000..9032d6e6 --- /dev/null +++ b/src/test/java/com/auth0/jwt/JWTSignerTest.java @@ -0,0 +1,185 @@ +package com.auth0.jwt; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import org.junit.Test; + +public class JWTSignerTest { + private static JWTSigner signer = new JWTSigner("my secret"); + + @Test + public void shouldSignEmpty() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + } + + @Test + public void shouldSignEmptyTwoParams() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + } + + @Test + public void shouldSignStringOrURI1() throws Exception { + HashMap claims = new HashMap(); + claims.put("iss", "foo"); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJpc3MiOiJmb28ifQ.7VNaEEPhOiEXfEgPrxkFFhQCAxl9X3F20sq9KVaVtJM", token); + } + + @Test + public void shouldSignStringOrURI2() throws Exception { + HashMap claims = new HashMap(); + claims.put("sub", "http://foo"); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJodHRwOi8vZm9vIn0.GYhCLgXYbAXp2Lr8T2yif7ylBVK1XZFkO8hEBa8WP8U", token); + } + + @Test + public void shouldSignStringOrURI3() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", ""); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJhdWQiOiIifQ.qobL4k5su7O7ssfCr7drTScIhWjheIc9uxipkR9MC0A", token); + } + + @Test + public void shouldSignStringOrURICollection() throws Exception { + HashMap claims = new HashMap(); + LinkedList aud = new LinkedList(); + aud.add("xyz"); + aud.add("ftp://foo"); + claims.put("aud", aud); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJhdWQiOlsieHl6IiwiZnRwOi8vZm9vIl19.xL_8PVO_8isoFSud1Nlqi8rA3jvdD5zALN3tjcQ0vbk", token); + } + + @Test + public void shouldSignIntDate1() throws Exception { + HashMap claims = new HashMap(); + claims.put("exp", 123); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJleHAiOjEyM30.1pI_TNQDCsKc3IEVX_2fcAKJmJZ8j3hhOfAvAdqKE0s", token); + } + + @Test + public void shouldSignIntDate2() throws Exception { + HashMap claims = new HashMap(); + claims.put("nbf", 0); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJuYmYiOjB9.uxwAmWxxPZwGRgfiXOHGxrXmxgay6tv93Pyiya3O5dE", token); + } + + @Test + public void shouldSignIntDate3() throws Exception { + HashMap claims = new HashMap(); + claims.put("iat", Long.MAX_VALUE); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJpYXQiOjkyMjMzNzIwMzY4NTQ3NzU4MDd9.nsfMBVmmDR0u1tVN54UzHDZL2wylDA50YjzN2WxZEsU", token); + } + + @Test + public void shouldSignString() throws Exception { + HashMap claims = new HashMap(); + claims.put("jti", "foo"); + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJqdGkiOiJmb28ifQ.6X8nx7sLNxdc4mYNL__gd0ab-m8QfheVHT2Y_2DQJMU", token); + } + + @Test + public void shouldSignNullEqualsMissing() throws Exception { + HashMap claims = new HashMap(); + for (String claimName : Arrays.asList("iss", "sub", "aud", "exp", "nbf", "iat", "jti")) { + claims.put(claimName, null); + } + String token = signer.sign(claims); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURI1() throws Exception { + HashMap claims = new HashMap(); + claims.put("iss", 0); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURI2() throws Exception { + HashMap claims = new HashMap(); + claims.put("sub", ":"); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURICollection1() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", 0); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURICollection2() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", Arrays.asList(0)); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURICollection3() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", Arrays.asList(":")); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectIntDate1() throws Exception { + HashMap claims = new HashMap(); + claims.put("exp", -1); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectIntDate2() throws Exception { + HashMap claims = new HashMap(); + claims.put("nbf", "100"); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectString() throws Exception { + HashMap claims = new HashMap(); + claims.put("jti", 100); + signer.sign(claims); + } + + @Test + public void shouldOptionsNone() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, new JWTSigner.Options()); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + } + + @Test + public void shouldOptionsAll() throws Exception { + HashMap claims = new HashMap(); + signer.sign(claims, new JWTSigner.Options() + .setExpirySeconds(1000).setNotValidBeforeLeeway(5) + .setIssuedAt(true).setJwtId(true)); + } + + @Test + public void shouldOptionsAlgorithm() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, + new JWTSigner.Options().setAlgorithm(Algorithm.HS512)); + assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFM1MTIifQ.e30.gH4cjvHOMA2QcZjwSqO-VZ4tyah8hDMVqUGAOth7vBWweOIzCwohpOlpLoRCKeDD3PyMqE1gwHqGuWDk2VuYmQ", token); + } + +} diff --git a/src/test/java/com/auth0/jwt/RoundtripTest.java b/src/test/java/com/auth0/jwt/RoundtripTest.java new file mode 100644 index 00000000..53330e77 --- /dev/null +++ b/src/test/java/com/auth0/jwt/RoundtripTest.java @@ -0,0 +1,142 @@ +package com.auth0.jwt; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.codec.binary.Base64; +import org.junit.Test; + +/** + * Test things that are difficult using signer or verifier alone. In particular, setting + * claims via Options produces output dependent on current time. + * + */ +public class RoundtripTest { + private static final String SECRET; + private static final String SECRET_BASE64; + static { + try { + SECRET = "my secret"; + SECRET_BASE64 = Base64.encodeBase64String(SECRET.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + private static JWTSigner signer = new JWTSigner(SECRET); + private static JWTVerifier verifier = new JWTVerifier(SECRET_BASE64); + + /* + * Roundtrip of different datatypes. + */ + @Test + public void shouldEmpty() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims); + Map decoded = verifier.verify(token); + assertEquals(claims, decoded); + } + + @Test + public void shouldString() throws Exception { + HashMap claims = new HashMap(); + claims.put("foo", "bar"); + String token = signer.sign(claims); + Map decoded = verifier.verify(token); + assertEquals(claims, decoded); + } + + @Test + public void shouldShort() throws Exception { + HashMap claims = new HashMap(); + claims.put("foo", (short) -10); + String token = signer.sign(claims); + Map decoded = verifier.verify(token); + Number fooValue = (Number) decoded.get("foo"); + decoded.put("foo", fooValue.shortValue()); + assertEquals(claims, decoded); + } + + @Test + public void shouldLong() throws Exception { + HashMap claims = new HashMap(); + claims.put("foo", Long.MAX_VALUE); + String token = signer.sign(claims); + Map decoded = verifier.verify(token); + assertEquals(claims, decoded); + } + + @Test + public void shouldObject() throws Exception { + HashMap claims = new HashMap(); + User user = new User(); + user.setUsername("foo"); + user.setPassword("bar"); + claims.put("user", user); + String token = signer.sign(claims); + Map decoded = verifier.verify(token); + HashMap expectedUser = new HashMap(); + expectedUser.put("username", "foo"); + expectedUser.put("password", "bar"); + HashMap expected = new HashMap(); + expected.put("user", expectedUser); + assertEquals(expected, decoded); + } + + @Test + public void shouldBoolean() throws Exception { + HashMap claims = new HashMap(); + claims.put("foo", true); + claims.put("bar", false); + String token = signer.sign(claims); + Map decoded = verifier.verify(token); + assertEquals(claims, decoded); + } + + /* + * Setting claims via Options + */ + + @Test + public void shouldOptionsIat() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, + new JWTSigner.Options().setIssuedAt(true)); + Map decoded = verifier.verify(token); + assertEquals(decoded.size(), 1); + long iat = ((Number) decoded.get("iat")).longValue(); + assertTrue(iat >= System.currentTimeMillis() / 1000l); + assertTrue(iat < System.currentTimeMillis() / 1000l + 10); + } + + @Test + public void shouldOptionsTimestamps() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, + new JWTSigner.Options() + .setExpirySeconds(50).setNotValidBeforeLeeway(10).setIssuedAt(true)); + Map decoded = verifier.verify(token); + assertEquals(decoded.size(), 3); + long iat = ((Number) decoded.get("iat")).longValue(); + long exp = ((Number) decoded.get("exp")).longValue(); + long nbf = ((Number) decoded.get("nbf")).longValue(); + assertEquals(exp, iat + 50); + assertEquals(nbf, iat - 10); + } + + @Test + public void shouldOptionsJti() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, + new JWTSigner.Options().setJwtId(true)); + Map decoded = verifier.verify(token); + assertEquals(decoded.size(), 1); + assertEquals(((String) decoded.get("jti")).length(), 36); + } +} + + + diff --git a/src/test/java/com/auth0/jwt/TestHarness.java b/src/test/java/com/auth0/jwt/TestHarness.java deleted file mode 100644 index 8a314d7c..00000000 --- a/src/test/java/com/auth0/jwt/TestHarness.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.auth0.jwt; - -import static org.junit.Assert.*; -import com.auth0.jwt.impl.BasicPayloadHandler; -import com.auth0.jwt.impl.JwtProxyImpl; -import org.junit.Test; - -/** - * Test harness for JwtProxy - */ -public class TestHarness { - - @Test - public void testHarness() throws Exception { - - final String secret = "This is a secret"; - final Algorithm algorithm = Algorithm.HS256; - - User user = new User(); - user.setUsername("jwt"); - user.setPassword("mypassword"); - - JwtProxy proxy = new JwtProxyImpl(); - proxy.setPayloadHandler(new BasicPayloadHandler()); - - ClaimSet claimSet = new ClaimSet(); - claimSet.setExp(24 * 60 * 60); // expire in 24 hours - String token = proxy.encode(algorithm, user, secret, claimSet); - - Object payload = proxy.decode(algorithm, token, secret); - assertEquals("{\"username\":\"jwt\",\"password\":\"mypassword\"}",payload); - } -} From 7c8cb413a9232d8bad3dd3143cadc1ea630b7a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20L=C3=B3pez=20Dato?= Date: Thu, 23 Oct 2014 10:03:13 -0300 Subject: [PATCH 43/69] Grammar fixes for README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3275f75c..88fce9fc 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,13 @@ Yes, here you are: ### Credits -Most of the code have been written by Luis Faja . We just wrapped it on a nicer interface and published it to maven. We'll be adding support for signing and other algorithms in the future. +Most of the code have been written by Luis Faja . We just wrapped it in a nicer interface and published it to Maven Central. We'll be adding support for signing and other algorithms in the future. ### FAQ #### Why another JSON Web Token implementation for Java? -We think that current JWT implementations are either too complex or not enough tested. We want something simple with the right number of abstractions. +We think that current JWT implementations are either too complex or not tested enough. We want something simple with the right number of abstractions. ## License From c3a8f855a944ed447cc0b13ced564b5c2f7947fe Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 31 Oct 2014 16:40:04 -0300 Subject: [PATCH 44/69] [maven-release-plugin] prepare release java-jwt-1.0.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 51fcc5f6..742f7e6e 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 0.6-SNAPSHOT + 1.0.0 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From eb5da3eee2f5a1c02308b2b08dd787ac936690c2 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 31 Oct 2014 16:40:09 -0300 Subject: [PATCH 45/69] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 742f7e6e..98269714 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 1.0.0 + 1.0.1-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 2f1766b701c26f20f62bab3801f40e89005bf5b7 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Fri, 31 Oct 2014 17:55:02 -0300 Subject: [PATCH 46/69] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 88fce9fc..36bcf298 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Yes, here you are: com.auth0 java-jwt - 0.5 + 1.0.0 ``` From c27801b97f39075054924288b64687c18faf0907 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Mon, 24 Nov 2014 10:04:41 -0300 Subject: [PATCH 47/69] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 36bcf298..940c35c0 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,10 @@ Most of the code have been written by Luis Faja Date: Tue, 2 Dec 2014 10:23:07 +0100 Subject: [PATCH 48/69] Use explicit JWTVerifyException for claims related verifying Close #11 --- .../com/auth0/jwt/JWTAudienceException.java | 31 +++++++++++++++++++ .../com/auth0/jwt/JWTExpiredException.java | 18 +++++++++++ .../com/auth0/jwt/JWTIssuerException.java | 18 +++++++++++ src/main/java/com/auth0/jwt/JWTVerifier.java | 18 ++++++----- .../com/auth0/jwt/JWTVerifyException.java | 10 ++++++ .../java/com/auth0/jwt/JWTVerifierTest.java | 8 ++--- 6 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/auth0/jwt/JWTAudienceException.java create mode 100644 src/main/java/com/auth0/jwt/JWTExpiredException.java create mode 100644 src/main/java/com/auth0/jwt/JWTIssuerException.java create mode 100644 src/main/java/com/auth0/jwt/JWTVerifyException.java diff --git a/src/main/java/com/auth0/jwt/JWTAudienceException.java b/src/main/java/com/auth0/jwt/JWTAudienceException.java new file mode 100644 index 00000000..6c3da263 --- /dev/null +++ b/src/main/java/com/auth0/jwt/JWTAudienceException.java @@ -0,0 +1,31 @@ +package com.auth0.jwt; + +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.ArrayList; +import java.util.List; + +public class JWTAudienceException extends JWTVerifyException { + private JsonNode audienceNode; + + public JWTAudienceException(JsonNode audienceNode) { + this.audienceNode = audienceNode; + } + + public JWTAudienceException(String message, JsonNode audienceNode) { + super(message); + this.audienceNode = audienceNode; + } + + public List getAudience() { + ArrayList audience = new ArrayList(); + if (audienceNode.isArray()) { + for (JsonNode jsonNode : audienceNode) { + audience.add(jsonNode.textValue()); + } + } else if (audienceNode.isTextual()) { + audience.add(audienceNode.textValue()); + } + return audience; + } +} diff --git a/src/main/java/com/auth0/jwt/JWTExpiredException.java b/src/main/java/com/auth0/jwt/JWTExpiredException.java new file mode 100644 index 00000000..50178aa8 --- /dev/null +++ b/src/main/java/com/auth0/jwt/JWTExpiredException.java @@ -0,0 +1,18 @@ +package com.auth0.jwt; + +public class JWTExpiredException extends JWTVerifyException { + private long expiration; + + public JWTExpiredException(long expiration) { + this.expiration = expiration; + } + + public JWTExpiredException(String message, long expiration) { + super(message); + this.expiration = expiration; + } + + public long getExpiration() { + return expiration; + }; +} diff --git a/src/main/java/com/auth0/jwt/JWTIssuerException.java b/src/main/java/com/auth0/jwt/JWTIssuerException.java new file mode 100644 index 00000000..4dc1cf16 --- /dev/null +++ b/src/main/java/com/auth0/jwt/JWTIssuerException.java @@ -0,0 +1,18 @@ +package com.auth0.jwt; + +public class JWTIssuerException extends JWTVerifyException { + private final String issuer; + + public JWTIssuerException(String issuer) { + this.issuer = issuer; + } + + public JWTIssuerException(String message, String issuer) { + super(message); + this.issuer = issuer; + } + + public String getIssuer() { + return issuer; + } +} diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 191f01f7..c1a70f0f 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -10,6 +10,7 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -62,11 +63,12 @@ public JWTVerifier(String secret) { * * @param token token to verify * @throws SignatureException when signature is invalid - * @throws IllegalStateException when token's structure, expiration, issuer or audience are invalid + * @throws JWTVerifyException when expiration, issuer or audience are invalid + * @throws IllegalStateException when token's structure is invalid */ public Map verify(String token) throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, - IOException, SignatureException { + IOException, SignatureException, JWTVerifyException { if (token == null || "".equals(token)) { throw new IllegalStateException("token not set"); } @@ -107,23 +109,23 @@ void verifySignature(String[] pieces, String algorithm) throws NoSuchAlgorithmEx } } - void verifyExpiration(JsonNode jwtClaims) { + void verifyExpiration(JsonNode jwtClaims) throws JWTExpiredException { final long expiration = jwtClaims.has("exp") ? jwtClaims.get("exp").asLong(0) : 0; if (expiration != 0 && System.currentTimeMillis() / 1000L >= expiration) { - throw new IllegalStateException("jwt expired"); + throw new JWTExpiredException("jwt expired", expiration); } } - void verifyIssuer(JsonNode jwtClaims) { + void verifyIssuer(JsonNode jwtClaims) throws JWTIssuerException { final String issuerFromToken = jwtClaims.has("iss") ? jwtClaims.get("iss").asText() : null; if (issuerFromToken != null && issuer != null && !issuer.equals(issuerFromToken)) { - throw new IllegalStateException("jwt issuer invalid"); + throw new JWTIssuerException("jwt issuer invalid", issuerFromToken); } } - void verifyAudience(JsonNode jwtClaims) { + void verifyAudience(JsonNode jwtClaims) throws JWTAudienceException { if (audience == null) return; JsonNode audNode = jwtClaims.get("aud"); @@ -138,7 +140,7 @@ void verifyAudience(JsonNode jwtClaims) { if (audience.equals(audNode.textValue())) return; } - throw new IllegalStateException("jwt audience invalid"); + throw new JWTAudienceException("jwt audience invalid", audNode); } String getAlgorithm(JsonNode jwtHeader) { diff --git a/src/main/java/com/auth0/jwt/JWTVerifyException.java b/src/main/java/com/auth0/jwt/JWTVerifyException.java new file mode 100644 index 00000000..08bc583b --- /dev/null +++ b/src/main/java/com/auth0/jwt/JWTVerifyException.java @@ -0,0 +1,10 @@ +package com.auth0.jwt; + +public class JWTVerifyException extends Exception { + public JWTVerifyException() { + } + + public JWTVerifyException(String message) { + super(message); + } +} diff --git a/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/src/test/java/com/auth0/jwt/JWTVerifierTest.java index 3079fe8f..b5fa998d 100644 --- a/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -90,7 +90,7 @@ public void shouldVerifySignature() throws Exception { .verifySignature(jws.split("\\."), "HmacSHA256"); } - @Test(expected = IllegalStateException.class) + @Test(expected = JWTExpiredException.class) public void shouldFailWhenExpired1SecondAgo() throws Exception { new JWTVerifier("such secret").verifyExpiration( createSingletonJSONNode("exp", Long.toString(System.currentTimeMillis() / 1000L - 1L))); @@ -108,7 +108,7 @@ public void shouldVerifyIssuer() throws Exception { .verifyIssuer(createSingletonJSONNode("iss", "very issuer")); } - @Test(expected = IllegalStateException.class) + @Test(expected = JWTIssuerException.class) public void shouldFailIssuer() throws Exception { new JWTVerifier("such secret", "amaze audience", "very issuer") .verifyIssuer(createSingletonJSONNode("iss", "wow")); @@ -126,7 +126,7 @@ public void shouldVerifyAudience() throws Exception { .verifyAudience(createSingletonJSONNode("aud", "amaze audience")); } - @Test(expected = IllegalStateException.class) + @Test(expected = JWTAudienceException.class) public void shouldFailAudience() throws Exception { new JWTVerifier("such secret", "amaze audience") .verifyAudience(createSingletonJSONNode("aud", "wow")); @@ -151,7 +151,7 @@ public void shouldVerifyArrayAudience() throws Exception { new ObjectMapper().readValue("[ \"foo\", \"amaze audience\" ]", ArrayNode.class))); } - @Test(expected = IllegalStateException.class) + @Test(expected = JWTAudienceException.class) public void shouldFailArrayAudience() throws Exception { new JWTVerifier("such secret", "amaze audience") .verifyAudience(createSingletonJSONNode("aud", From 3d665f366d59ccea82a4c5b3f2d5fe98e01823f9 Mon Sep 17 00:00:00 2001 From: OCW-DUO Date: Mon, 15 Dec 2014 09:25:32 +0100 Subject: [PATCH 49/69] Made project compatible with jdk 1.5 For legacy purposes, the java-jwt project can now be used on 1.5+ --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 98269714..dab8d4b9 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ http://www.jwt.io - 1.6 + 1.5 com.auth0.jwt.internal @@ -49,7 +49,7 @@ com.fasterxml.jackson.core jackson-databind - 2.0.0 + 2.0.1 commons-codec From ae8abc28566c3464111b3325360ac687671a1209 Mon Sep 17 00:00:00 2001 From: Martin Gontovnikas Date: Mon, 22 Dec 2014 18:37:55 -0300 Subject: [PATCH 50/69] Fixed header. Changed type for typ. Fixes #17 --- src/main/java/com/auth0/jwt/JWTSigner.java | 2 +- .../java/com/auth0/jwt/JWTSignerTest.java | 26 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTSigner.java b/src/main/java/com/auth0/jwt/JWTSigner.java index 55084a12..84dfc46d 100644 --- a/src/main/java/com/auth0/jwt/JWTSigner.java +++ b/src/main/java/com/auth0/jwt/JWTSigner.java @@ -91,7 +91,7 @@ private String encodedHeader(Algorithm algorithm) throws UnsupportedEncodingExce // create the header ObjectNode header = JsonNodeFactory.instance.objectNode(); - header.put("type", "JWT"); + header.put("typ", "JWT"); header.put("alg", algorithm.name()); return base64UrlEncode(header.toString().getBytes("UTF-8")); diff --git a/src/test/java/com/auth0/jwt/JWTSignerTest.java b/src/test/java/com/auth0/jwt/JWTSignerTest.java index 9032d6e6..a63f52d3 100644 --- a/src/test/java/com/auth0/jwt/JWTSignerTest.java +++ b/src/test/java/com/auth0/jwt/JWTSignerTest.java @@ -16,14 +16,14 @@ public class JWTSignerTest { public void shouldSignEmpty() throws Exception { HashMap claims = new HashMap(); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); } @Test public void shouldSignEmptyTwoParams() throws Exception { HashMap claims = new HashMap(); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); } @Test @@ -31,7 +31,7 @@ public void shouldSignStringOrURI1() throws Exception { HashMap claims = new HashMap(); claims.put("iss", "foo"); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJpc3MiOiJmb28ifQ.7VNaEEPhOiEXfEgPrxkFFhQCAxl9X3F20sq9KVaVtJM", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJmb28ifQ.UbvkKJx4ubG9SQYs3Hpe6FJl1ix89jSLw0I9GNTnLgY", token); } @Test @@ -39,7 +39,7 @@ public void shouldSignStringOrURI2() throws Exception { HashMap claims = new HashMap(); claims.put("sub", "http://foo"); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJodHRwOi8vZm9vIn0.GYhCLgXYbAXp2Lr8T2yif7ylBVK1XZFkO8hEBa8WP8U", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJodHRwOi8vZm9vIn0.EaYoTXJWUNd_1tWfZo4EZoKUP8hVMJm1LHBQNo4Xfwg", token); } @Test @@ -47,7 +47,7 @@ public void shouldSignStringOrURI3() throws Exception { HashMap claims = new HashMap(); claims.put("aud", ""); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJhdWQiOiIifQ.qobL4k5su7O7ssfCr7drTScIhWjheIc9uxipkR9MC0A", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIifQ.T2EKheH_WVVwybctic8Sqk89miYVKADW0AeXOicDbz8", token); } @Test @@ -58,7 +58,7 @@ public void shouldSignStringOrURICollection() throws Exception { aud.add("ftp://foo"); claims.put("aud", aud); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJhdWQiOlsieHl6IiwiZnRwOi8vZm9vIl19.xL_8PVO_8isoFSud1Nlqi8rA3jvdD5zALN3tjcQ0vbk", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsieHl6IiwiZnRwOi8vZm9vIl19.WGpsdOnLJ2k7Rr4WeEuabHO4wNQIhJfPMZot1DrTUgA", token); } @Test @@ -66,7 +66,7 @@ public void shouldSignIntDate1() throws Exception { HashMap claims = new HashMap(); claims.put("exp", 123); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJleHAiOjEyM30.1pI_TNQDCsKc3IEVX_2fcAKJmJZ8j3hhOfAvAdqKE0s", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEyM30.FzAXEHf0LVQPOyRQFftA1VBAj8RmZGEfwQIPSfg_DUg", token); } @Test @@ -74,7 +74,7 @@ public void shouldSignIntDate2() throws Exception { HashMap claims = new HashMap(); claims.put("nbf", 0); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJuYmYiOjB9.uxwAmWxxPZwGRgfiXOHGxrXmxgay6tv93Pyiya3O5dE", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjB9.ChHEHjtyr4qOUMu6KDsa2BjGXtkGurboD5ljr99gVzw", token); } @Test @@ -82,7 +82,7 @@ public void shouldSignIntDate3() throws Exception { HashMap claims = new HashMap(); claims.put("iat", Long.MAX_VALUE); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJpYXQiOjkyMjMzNzIwMzY4NTQ3NzU4MDd9.nsfMBVmmDR0u1tVN54UzHDZL2wylDA50YjzN2WxZEsU", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjkyMjMzNzIwMzY4NTQ3NzU4MDd9.7yrsheXoAuqk5hDcbKmT3l6aDNNr7RMnbVe6kVkvv4M", token); } @Test @@ -90,7 +90,7 @@ public void shouldSignString() throws Exception { HashMap claims = new HashMap(); claims.put("jti", "foo"); String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJqdGkiOiJmb28ifQ.6X8nx7sLNxdc4mYNL__gd0ab-m8QfheVHT2Y_2DQJMU", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJmb28ifQ.CriA-W8LKO4bCxy3e2Nu7kx2MxgcHGyFu_GVLMX3bko", token); } @Test @@ -100,7 +100,7 @@ public void shouldSignNullEqualsMissing() throws Exception { claims.put(claimName, null); } String token = signer.sign(claims); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); } @Test(expected = Exception.class) @@ -163,7 +163,7 @@ public void shouldFailExpectString() throws Exception { public void shouldOptionsNone() throws Exception { HashMap claims = new HashMap(); String token = signer.sign(claims, new JWTSigner.Options()); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.e30.22wExCVEVtV1rZU51TB9W64deZc_ZN7mc_Z1Yq0dmo0", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); } @Test @@ -179,7 +179,7 @@ public void shouldOptionsAlgorithm() throws Exception { HashMap claims = new HashMap(); String token = signer.sign(claims, new JWTSigner.Options().setAlgorithm(Algorithm.HS512)); - assertEquals("eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFM1MTIifQ.e30.gH4cjvHOMA2QcZjwSqO-VZ4tyah8hDMVqUGAOth7vBWweOIzCwohpOlpLoRCKeDD3PyMqE1gwHqGuWDk2VuYmQ", token); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30.11MgCe-_uiheyy_kARCwhSZbeq3IkMn40GLQkczQ4Bjn_lkCYfSeqz0HeeYpitksiQ2bW47N0oGKCOYOlmQPyg", token); } } From 1e6f68764b26f62d39b04da6eca878c0989c6e5a Mon Sep 17 00:00:00 2001 From: Martin Gontovnikas Date: Mon, 22 Dec 2014 19:01:37 -0300 Subject: [PATCH 51/69] Secrets are no longer Base64 Fixes #2 --- src/main/java/com/auth0/jwt/JWTVerifier.java | 35 +++++++++++++------ .../com/auth0/jwt/JWTVerifyException.java | 16 +++++++-- .../java/com/auth0/jwt/JWTVerifierTest.java | 12 +++---- .../java/com/auth0/jwt/RoundtripTest.java | 10 ++---- 4 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index c1a70f0f..16c98792 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -2,11 +2,15 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; + import org.apache.commons.codec.binary.Base64; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; + import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; @@ -23,22 +27,33 @@ */ public class JWTVerifier { - private final String secret; + private final byte[] secret; private final String audience; private final String issuer; - private final Base64 decoder; + private final Base64 decoder = new Base64(true);; private final ObjectMapper mapper; private Map algorithms; - + public JWTVerifier(String secret, String audience, String issuer) { - if (secret == null || "".equals(secret)) { + this(secret.getBytes(Charset.forName("UTF-8")), audience, issuer); + } + + public JWTVerifier(String secret, String audience) { + this(secret, audience, null); + } + + public JWTVerifier(String secret) { + this(secret, null, null); + } + + public JWTVerifier(byte[] secret, String audience, String issuer) { + if (secret == null || secret.length == 0) { throw new IllegalArgumentException("Secret cannot be null or empty"); } - - decoder = new Base64(true); - mapper = new ObjectMapper(); + + mapper = new ObjectMapper(); algorithms = new HashMap(); algorithms.put("HS256", "HmacSHA256"); @@ -50,11 +65,11 @@ public JWTVerifier(String secret, String audience, String issuer) { this.issuer = issuer; } - public JWTVerifier(String secret, String audience) { + public JWTVerifier(byte[] secret, String audience) { this(secret, audience, null); } - public JWTVerifier(String secret) { + public JWTVerifier(byte[] secret) { this(secret, null, null); } @@ -101,7 +116,7 @@ public Map verify(String token) void verifySignature(String[] pieces, String algorithm) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { Mac hmac = Mac.getInstance(algorithm); - hmac.init(new SecretKeySpec(decoder.decodeBase64(secret), algorithm)); + hmac.init(new SecretKeySpec(secret, algorithm)); byte[] sig = hmac.doFinal(new StringBuilder(pieces[0]).append(".").append(pieces[1]).toString().getBytes()); if (!Arrays.equals(sig, decoder.decodeBase64(pieces[2]))) { diff --git a/src/main/java/com/auth0/jwt/JWTVerifyException.java b/src/main/java/com/auth0/jwt/JWTVerifyException.java index 08bc583b..39ec2039 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifyException.java +++ b/src/main/java/com/auth0/jwt/JWTVerifyException.java @@ -1,10 +1,22 @@ package com.auth0.jwt; public class JWTVerifyException extends Exception { - public JWTVerifyException() { + /** + * + */ + private static final long serialVersionUID = -4911506451239107610L; + + public JWTVerifyException() { } + + + + public JWTVerifyException(String message, Throwable cause) { + super(message, cause); + } + - public JWTVerifyException(String message) { + public JWTVerifyException(String message) { super(message); } } diff --git a/src/test/java/com/auth0/jwt/JWTVerifierTest.java b/src/test/java/com/auth0/jwt/JWTVerifierTest.java index b5fa998d..7cdc4cd4 100644 --- a/src/test/java/com/auth0/jwt/JWTVerifierTest.java +++ b/src/test/java/com/auth0/jwt/JWTVerifierTest.java @@ -14,13 +14,11 @@ import static org.junit.Assert.assertEquals; public class JWTVerifierTest { + + private static final Base64 decoder = new Base64(true);; - @Test(expected = IllegalArgumentException.class) - public void constructorShouldFailOnNullSecret() { - new JWTVerifier(null); - } - - @Test(expected = IllegalArgumentException.class) + + @Test(expected = IllegalArgumentException.class) public void constructorShouldFailOnEmptySecret() { new JWTVerifier(""); } @@ -85,7 +83,7 @@ public void shouldVerifySignature() throws Exception { "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ" + "." + "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; - final String secret = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"; + byte[] secret = decoder.decodeBase64("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"); new JWTVerifier(secret, "audience") .verifySignature(jws.split("\\."), "HmacSHA256"); } diff --git a/src/test/java/com/auth0/jwt/RoundtripTest.java b/src/test/java/com/auth0/jwt/RoundtripTest.java index 53330e77..b4e61144 100644 --- a/src/test/java/com/auth0/jwt/RoundtripTest.java +++ b/src/test/java/com/auth0/jwt/RoundtripTest.java @@ -17,17 +17,11 @@ */ public class RoundtripTest { private static final String SECRET; - private static final String SECRET_BASE64; static { - try { - SECRET = "my secret"; - SECRET_BASE64 = Base64.encodeBase64String(SECRET.getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } + SECRET = "my secret"; } private static JWTSigner signer = new JWTSigner(SECRET); - private static JWTVerifier verifier = new JWTVerifier(SECRET_BASE64); + private static JWTVerifier verifier = new JWTVerifier(SECRET); /* * Roundtrip of different datatypes. From e81ab98be5a7fc9c07137e104cffcc9e923d7260 Mon Sep 17 00:00:00 2001 From: Martin Gontovnikas Date: Mon, 22 Dec 2014 19:12:31 -0300 Subject: [PATCH 52/69] Fixed docs --- src/main/java/com/auth0/jwt/JWTSigner.java | 35 +++++++++----------- src/main/java/com/auth0/jwt/JWTVerifier.java | 9 +++-- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTSigner.java b/src/main/java/com/auth0/jwt/JWTSigner.java index 84dfc46d..5c2a9420 100644 --- a/src/main/java/com/auth0/jwt/JWTSigner.java +++ b/src/main/java/com/auth0/jwt/JWTSigner.java @@ -27,7 +27,7 @@ */ public class JWTSigner { private final String secret; - + public JWTSigner(String secret) { this.secret = secret; } @@ -36,7 +36,7 @@ public JWTSigner(String secret) { * Generate a JSON Web Token. * using the default algorithm HMAC SHA-256 ("HS256") * and no claims automatically set. - * + * * @param claims A map of the JWT claims that form the payload. Registered claims * must be of appropriate Java datatype as following: *
    @@ -47,9 +47,8 @@ public JWTSigner(String secret) { * All claims with a null value are left out the JWT. * Any claims set automatically as specified in * the "options" parameter override claims in this map. - * - * @param secret Key to use in signing. Used as-is without Base64 encoding. - * + * + * * @param options Allow choosing the signing algorithm, and automatic setting of some registered claims. */ public String sign(Map claims, Options options) { @@ -72,15 +71,11 @@ public String sign(Map claims, Options options) { /** * Generate a JSON Web Token using the default algorithm HMAC SHA-256 ("HS256") * and no claims automatically set. - * - * @param secret Key to use in signing. Used as-is without Base64 encoding. - * - * For details, see the two parameter variant of this method. */ public String sign(Map claims) { return sign(claims, null); } - + /** * Generate the header part of a JSON web token. */ @@ -99,7 +94,7 @@ private String encodedHeader(Algorithm algorithm) throws UnsupportedEncodingExce /** * Generate the JSON web token payload string from the claims. - * @param options + * @param options */ private String encodedPayload(Map _claims, Options options) throws Exception { Map claims = new HashMap(_claims); @@ -110,14 +105,14 @@ private String encodedPayload(Map _claims, Options options) thro enforceIntDate(claims, "nbf"); enforceIntDate(claims, "iat"); enforceString(claims, "jti"); - + if (options != null) processPayloadOptions(claims, options); String payload = new ObjectMapper().writeValueAsString(claims); return base64UrlEncode(payload.getBytes("UTF-8")); } - + private void processPayloadOptions(Map claims, Options options) { long now = System.currentTimeMillis() / 1000l; if (options.expirySeconds != null) @@ -202,7 +197,7 @@ private String checkStringOrURI(Object value) { } return null; } - + /** * Sign the header and payload */ @@ -269,7 +264,7 @@ public static class Options { private Integer notValidBeforeLeeway; private boolean issuedAt; private boolean jwtId; - + public Algorithm getAlgorithm() { return algorithm; } @@ -280,8 +275,8 @@ public Options setAlgorithm(Algorithm algorithm) { this.algorithm = algorithm; return this; } - - + + public Integer getExpirySeconds() { return expirySeconds; } @@ -293,7 +288,7 @@ public Options setExpirySeconds(Integer expirySeconds) { this.expirySeconds = expirySeconds; return this; } - + public Integer getNotValidBeforeLeeway() { return notValidBeforeLeeway; } @@ -305,7 +300,7 @@ public Options setNotValidBeforeLeeway(Integer notValidBeforeLeeway) { this.notValidBeforeLeeway = notValidBeforeLeeway; return this; } - + public boolean isIssuedAt() { return issuedAt; } @@ -317,7 +312,7 @@ public Options setIssuedAt(boolean issuedAt) { this.issuedAt = issuedAt; return this; } - + public boolean isJwtId() { return jwtId; } diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 16c98792..2751e836 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -21,7 +21,6 @@ /** * JWT Java Implementation - *

    * Adapted from https://bitbucket.org/lluisfaja/javajwt/wiki/Home * See JWTVerifier.java */ @@ -35,7 +34,7 @@ public class JWTVerifier { private final ObjectMapper mapper; private Map algorithms; - + public JWTVerifier(String secret, String audience, String issuer) { this(secret.getBytes(Charset.forName("UTF-8")), audience, issuer); } @@ -47,12 +46,12 @@ public JWTVerifier(String secret, String audience) { public JWTVerifier(String secret) { this(secret, null, null); } - + public JWTVerifier(byte[] secret, String audience, String issuer) { if (secret == null || secret.length == 0) { throw new IllegalArgumentException("Secret cannot be null or empty"); } - + mapper = new ObjectMapper(); algorithms = new HashMap(); @@ -177,4 +176,4 @@ JsonNode decodeAndParse(String b64String) throws IOException { JsonNode jwtHeader = mapper.readValue(jsonString, JsonNode.class); return jwtHeader; } -} \ No newline at end of file +} From e27ac202d6ee421504f06baee837f672249a0cf4 Mon Sep 17 00:00:00 2001 From: Martin Gontovnikas Date: Mon, 22 Dec 2014 19:12:53 -0300 Subject: [PATCH 53/69] [maven-release-plugin] prepare release java-jwt-2.0.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dab8d4b9..d2eed879 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 1.0.1-SNAPSHOT + 2.0.0 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From b9683f8dbf0ba3d16fb78723f250c78198e92b24 Mon Sep 17 00:00:00 2001 From: Martin Gontovnikas Date: Mon, 22 Dec 2014 19:13:00 -0300 Subject: [PATCH 54/69] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d2eed879..a93869bf 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.0.0 + 2.0.1-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From b227e8135f259e30c7a718b22030360bba8c2111 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Thu, 15 Jan 2015 11:56:25 -0300 Subject: [PATCH 55/69] Using constant-time comparison for signatures. --- src/main/java/com/auth0/jwt/JWTVerifier.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 2751e836..91decc66 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -12,6 +12,7 @@ import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.security.InvalidKeyException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.util.ArrayList; @@ -118,7 +119,7 @@ void verifySignature(String[] pieces, String algorithm) throws NoSuchAlgorithmEx hmac.init(new SecretKeySpec(secret, algorithm)); byte[] sig = hmac.doFinal(new StringBuilder(pieces[0]).append(".").append(pieces[1]).toString().getBytes()); - if (!Arrays.equals(sig, decoder.decodeBase64(pieces[2]))) { + if (!MessageDigest.isEqual(sig, decoder.decodeBase64(pieces[2]))) { throw new SignatureException("signature verification failed"); } } From aae6118fef7ea5081719b39fad9181eab02f292f Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Thu, 15 Jan 2015 14:04:25 -0300 Subject: [PATCH 56/69] [maven-release-plugin] prepare release java-jwt-2.0.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a93869bf..a6d49fb7 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.0.1-SNAPSHOT + 2.0.1 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 6b5b7bab20ca314c7759c2fd71725fea1ef52cdf Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Thu, 15 Jan 2015 14:04:30 -0300 Subject: [PATCH 57/69] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a6d49fb7..45d6af06 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.0.1 + 2.0.2-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From 725e3eae280b61fca27351ffa3890f209f26b428 Mon Sep 17 00:00:00 2001 From: Sean Payne Date: Sat, 24 Jan 2015 15:51:58 -0800 Subject: [PATCH 58/69] Changed secret data type from string to bytes. --- src/main/java/com/auth0/jwt/JWTSigner.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTSigner.java b/src/main/java/com/auth0/jwt/JWTSigner.java index 5c2a9420..e98c3522 100644 --- a/src/main/java/com/auth0/jwt/JWTSigner.java +++ b/src/main/java/com/auth0/jwt/JWTSigner.java @@ -26,9 +26,9 @@ * No support for RSA encryption at present */ public class JWTSigner { - private final String secret; + private final byte[] secret; - public JWTSigner(String secret) { + public JWTSigner(byte[] secret) { this.secret = secret; } @@ -216,7 +216,7 @@ private String base64UrlEncode(byte[] str) { /** * Switch the signing algorithm based on input, RSA not supported */ - private static byte[] sign(Algorithm algorithm, String msg, String secret) throws Exception { + private static byte[] sign(Algorithm algorithm, String msg, byte[] secret) throws Exception { switch (algorithm) { case HS256: case HS384: @@ -233,9 +233,9 @@ private static byte[] sign(Algorithm algorithm, String msg, String secret) throw /** * Sign an input string using HMAC and return the encrypted bytes */ - private static byte[] signHmac(Algorithm algorithm, String msg, String secret) throws Exception { + private static byte[] signHmac(Algorithm algorithm, String msg, byte[] secret) throws Exception { Mac mac = Mac.getInstance(algorithm.getValue()); - mac.init(new SecretKeySpec(secret.getBytes(), algorithm.getValue())); + mac.init(new SecretKeySpec(secret, algorithm.getValue())); return mac.doFinal(msg.getBytes()); } From 5387505b61376f83ca5299e461a71f61e8284ec5 Mon Sep 17 00:00:00 2001 From: Sean Payne Date: Sat, 24 Jan 2015 15:52:33 -0800 Subject: [PATCH 59/69] Revert "Changed secret data type from string to bytes." This reverts commit 725e3eae280b61fca27351ffa3890f209f26b428. --- src/main/java/com/auth0/jwt/JWTSigner.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTSigner.java b/src/main/java/com/auth0/jwt/JWTSigner.java index e98c3522..5c2a9420 100644 --- a/src/main/java/com/auth0/jwt/JWTSigner.java +++ b/src/main/java/com/auth0/jwt/JWTSigner.java @@ -26,9 +26,9 @@ * No support for RSA encryption at present */ public class JWTSigner { - private final byte[] secret; + private final String secret; - public JWTSigner(byte[] secret) { + public JWTSigner(String secret) { this.secret = secret; } @@ -216,7 +216,7 @@ private String base64UrlEncode(byte[] str) { /** * Switch the signing algorithm based on input, RSA not supported */ - private static byte[] sign(Algorithm algorithm, String msg, byte[] secret) throws Exception { + private static byte[] sign(Algorithm algorithm, String msg, String secret) throws Exception { switch (algorithm) { case HS256: case HS384: @@ -233,9 +233,9 @@ private static byte[] sign(Algorithm algorithm, String msg, byte[] secret) throw /** * Sign an input string using HMAC and return the encrypted bytes */ - private static byte[] signHmac(Algorithm algorithm, String msg, byte[] secret) throws Exception { + private static byte[] signHmac(Algorithm algorithm, String msg, String secret) throws Exception { Mac mac = Mac.getInstance(algorithm.getValue()); - mac.init(new SecretKeySpec(secret, algorithm.getValue())); + mac.init(new SecretKeySpec(secret.getBytes(), algorithm.getValue())); return mac.doFinal(msg.getBytes()); } From a9bbdfb3b386598a111e9e8e4e2537c75e0d4c32 Mon Sep 17 00:00:00 2001 From: Sean Payne Date: Sat, 24 Jan 2015 15:56:59 -0800 Subject: [PATCH 60/69] Changed storage of secret to use an array of bytes instead of a string. Added new constructor to accept the array of bytes. --- src/main/java/com/auth0/jwt/JWTSigner.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTSigner.java b/src/main/java/com/auth0/jwt/JWTSigner.java index 5c2a9420..3468bfd2 100644 --- a/src/main/java/com/auth0/jwt/JWTSigner.java +++ b/src/main/java/com/auth0/jwt/JWTSigner.java @@ -26,9 +26,13 @@ * No support for RSA encryption at present */ public class JWTSigner { - private final String secret; + private final byte[] secret; public JWTSigner(String secret) { + this(secret.getBytes()); + } + + public JWTSigner(byte[] secret) { this.secret = secret; } @@ -216,7 +220,7 @@ private String base64UrlEncode(byte[] str) { /** * Switch the signing algorithm based on input, RSA not supported */ - private static byte[] sign(Algorithm algorithm, String msg, String secret) throws Exception { + private static byte[] sign(Algorithm algorithm, String msg, byte[] secret) throws Exception { switch (algorithm) { case HS256: case HS384: @@ -233,9 +237,9 @@ private static byte[] sign(Algorithm algorithm, String msg, String secret) throw /** * Sign an input string using HMAC and return the encrypted bytes */ - private static byte[] signHmac(Algorithm algorithm, String msg, String secret) throws Exception { + private static byte[] signHmac(Algorithm algorithm, String msg, byte[] secret) throws Exception { Mac mac = Mac.getInstance(algorithm.getValue()); - mac.init(new SecretKeySpec(secret.getBytes(), algorithm.getValue())); + mac.init(new SecretKeySpec(secret, algorithm.getValue())); return mac.doFinal(msg.getBytes()); } From e21dd12ac14343500d80984a7f4595993cbd79e0 Mon Sep 17 00:00:00 2001 From: Sean Payne Date: Sat, 24 Jan 2015 16:23:17 -0800 Subject: [PATCH 61/69] Added unit test to test byte array-constructed signer. --- .../com/auth0/jwt/JWTSignerTest_Bytes.java | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 src/test/java/com/auth0/jwt/JWTSignerTest_Bytes.java diff --git a/src/test/java/com/auth0/jwt/JWTSignerTest_Bytes.java b/src/test/java/com/auth0/jwt/JWTSignerTest_Bytes.java new file mode 100644 index 00000000..93cffda4 --- /dev/null +++ b/src/test/java/com/auth0/jwt/JWTSignerTest_Bytes.java @@ -0,0 +1,200 @@ +package com.auth0.jwt; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; + +import static org.junit.Assert.assertEquals; + +public class JWTSignerTest_Bytes { + private static JWTSigner signer = new JWTSigner(new byte[] { 109, 121, 32, 115, 101, 99, 114, 101, 116}); + + @Test + public void shouldSignEmpty() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); + } + + @Test + public void shouldSignEmptyTwoParams() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); + } + + @Test + public void shouldSignStringOrURI1() throws Exception { + HashMap claims = new HashMap(); + claims.put("iss", "foo"); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJmb28ifQ.UbvkKJx4ubG9SQYs3Hpe6FJl1ix89jSLw0I9GNTnLgY", token); + } + + @Test + public void shouldSignStringOrURI2() throws Exception { + HashMap claims = new HashMap(); + claims.put("sub", "http://foo"); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJodHRwOi8vZm9vIn0.EaYoTXJWUNd_1tWfZo4EZoKUP8hVMJm1LHBQNo4Xfwg", token); + } + + @Test + public void shouldSignStringOrURI3() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", ""); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIifQ.T2EKheH_WVVwybctic8Sqk89miYVKADW0AeXOicDbz8", token); + } + + @Test + public void shouldSignStringOrURICollection() throws Exception { + HashMap claims = new HashMap(); + LinkedList aud = new LinkedList(); + aud.add("xyz"); + aud.add("ftp://foo"); + claims.put("aud", aud); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsieHl6IiwiZnRwOi8vZm9vIl19.WGpsdOnLJ2k7Rr4WeEuabHO4wNQIhJfPMZot1DrTUgA", token); + } + + @Test + public void shouldSignIntDate1() throws Exception { + HashMap claims = new HashMap(); + claims.put("exp", 123); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEyM30.FzAXEHf0LVQPOyRQFftA1VBAj8RmZGEfwQIPSfg_DUg", token); + } + + @Test + public void bytes_shouldSignIntDate1() throws Exception { + HashMap claims = new HashMap(); + claims.put("exp", 123); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEyM30.FzAXEHf0LVQPOyRQFftA1VBAj8RmZGEfwQIPSfg_DUg", token); + } + + @Test + public void shouldSignIntDate2() throws Exception { + HashMap claims = new HashMap(); + claims.put("nbf", 0); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjB9.ChHEHjtyr4qOUMu6KDsa2BjGXtkGurboD5ljr99gVzw", token); + } + + @Test + public void shouldSignIntDate3() throws Exception { + HashMap claims = new HashMap(); + claims.put("iat", Long.MAX_VALUE); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjkyMjMzNzIwMzY4NTQ3NzU4MDd9.7yrsheXoAuqk5hDcbKmT3l6aDNNr7RMnbVe6kVkvv4M", token); + } + + @Test + public void bytes_shouldSignIntDate2() throws Exception { + HashMap claims = new HashMap(); + claims.put("nbf", 0); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjB9.ChHEHjtyr4qOUMu6KDsa2BjGXtkGurboD5ljr99gVzw", token); + } + + @Test + public void shouldSignString() throws Exception { + HashMap claims = new HashMap(); + claims.put("jti", "foo"); + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJmb28ifQ.CriA-W8LKO4bCxy3e2Nu7kx2MxgcHGyFu_GVLMX3bko", token); + } + + @Test + public void shouldSignNullEqualsMissing() throws Exception { + HashMap claims = new HashMap(); + for (String claimName : Arrays.asList("iss", "sub", "aud", "exp", "nbf", "iat", "jti")) { + claims.put(claimName, null); + } + String token = signer.sign(claims); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURI1() throws Exception { + HashMap claims = new HashMap(); + claims.put("iss", 0); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURI2() throws Exception { + HashMap claims = new HashMap(); + claims.put("sub", ":"); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURICollection1() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", 0); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURICollection2() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", Arrays.asList(0)); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectStringOrURICollection3() throws Exception { + HashMap claims = new HashMap(); + claims.put("aud", Arrays.asList(":")); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectIntDate1() throws Exception { + HashMap claims = new HashMap(); + claims.put("exp", -1); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectIntDate2() throws Exception { + HashMap claims = new HashMap(); + claims.put("nbf", "100"); + signer.sign(claims); + } + + @Test(expected = Exception.class) + public void shouldFailExpectString() throws Exception { + HashMap claims = new HashMap(); + claims.put("jti", 100); + signer.sign(claims); + } + + @Test + public void shouldOptionsNone() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, new JWTSigner.Options()); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.86pkOAQxvnSDd91EThNNpOTbO-hbvxdssnFjQqT04NU", token); + } + + @Test + public void shouldOptionsAll() throws Exception { + HashMap claims = new HashMap(); + signer.sign(claims, new JWTSigner.Options() + .setExpirySeconds(1000).setNotValidBeforeLeeway(5) + .setIssuedAt(true).setJwtId(true)); + } + + @Test + public void shouldOptionsAlgorithm() throws Exception { + HashMap claims = new HashMap(); + String token = signer.sign(claims, + new JWTSigner.Options().setAlgorithm(Algorithm.HS512)); + assertEquals("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.e30.11MgCe-_uiheyy_kARCwhSZbeq3IkMn40GLQkczQ4Bjn_lkCYfSeqz0HeeYpitksiQ2bW47N0oGKCOYOlmQPyg", token); + } + +} From c33fe66c208bb490c2c9a1b1b3b50c3763f5014d Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Mon, 26 Jan 2015 18:42:45 -0300 Subject: [PATCH 62/69] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 940c35c0..c7a284e4 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Yes, here you are: com.auth0 java-jwt - 1.0.0 + 2.0.1 ``` From 53d2a14711673c89c2b409ca7c54deae22e11b6a Mon Sep 17 00:00:00 2001 From: Nathan Totten Date: Tue, 27 Jan 2015 13:22:28 -0800 Subject: [PATCH 63/69] Update and rename LICENSE.md to LICENSE.txt --- LICENSE.md | 20 -------------------- LICENSE.txt | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 20 deletions(-) delete mode 100644 LICENSE.md create mode 100644 LICENSE.txt diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 8258c89c..00000000 --- a/LICENSE.md +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Auth0, Inc - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..bcd1854c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Auth0, Inc. (http://auth0.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 4cf0704f7824a41667a3d2884ca4ff392f02429f Mon Sep 17 00:00:00 2001 From: Nathan Totten Date: Tue, 27 Jan 2015 13:23:16 -0800 Subject: [PATCH 64/69] Update README.md --- README.md | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index c7a284e4..198bcde9 100644 --- a/README.md +++ b/README.md @@ -38,36 +38,17 @@ Yes, here you are: Most of the code have been written by Luis Faja . We just wrapped it in a nicer interface and published it to Maven Central. We'll be adding support for signing and other algorithms in the future. -### FAQ - - -#### Why another JSON Web Token implementation for Java? +### Why another JSON Web Token implementation for Java? We think that current JWT implementations are either too complex or not tested enough. We want something simple with the right number of abstractions. ## Issue Reporting If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. -## License - -The MIT License (MIT) - -Copyright (c) 2014 Auth0, Inc. +## Author -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +[Auth0](auth0.com) -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +## License -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +This project is licensed under the MIT license. See the [LICENSE](LICENSE.txt) file for more info. From 044e4fd75cd2b524faa24b66476a103cc07b50d2 Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sun, 1 Mar 2015 15:19:04 -0300 Subject: [PATCH 65/69] [maven-release-plugin] prepare release java-jwt-2.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 45d6af06..561fa0e4 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.0.2-SNAPSHOT + 2.1.0 Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From e040916f016af65f1bbef870c19737f75bae976b Mon Sep 17 00:00:00 2001 From: Alberto Pose Date: Sun, 1 Mar 2015 15:19:11 -0300 Subject: [PATCH 66/69] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 561fa0e4..7019448c 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.auth0 java-jwt - 2.1.0 + 2.1.1-SNAPSHOT Java JWT Java implementation of JSON Web Token developed against draft-ietf-oauth-json-web-token-08. From a8c310356de384ff7824bc7362c3fd6e7069d8d9 Mon Sep 17 00:00:00 2001 From: Tom Haggie Date: Thu, 2 Apr 2015 01:11:32 -0700 Subject: [PATCH 67/69] JWTVerifier was creating a URL safe base 64 decoder but wasn't using it as it was using the non URL safe static methods on Base64. I'm assuming here that a) these things are wanted to be decoded URL safe b) the verifier isn't expected to be thread safe. --- src/main/java/com/auth0/jwt/JWTVerifier.java | 23 +++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/auth0/jwt/JWTVerifier.java b/src/main/java/com/auth0/jwt/JWTVerifier.java index 91decc66..89e91486 100644 --- a/src/main/java/com/auth0/jwt/JWTVerifier.java +++ b/src/main/java/com/auth0/jwt/JWTVerifier.java @@ -1,25 +1,22 @@ package com.auth0.jwt; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.apache.commons.codec.binary.Base64; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; -import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.codec.binary.Base64; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * JWT Java Implementation * Adapted from https://bitbucket.org/lluisfaja/javajwt/wiki/Home @@ -119,7 +116,7 @@ void verifySignature(String[] pieces, String algorithm) throws NoSuchAlgorithmEx hmac.init(new SecretKeySpec(secret, algorithm)); byte[] sig = hmac.doFinal(new StringBuilder(pieces[0]).append(".").append(pieces[1]).toString().getBytes()); - if (!MessageDigest.isEqual(sig, decoder.decodeBase64(pieces[2]))) { + if (!MessageDigest.isEqual(sig, decoder.decode(pieces[2]))) { throw new SignatureException("signature verification failed"); } } @@ -173,7 +170,7 @@ String getAlgorithm(JsonNode jwtHeader) { } JsonNode decodeAndParse(String b64String) throws IOException { - String jsonString = new String(decoder.decodeBase64(b64String), "UTF-8"); + String jsonString = new String(decoder.decode(b64String), "UTF-8"); JsonNode jwtHeader = mapper.readValue(jsonString, JsonNode.class); return jwtHeader; } From 72a989a84ccd23e7f34aeed80ce289024b570d65 Mon Sep 17 00:00:00 2001 From: christopherthielen Date: Tue, 21 Apr 2015 19:24:59 -0500 Subject: [PATCH 68/69] fix(test): Fix race condition in RoundtripTest.shouldOptionsIat closes #28 --- src/test/java/com/auth0/jwt/RoundtripTest.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/auth0/jwt/RoundtripTest.java b/src/test/java/com/auth0/jwt/RoundtripTest.java index b4e61144..35299337 100644 --- a/src/test/java/com/auth0/jwt/RoundtripTest.java +++ b/src/test/java/com/auth0/jwt/RoundtripTest.java @@ -97,15 +97,17 @@ public void shouldBoolean() throws Exception { @Test public void shouldOptionsIat() throws Exception { HashMap claims = new HashMap(); - String token = signer.sign(claims, - new JWTSigner.Options().setIssuedAt(true)); + long before = System.currentTimeMillis(); + String token = signer.sign(claims, new JWTSigner.Options().setIssuedAt(true)); + long after = System.currentTimeMillis(); Map decoded = verifier.verify(token); + assertEquals(decoded.size(), 1); long iat = ((Number) decoded.get("iat")).longValue(); - assertTrue(iat >= System.currentTimeMillis() / 1000l); - assertTrue(iat < System.currentTimeMillis() / 1000l + 10); + assertTrue(iat >= before / 1000l); + assertTrue(iat <= after / 1000l); } - + @Test public void shouldOptionsTimestamps() throws Exception { HashMap claims = new HashMap(); From 588cd8714149ac9621c5ee3561d67dd6f05890b9 Mon Sep 17 00:00:00 2001 From: nicholasle Date: Wed, 27 May 2015 10:51:59 +0900 Subject: [PATCH 69/69] Update README.md Update the sample application to make it works for Auth0's users. Update maven to the latest version of java-jwt 2.1.0 --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 198bcde9..b90b5f9b 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,18 @@ An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) developed against `draft-ietf-oauth-json-web-token-08`. ### Usage +Note for Auth0 users: +By default, Auth0's CLIENT_SECRET is base64-encoded. +To work with JWTVerifier, it must be decoded first. ```java public class Application { public static void main (String [] args) { try { + Base64 decoder = new Base64(true); + byte[] secret = decoder.decodeBase64(CLIENT_SECRET); Map decodedPayload = - new JWTVerifier("secret", "audience").verify("my-token"); + new JWTVerifier(secret, "audience").verify("my-token"); // Get custom fields from decoded Payload System.out.println(decodedPayload.get("name")); @@ -30,7 +35,7 @@ Yes, here you are: com.auth0 java-jwt - 2.0.1 + 2.1.0 ```