Skip to content

Commit 535af31

Browse files
committed
add public claim validation.
1 parent 3eac127 commit 535af31

5 files changed

Lines changed: 296 additions & 17 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.auth0.jwtdecodejava.exceptions;
2+
3+
4+
public class InvalidClaimException extends JWTException {
5+
public InvalidClaimException(String description) {
6+
super(description);
7+
}
8+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.auth0.jwtdecodejava.impl;
2+
3+
import com.auth0.jwtdecodejava.JWTDecoder;
4+
import com.auth0.jwtdecodejava.exceptions.InvalidClaimException;
5+
import com.auth0.jwtdecodejava.exceptions.JWTException;
6+
import com.auth0.jwtdecodejava.interfaces.JWT;
7+
8+
import java.util.Arrays;
9+
import java.util.Date;
10+
import java.util.HashMap;
11+
import java.util.Map;
12+
13+
public class JWTVerifier {
14+
private final Map<String, Object> claims;
15+
16+
private JWTVerifier() {
17+
this.claims = new HashMap<>();
18+
}
19+
20+
public static JWTVerifier init(){
21+
return new JWTVerifier();
22+
}
23+
24+
public JWTVerifier withIssuer(String issuer) {
25+
requireClaim(Claims.ISSUER, issuer);
26+
return this;
27+
}
28+
29+
public JWTVerifier withSubject(String subject) {
30+
requireClaim(Claims.SUBJECT, subject);
31+
return this;
32+
}
33+
34+
public JWTVerifier withAudience(String[] audience) {
35+
requireClaim(Claims.AUDIENCE, audience);
36+
return this;
37+
}
38+
39+
public JWTVerifier withExpiresAt(Date expiresAt) {
40+
requireClaim(Claims.EXPIRES_AT, expiresAt);
41+
return this;
42+
}
43+
44+
public JWTVerifier withNotBefore(Date notBefore) {
45+
requireClaim(Claims.NOT_BEFORE, notBefore);
46+
return this;
47+
}
48+
49+
public JWTVerifier withIssuedAt(Date issuedAt) {
50+
requireClaim(Claims.ISSUED_AT, issuedAt);
51+
return this;
52+
}
53+
54+
public JWTVerifier withJWTId(String jwtId) {
55+
requireClaim(Claims.JWT_ID, jwtId);
56+
return this;
57+
}
58+
59+
public JWT verify(String jwt) throws JWTException{
60+
JWT decode = JWTDecoder.decode(jwt);
61+
verifyClaims(decode, claims);
62+
return decode;
63+
}
64+
65+
private void verifyClaims(JWT jwt, Map<String, Object> claims) {
66+
for (Map.Entry<String, Object> entry : claims.entrySet()) {
67+
assertValidClaim(jwt, entry.getKey(), entry.getValue());
68+
}
69+
}
70+
71+
private void assertValidClaim(JWT jwt, String claimName, Object expectedValue) {
72+
boolean isValid;
73+
if (Claims.AUDIENCE.equals(claimName)) {
74+
isValid = Arrays.equals(jwt.getAudience(), (String[]) expectedValue);
75+
} else if (Claims.NOT_BEFORE.equals(claimName) || Claims.EXPIRES_AT.equals(claimName) || Claims.ISSUED_AT.equals(claimName)) {
76+
Date dateValue = (Date) expectedValue;
77+
isValid = dateValue.equals(jwt.getClaim(claimName).asDate());
78+
} else {
79+
String stringValue = (String) expectedValue;
80+
isValid = stringValue.equals(jwt.getClaim(claimName).asString());
81+
}
82+
83+
if (!isValid) {
84+
throw new InvalidClaimException(String.format("The Claim '%s' value doesn't match the required one.", claimName));
85+
}
86+
}
87+
88+
private void requireClaim(String name, Object value) {
89+
if (value == null) {
90+
claims.remove(name);
91+
return;
92+
}
93+
claims.put(name, value);
94+
}
95+
}

lib/src/main/java/com/auth0/jwtdecodejava/impl/PayloadDeserializer.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,19 @@ public Payload deserialize(JsonParser p, DeserializationContext ctxt) throws IOE
3232
throw new JWTException("Null map");
3333
}
3434

