Skip to content

Commit 74c0944

Browse files
JwtTokenAnnationHandler: Richer exceptions, minor cleanup otherwise (#339)
Co-authored-by: Jan-Olav Eide <jan-olav.eide@nav.no>
1 parent 62f6dce commit 74c0944

File tree

4 files changed

+115
-82
lines changed

4 files changed

+115
-82
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
package no.nav.security.token.support.core.exceptions;
22

3+
import java.lang.reflect.Method;
4+
35
public class AnnotationRequiredException extends RuntimeException {
46
public AnnotationRequiredException(String message) {
57
super(message);
68
}
9+
10+
public AnnotationRequiredException(Method method) {
11+
this("Server misconfigured - controller/method ["
12+
+ method.getClass().getName() + "." + method.getName()
13+
+ "] not annotated @Unprotected, @Protected, @RequiredClaims or added to ignore list");
14+
}
715
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
11
package no.nav.security.token.support.core.exceptions;
22

3+
import static java.util.stream.Collectors.toMap;
4+
5+
import java.util.Arrays;
6+
import java.util.Map;
7+
8+
import no.nav.security.token.support.core.api.ProtectedWithClaims;
9+
import no.nav.security.token.support.core.api.RequiredIssuers;
10+
311
public class JwtTokenInvalidClaimException extends RuntimeException {
412

513
public JwtTokenInvalidClaimException(String message) {
614
super(message);
715
}
16+
17+
public JwtTokenInvalidClaimException(RequiredIssuers ann) {
18+
throw new JwtTokenInvalidClaimException("required claims not present in token for any of " + issuersAndClaims(ann));
19+
}
20+
21+
public JwtTokenInvalidClaimException(ProtectedWithClaims ann) {
22+
this("required claims not present in token." + Arrays.asList(ann.claimMap()));
23+
}
24+
25+
private static Map<String, String[]> issuersAndClaims(RequiredIssuers ann) {
26+
return Arrays.stream(ann.value())
27+
.collect(toMap(ProtectedWithClaims::issuer, ProtectedWithClaims::claimMap));
28+
}
829
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,29 @@
11
package no.nav.security.token.support.core.exceptions;
22

3+
import static java.util.stream.Collectors.toList;
4+
5+
import java.util.Arrays;
6+
import java.util.List;
7+
8+
import no.nav.security.token.support.core.api.ProtectedWithClaims;
9+
import no.nav.security.token.support.core.api.RequiredIssuers;
10+
311
public class JwtTokenMissingException extends RuntimeException {
412
public JwtTokenMissingException(String message) {
513
super(message);
614
}
15+
16+
public JwtTokenMissingException(RequiredIssuers ann) {
17+
this("no valid token found in validation context for any of the issuers " + issuers(ann));
18+
}
19+
20+
public JwtTokenMissingException() {
21+
this("no valid token found in validation context");
22+
}
23+
24+
private static List<String> issuers(RequiredIssuers ann) {
25+
return Arrays.stream(ann.value())
26+
.map(ProtectedWithClaims::issuer)
27+
.collect(toList());
28+
}
729
}

token-validation-core/src/main/java/no/nav/security/token/support/core/validation/JwtTokenAnnotationHandler.java

Lines changed: 64 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@
77
import java.lang.reflect.Method;
88
import java.util.Arrays;
99
import java.util.List;
10-
import java.util.Map;
1110
import java.util.Objects;
12-
import java.util.stream.Collectors;
11+
import java.util.Optional;
1312

1413
import org.slf4j.Logger;
1514
import org.slf4j.LoggerFactory;
@@ -26,120 +25,104 @@
2625

2726
public class JwtTokenAnnotationHandler {
2827

29-
private static final Logger log = LoggerFactory.getLogger(JwtTokenAnnotationHandler.class);
28+
private static final List<Class<? extends Annotation>> SUPPORTED_ANNOTATIONS = List.of(RequiredIssuers.class, ProtectedWithClaims.class,
29+
Protected.class, Unprotected.class);
30+
private static final Logger LOG = LoggerFactory.getLogger(JwtTokenAnnotationHandler.class);
3031
private final TokenValidationContextHolder tokenValidationContextHolder;
3132

3233
public JwtTokenAnnotationHandler(TokenValidationContextHolder tokenValidationContextHolder) {
3334
this.tokenValidationContextHolder = tokenValidationContextHolder;
3435
}
3536

36-
public boolean assertValidAnnotation(Method method) throws AnnotationRequiredException {
37-
Annotation annotation = getAnnotation(method,
38-
List.of(RequiredIssuers.class, ProtectedWithClaims.class, Protected.class, Unprotected.class));
39-
if (annotation == null) {
40-
throw new AnnotationRequiredException("Server misconfigured - controller/method ["
41-
+ method.getClass().getName() + "." + method.getName()
42-
+ "] not annotated @Unprotected, @Protected or added to ignore list");
43-
}
44-
return assertValidAnnotation(annotation);
37+
public boolean assertValidAnnotation(Method m) throws AnnotationRequiredException {
38+
return Optional.ofNullable(getAnnotation(m, SUPPORTED_ANNOTATIONS))
39+
.map(this::assertValidAnnotation)
40+
.orElseThrow(() -> new AnnotationRequiredException(m));
4541
}
4642

47-
private boolean assertValidAnnotation(Annotation annotation) {
48-
if (annotation instanceof Unprotected) {
49-
log.debug("annotation is of type={}, no token validation performed.", Unprotected.class.getSimpleName());
43+
private boolean assertValidAnnotation(Annotation a) {
44+
if (a instanceof Unprotected) {
45+
LOG.debug("annotation is of type={}, no token validation performed.", Unprotected.class.getSimpleName());
5046
return true;
5147
}
52-
if (annotation instanceof RequiredIssuers) {
53-
boolean hasToken = false;
54-
var ann = RequiredIssuers.class.cast(annotation);
55-
for (var sub : ann.value()) {
56-
var jwtToken = getJwtToken(sub.issuer(), tokenValidationContextHolder);
57-
if (jwtToken.isEmpty()) {
58-
continue;
59-
}
60-
hasToken = true;
61-
if (handleProtectedWithClaimsAnnotation(sub, jwtToken.get())) {
62-
return true;
63-
}
64-
}
65-
if (!hasToken) {
66-
throw new JwtTokenMissingException("no valid token found in validation context for any of the issuers " + issuers(ann));
67-
}
68-
throw new JwtTokenInvalidClaimException("required claims not present in token for any of " + issuersAndClaims(ann));
48+
if (a instanceof RequiredIssuers) {
49+
handleRequiredIssuers(RequiredIssuers.class.cast(a));
6950
}
70-
if (annotation instanceof ProtectedWithClaims) {
71-
log.debug("annotation is of type={}, do token validation and claim checking.", ProtectedWithClaims.class.getSimpleName());
72-
var ann = ProtectedWithClaims.class.cast(annotation);
73-
var jwtToken = getJwtToken(ann.issuer(), tokenValidationContextHolder);
74-
if (jwtToken.isEmpty()) {
75-
throw new JwtTokenMissingException("no valid token found in validation context");
76-
}
77-
if (!handleProtectedWithClaimsAnnotation(ann, jwtToken.get())) {
78-
throw new JwtTokenInvalidClaimException("required claims not present in token." + Arrays.asList(ann.claimMap()));
79-
}
80-
return true;
51+
if (a instanceof ProtectedWithClaims) {
52+
return handleProtectedWithClaims(ProtectedWithClaims.class.cast(a));
8153
}
82-
if (annotation instanceof Protected) {
83-
log.debug("annotation is of type={}, check if context has valid token.", Protected.class.getSimpleName());
84-
if (contextHasValidToken(tokenValidationContextHolder)) {
85-
return true;
86-
}
87-
throw new JwtTokenMissingException("no valid token found in validation context");
88-
54+
if (a instanceof Protected) {
55+
return handleProtected();
8956
}
90-
log.debug("annotation is unknown, type={}, no token validation performed. but possible bug so throw exception", annotation.annotationType());
57+
LOG.debug("annotation is unknown, type={}, no token validation performed. but possible bug so throw exception", a.annotationType());
9158
return false;
92-
9359
}
9460

95-
private static Map<String, String[]> issuersAndClaims(RequiredIssuers ann) {
96-
return Arrays.stream(ann.value())
97-
.collect(Collectors.toMap(ProtectedWithClaims::issuer, ProtectedWithClaims::claimMap));
61+
private boolean handleProtected() {
62+
LOG.debug("annotation is of type={}, check if context has valid token.", Protected.class.getSimpleName());
63+
if (contextHasValidToken(tokenValidationContextHolder)) {
64+
return true;
65+
}
66+
throw new JwtTokenMissingException();
9867
}
9968

100-
private static List<String> issuers(RequiredIssuers ann) {
101-
return Arrays.stream(ann.value()).map(ProtectedWithClaims::issuer).collect(Collectors.toList());
69+
private boolean handleProtectedWithClaims(ProtectedWithClaims a) {
70+
LOG.debug("annotation is of type={}, do token validation and claim checking.", ProtectedWithClaims.class.getSimpleName());
71+
var jwtToken = getJwtToken(a.issuer(), tokenValidationContextHolder);
72+
if (jwtToken.isEmpty()) {
73+
throw new JwtTokenMissingException();
74+
}
75+
if (!handleProtectedWithClaimsAnnotation(a, jwtToken.get())) {
76+
throw new JwtTokenInvalidClaimException(a);
77+
}
78+
return true;
10279
}
10380

104-
protected Annotation getAnnotation(Method method, List<Class<? extends Annotation>> types) {
105-
Annotation annotation = findAnnotation(method.getAnnotations(), types);
106-
if (annotation != null) {
107-
log.debug("method " + method + " marked @{}", annotation.annotationType());
108-
return annotation;
81+
private boolean handleRequiredIssuers(RequiredIssuers a) {
82+
boolean hasToken = false;
83+
for (var sub : a.value()) {
84+
var jwtToken = getJwtToken(sub.issuer(), tokenValidationContextHolder);
85+
if (jwtToken.isEmpty()) {
86+
continue;
87+
}
88+
if (handleProtectedWithClaimsAnnotation(sub, jwtToken.get())) {
89+
return true;
90+
}
91+
hasToken = true;
10992
}
110-
Class<?> declaringClass = method.getDeclaringClass();
111-
annotation = findAnnotation(declaringClass.getAnnotations(), types);
112-
if (annotation != null) {
113-
log.debug("method {} marked @{} through annotation on class", method, annotation.annotationType());
114-
return annotation;
93+
if (!hasToken) {
94+
throw new JwtTokenMissingException(a);
11595
}
116-
return null;
96+
throw new JwtTokenInvalidClaimException(a);
97+
}
98+
99+
protected Annotation getAnnotation(Method method, List<Class<? extends Annotation>> types) {
100+
return Optional.ofNullable(findAnnotation(types, method.getAnnotations()))
101+
.orElseGet(() -> findAnnotation(types, method.getDeclaringClass().getAnnotations()));
117102
}
118103

119-
private static Annotation findAnnotation(Annotation[] annotations, List<Class<? extends Annotation>> types) {
120-
return annotations != null ? Arrays.stream(annotations)
104+
private static Annotation findAnnotation(List<Class<? extends Annotation>> types, Annotation... annotations) {
105+
return Arrays.stream(annotations)
121106
.filter(a -> types.contains(a.annotationType()))
122107
.findFirst()
123-
.orElse(null) : null;
108+
.orElse(null);
124109
}
125110

126-
protected boolean handleProtectedWithClaimsAnnotation(ProtectedWithClaims annotation, JwtToken jwtToken) {
127-
return handleProtectedWithClaims(annotation.issuer(), annotation.claimMap(), annotation.combineWithOr(), jwtToken);
111+
protected boolean handleProtectedWithClaimsAnnotation(ProtectedWithClaims a, JwtToken jwtToken) {
112+
return handleProtectedWithClaims(a.issuer(), a.claimMap(), a.combineWithOr(), jwtToken);
128113
}
129114

130115
protected boolean handleProtectedWithClaims(String issuer, String[] requiredClaims, boolean combineWithOr, JwtToken jwtToken) {
131116
if (Objects.nonNull(issuer) && issuer.length() > 0) {
132-
if (!containsRequiredClaims(jwtToken, combineWithOr, requiredClaims)) {
133-
return false;
134-
}
117+
return containsRequiredClaims(jwtToken, combineWithOr, requiredClaims);
135118
}
136119
return true;
137120
}
138121

139-
protected boolean containsRequiredClaims(JwtToken jwtBearerToken, boolean combineWithOr, String... claims) {
140-
log.debug("choose matching logic based on combineWithOr=" + combineWithOr);
141-
return combineWithOr ? containsAnyClaim(jwtBearerToken, claims)
142-
: containsAllClaims(jwtBearerToken, claims);
122+
protected boolean containsRequiredClaims(JwtToken jwtToken, boolean combineWithOr, String... claims) {
123+
LOG.debug("choose matching logic based on combineWithOr=" + combineWithOr);
124+
return combineWithOr ? containsAnyClaim(jwtToken, claims)
125+
: containsAllClaims(jwtToken, claims);
143126
}
144127

145128
private boolean containsAllClaims(JwtToken jwtToken, String... claims) {
@@ -159,8 +142,7 @@ private boolean containsAnyClaim(JwtToken jwtToken, String... claims) {
159142
.filter(pair -> pair.length == 2)
160143
.anyMatch(pair -> jwtToken.containsClaim(pair[0].trim(), pair[1].trim()));
161144
}
162-
log.debug("no claims listed, so claim checking is ok.");
145+
LOG.debug("no claims listed, so claim checking is ok.");
163146
return true;
164147
}
165-
166148
}

0 commit comments

Comments
 (0)