1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.security.jwt;
23
24 import com.nimbusds.jose.JOSEException;
25 import com.nimbusds.jose.JWSAlgorithm;
26 import com.nimbusds.jose.JWSVerifier;
27 import com.nimbusds.jose.crypto.ECDSAVerifier;
28 import com.nimbusds.jose.crypto.Ed25519Verifier;
29 import com.nimbusds.jose.crypto.MACVerifier;
30 import com.nimbusds.jose.crypto.RSASSAVerifier;
31 import com.nimbusds.jose.jwk.JWK;
32 import com.nimbusds.jwt.SignedJWT;
33
34 import org.apache.commons.lang3.StringUtils;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37 import org.springframework.context.expression.MapAccessor;
38 import org.springframework.expression.Expression;
39 import org.springframework.expression.ExpressionParser;
40 import org.springframework.expression.spel.standard.SpelExpressionParser;
41 import org.springframework.expression.spel.support.StandardEvaluationContext;
42 import org.springframework.util.Assert;
43
44 import java.text.ParseException;
45 import java.util.ArrayList;
46 import java.util.Date;
47 import java.util.List;
48
49
50
51
52 public final class JWTVerifier {
53
54
55 private static final Logger logger = LoggerFactory.getLogger(JWTVerifier.class);
56
57 private JWTVerifier() { }
58
59
60
61
62
63
64
65
66
67
68
69 public static SignedJWT verify(String token, JWKSetProvider retriever, List<String> claimConstraints)
70 throws JOSEException, java.text.ParseException {
71 Assert.notNull(token, "A token must be set");
72 Assert.notNull(retriever, "A JWKS retriever must be set");
73
74 SignedJWT jwt = SignedJWT.parse(token);
75 JWSAlgorithm alg = jwt.getHeader().getAlgorithm();
76
77 List<JWK> jwkSet = retriever.getAll();
78
79 List<JWSVerifier> verifiers = new ArrayList<>();
80 if (alg.equals(JWSAlgorithm.RS256) || alg.equals(JWSAlgorithm.RS384) || alg.equals(JWSAlgorithm.RS512)) {
81 for (JWK jwk : jwkSet) {
82 verifiers.add(new RSASSAVerifier(jwk.toRSAKey()));
83 }
84 return verify(jwt, claimConstraints, verifiers.toArray(new JWSVerifier[0]));
85 } else if (alg.equals(JWSAlgorithm.ES256) || alg.equals(JWSAlgorithm.ES256K) || alg.equals(JWSAlgorithm.ES384)
86 || alg.equals(JWSAlgorithm.ES512)) {
87 for (JWK jwk : jwkSet) {
88 verifiers.add(new ECDSAVerifier(jwk.toECKey()));
89 }
90 return verify(jwt, claimConstraints, verifiers.toArray(new JWSVerifier[0]));
91 } else if (alg.equals(JWSAlgorithm.EdDSA) || alg.equals(JWSAlgorithm.Ed25519)) {
92 for (JWK jwk : jwkSet) {
93 verifiers.add(new Ed25519Verifier(jwk.toPublicJWK().toOctetKeyPair()));
94 }
95 return verify(jwt, claimConstraints, verifiers.toArray(new JWSVerifier[0]));
96 } else {
97 throw new IllegalArgumentException("Unsupported algorithm '" + alg + "'");
98 }
99 }
100
101
102
103
104
105
106
107
108
109
110 public static SignedJWT verify(String token, String secret, List<String> claimConstraints)
111 throws JOSEException, java.text.ParseException {
112 Assert.notNull(token, "A token must be set");
113 Assert.isTrue(StringUtils.isNotBlank(secret), "A secret must be set");
114
115 SignedJWT jwt = SignedJWT.parse(token);
116 JWSAlgorithm alg = jwt.getHeader().getAlgorithm();
117
118 if (alg.equals(JWSAlgorithm.HS256) || alg.equals(JWSAlgorithm.HS384) || alg.equals(JWSAlgorithm.HS512)) {
119 return verify(jwt, claimConstraints, new MACVerifier(secret));
120 } else {
121 throw new IllegalArgumentException("Unsupported algorithm '" + alg + "'");
122 }
123 }
124
125 public static SignedJWT verify(SignedJWT jwt, List<String> claimConstraints, JWSVerifier... verifiers)
126 throws JOSEException {
127 Assert.notNull(jwt, "A decoded JWT must be set");
128 Assert.notNull(claimConstraints, "Claim constraints must be set");
129 Assert.notNull(verifiers, "Verifiers must be set");
130
131 boolean verified = false;
132 Exception lastException = new JOSEException("JWT could not be verified");
133 for (JWSVerifier verifier : verifiers) {
134 try {
135
136 if (!jwt.verify(verifier)) {
137 throw new JOSEException("JWT could not be verified");
138 }
139
140
141 Date expirationTime = jwt.getJWTClaimsSet().getExpirationTime();
142 if (expirationTime != null && !new Date().before(expirationTime)) {
143 throw new JOSEException("JWT is expired");
144 }
145
146
147 ExpressionParser parser = new SpelExpressionParser();
148 StandardEvaluationContext ctx = new StandardEvaluationContext();
149 ctx.addPropertyAccessor(new MapAccessor());
150 for (String constraint : claimConstraints) {
151 Expression exp = parser.parseExpression(constraint);
152 if (!exp.getValue(ctx, jwt.getJWTClaimsSet().getClaims(), Boolean.class)) {
153 throw new JOSEException("The claims did not fulfill constraint '" + constraint + "'");
154 }
155 }
156
157
158 verified = true;
159 break;
160 } catch (JOSEException | ParseException e) {
161
162 lastException = e;
163 }
164 }
165
166
167 if (!verified) {
168 throw new JOSEException(lastException.getMessage());
169 }
170
171 return jwt;
172 }
173 }