35-
String issuer = removeString(tree, Claims.ISSUER);
36-
String subject = removeString(tree, Claims.SUBJECT);
37-
String[] audience = removeStringOrArray(tree, Claims.AUDIENCE);
38-
Date expiresAt = removeDate(tree, Claims.EXPIRES_AT);
39-
Date notBefore = removeDate(tree, Claims.NOT_BEFORE);
40-
Date issuedAt = removeDate(tree, Claims.ISSUED_AT);
41-
String jwtId = removeString(tree, Claims.JWT_ID);
35+
String issuer = getString(tree, Claims.ISSUER);
36+
String subject = getString(tree, Claims.SUBJECT);
37+
String[] audience = getStringOrArray(tree, Claims.AUDIENCE);
38+
Date expiresAt = getDate(tree, Claims.EXPIRES_AT);
39+
Date notBefore = getDate(tree, Claims.NOT_BEFORE);
40+
Date issuedAt = getDate(tree, Claims.ISSUED_AT);
41+
String jwtId = getString(tree, Claims.JWT_ID);
4242

4343
return new PayloadImpl(issuer, subject, audience, expiresAt, notBefore, issuedAt, jwtId, tree);
4444
}
4545

46-
private String[] removeStringOrArray(Map<String, JsonNode> tree, String claimName) {
47-
JsonNode node = tree.remove(claimName);
46+
private String[] getStringOrArray(Map<String, JsonNode> tree, String claimName) {
47+
JsonNode node = tree.get(claimName);
4848
if (node == null || node.isNull() || !(node.isArray() || node.isTextual())) {
4949
return null;
5050
}
@@ -64,17 +64,17 @@ private String[] removeStringOrArray(Map<String, JsonNode> tree, String claimNam
6464
return arr;
6565
}
6666

67-
private Date removeDate(Map<String, JsonNode> tree, String claimName) {
68-
JsonNode node = tree.remove(claimName);
67+
private Date getDate(Map<String, JsonNode> tree, String claimName) {
68+
JsonNode node = tree.get(claimName);
6969
if (node == null || node.isNull() || !node.canConvertToLong()) {
7070
return null;
7171
}
7272
final long ms = node.asLong() * 1000;
7373
return new Date(ms);
7474
}
7575

76-
private String removeString(Map<String, JsonNode> tree, String claimName) {
77-
JsonNode node = tree.remove(claimName);
76+
private String getString(Map<String, JsonNode> tree, String claimName) {
77+
JsonNode node = tree.get(claimName);
7878
if (node == null || node.isNull()) {
7979
return null;
8080
}

lib/src/main/java/com/auth0/jwtdecodejava/impl/PayloadImpl.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,17 @@ class PayloadImpl implements Payload {
1818
private final Date notBefore;
1919
private final Date issuedAt;
2020
private final String jwtId;
21-
private final Map<String, JsonNode> extras;
21+
private final Map<String, JsonNode> tree;
2222

23-
PayloadImpl(String issuer, String subject, String[] audience, Date expiresAt, Date notBefore, Date issuedAt, String jwtId, Map<String, JsonNode> extras) {
23+
PayloadImpl(String issuer, String subject, String[] audience, Date expiresAt, Date notBefore, Date issuedAt, String jwtId, Map<String, JsonNode> tree) {
2424
this.issuer = issuer;
2525
this.subject = subject;
2626
this.audience = audience;
2727
this.expiresAt = expiresAt;
2828
this.notBefore = notBefore;
2929
this.issuedAt = issuedAt;
3030
this.jwtId = jwtId;
31-
this.extras = extras;
31+
this.tree = tree;
3232
}
3333

3434
@Override
@@ -68,7 +68,7 @@ public String getId() {
6868

6969
@Override
7070
public Claim getClaim(@NotNull String name) {
71-
return extractClaim(name, extras);
71+
return extractClaim(name, tree);
7272
}
7373

7474
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package com.auth0.jwtdecodejava.impl;
2+
3+
import com.auth0.jwtdecodejava.exceptions.InvalidClaimException;
4+
import com.auth0.jwtdecodejava.interfaces.JWT;
5+
import org.junit.Rule;
6+
import org.junit.Test;
7+
import org.junit.rules.ExpectedException;
8+
9+
import java.util.Date;
10+
11+
import static org.hamcrest.Matchers.is;
12+
import static org.hamcrest.Matchers.notNullValue;
13+
import static org.junit.Assert.assertThat;
14+
15+
public class JWTVerifierTest {
16+
17+
@Rule
18+
public ExpectedException exception = ExpectedException.none();
19+
20+
@Test
21+
public void shouldValidateIssuer() throws Exception {
22+
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.MT8JrEvIB69bH5W9RUR2ap-H3e69fM7LEQCiZF-7FbI";
23+
JWT jwt = JWTVerifier.init()
24+
.withIssuer("auth0")
25+
.verify(token);
26+
27+
assertThat(jwt, is(notNullValue()));
28+
}
29+
30+
@Test
31+
public void shouldThrowOnInvalidIssuer() throws Exception {
32+
exception.expect(InvalidClaimException.class);
33+
exception.expectMessage("The Claim 'iss' value doesn't match the required one.");
34+
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.MT8JrEvIB69bH5W9RUR2ap-H3e69fM7LEQCiZF-7FbI";
35+
JWTVerifier.init()
36+
.withIssuer("invalid")
37+
.verify(token);
38+
}
39+
40+
41+
@Test
42+
public void shouldValidateSubject() throws Exception {
43+
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I";
44+
JWT jwt = JWTVerifier.init()
45+
.withSubject("1234567890")
46+
.verify(token);
47+
48+
assertThat(jwt, is(notNullValue()));
49+
}
50+
51+
@Test
52+
public void shouldThrowOnInvalidSubject() throws Exception {
53+
exception.expect(InvalidClaimException.class);
54+
exception.expectMessage("The Claim 'sub' value doesn't match the required one.");
55+
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I";
56+
JWTVerifier.init()
57+
.withSubject("invalid")
58+
.verify(token);
59+
}
60+
61+
@Test
62+
public void shouldValidateAudience() throws Exception {
63+
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.MT8JrEvIB69bH5W9RUR2ap-H3e69fM7LEQCiZF-7FbI";
64+
JWT jwt = JWTVerifier.init()
65+
.withAudience(new String[]{"Mark"})
66+
.verify(token);
67+
68+
assertThat(jwt, is(notNullValue()));
69+
70+
String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIl19.MT8JrEvIB69bH5W9RUR2ap-H3e69fM7LEQCiZF-7FbI";
71+
JWT jwtArr = JWTVerifier.init()
72+
.withAudience(new String[]{"Mark", "David"})
73+
.verify(tokenArr);
74+
75+
assertThat(jwtArr, is(notNullValue()));
76+
}
77+
78+
@Test
79+
public void shouldThrowOnInvalidAudience() throws Exception {
80+
exception.expect(InvalidClaimException.class);
81+
exception.expectMessage("The Claim 'aud' value doesn't match the required one.");
82+
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I";
83+
JWTVerifier.init()
84+
.withAudience(new String[]{"nope"})
85+
.verify(token);
86+
}
87+
88+
@Test
89+
public void shouldValidateExpiresAt() throws Exception {
90+
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.MT8JrEvIB69bH5W9RUR2ap-H3e69fM7LEQCiZF-7FbI";
91+
JWT jwt = JWTVerifier.init()
92+
.withExpiresAt(new Date(1477592000))
93+
.verify(token);
94+
95+
assertThat(jwt, is(notNullValue()));
96+
}
97+
98+
@Test
99+
public void shouldThrowOnInvalidExpiresAt() throws Exception {
100+
exception.expect(InvalidClaimException.class);
101+
exception.expectMessage("The Claim 'exp' value doesn't match the required one.");
102+
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.MT8JrEvIB69bH5W9RUR2ap-H3e69fM7LEQCiZF-7FbI";
103+
JWTVerifier.init()
104+
.withExpiresAt(new Date())
105+
.verify(token);
106+
}
107+
108+
@Test
109+
public void shouldValidateNotBefore() throws Exception {
110+
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0Nzc1OTJ9.MT8JrEvIB69bH5W9RUR2ap-H3e69fM7LEQCiZF-7FbI";
111+
JWT jwt = JWTVerifier.init()
112+
.withNotBefore(new Date(1477592000))
113+
.verify(token);
114+
115+
assertThat(jwt, is(notNullValue()));
116+
}
117+
118+
@Test
119+
public void shouldThrowOnInvalidNotBefore() throws Exception {
120+
exception.expect(InvalidClaimException.class);
121+
exception.expectMessage("The Claim 'nbf' value doesn't match the required one.");
122+
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0Nzc1OTJ9.MT8JrEvIB69bH5W9RUR2ap-H3e69fM7LEQCiZF-7FbI";
123+
JWTVerifier.init()
124+
.withNotBefore(new Date())
125+
.verify(token);
126+
}
127+
128+
@Test
129+
public void shouldValidateIssuedAt() throws Exception {
130+
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.MT8JrEvIB69bH5W9RUR2ap-H3e69fM7LEQCiZF-7FbI";
131+
JWT jwt = JWTVerifier.init()
132+
.withIssuedAt(new Date(1477592000))
133+
.verify(token);
134+
135+
assertThat(jwt, is(notNullValue()));
136+
}
137+
138+
@Test
139+
public void shouldThrowOnInvalidIssuedAt() throws Exception {
140+
exception.expect(InvalidClaimException.class);
141+
exception.expectMessage("The Claim 'iat' value doesn't match the required one.");
142+
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.MT8JrEvIB69bH5W9RUR2ap-H3e69fM7LEQCiZF-7FbI";
143+
JWTVerifier.init()
144+
.withIssuedAt(new Date())
145+
.verify(token);
146+
}
147+
148+
@Test
149+
public void shouldValidateJWTId() throws Exception {
150+
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJqd3RfaWRfMTIzIn0.MT8JrEvIB69bH5W9RUR2ap-H3e69fM7LEQCiZF-7FbI";
151+
JWT jwt = JWTVerifier.init()
152+
.withJWTId("jwt_id_123")
153+
.verify(token);
154+
155+
assertThat(jwt, is(notNullValue()));
156+
}
157+
158+
@Test
159+
public void shouldThrowOnInvalidJWTId() throws Exception {
160+
exception.expect(InvalidClaimException.class);
161+
exception.expectMessage("The Claim 'jti' value doesn't match the required one.");
162+
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJqd3RfaWRfMTIzIn0.MT8JrEvIB69bH5W9RUR2ap-H3e69fM7LEQCiZF-7FbI";
163+
JWTVerifier.init()
164+
.withJWTId("invalid")
165+
.verify(token);
166+
}
167+
168+
@Test
169+
public void shouldSkipClaimValidationsIfNoClaimsRequired() throws Exception {
170+
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.MT8JrEvIB69bH5W9RUR2ap-H3e69fM7LEQCiZF-7FbI";
171+
JWT jwt = JWTVerifier.init()
172+
.verify(token);
173+
174+
assertThat(jwt, is(notNullValue()));
175+
}
176+
}

0 commit comments

Comments
 (0)