View Javadoc
1   package media.pepperpot.tca.authenticator;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.net.MalformedURLException;
6   import java.net.URL;
7   import java.nio.file.Files;
8   import java.nio.file.Paths;
9   import java.security.Principal;
10  import java.text.ParseException;
11  import java.util.ArrayList;
12  import java.util.Arrays;
13  import java.util.Enumeration;
14  import java.util.List;
15  import java.util.Properties;
16  import java.util.Set;
17  import java.util.stream.Collectors;
18  
19  import javax.servlet.http.HttpServletResponse;
20  
21  import org.apache.catalina.authenticator.AuthenticatorBase;
22  import org.apache.catalina.connector.Request;
23  import org.apache.juli.logging.Log;
24  import org.apache.juli.logging.LogFactory;
25  
26  import com.nimbusds.jose.EncryptionMethod;
27  import com.nimbusds.jose.JOSEException;
28  import com.nimbusds.jose.JWEAlgorithm;
29  import com.nimbusds.jose.JWSAlgorithm;
30  import com.nimbusds.jose.jwk.JWKSet;
31  import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
32  import com.nimbusds.jose.jwk.source.ImmutableSecret;
33  import com.nimbusds.jose.jwk.source.JWKSource;
34  import com.nimbusds.jose.jwk.source.JWKSourceBuilder;
35  import com.nimbusds.jose.proc.BadJOSEException;
36  import com.nimbusds.jose.proc.JWEDecryptionKeySelector;
37  import com.nimbusds.jose.proc.JWEKeySelector;
38  import com.nimbusds.jose.proc.JWSKeySelector;
39  import com.nimbusds.jose.proc.JWSVerificationKeySelector;
40  import com.nimbusds.jose.proc.SecurityContext;
41  import com.nimbusds.jwt.EncryptedJWT;
42  import com.nimbusds.jwt.JWT;
43  import com.nimbusds.jwt.JWTClaimsSet;
44  import com.nimbusds.jwt.JWTParser;
45  import com.nimbusds.jwt.PlainJWT;
46  import com.nimbusds.jwt.SignedJWT;
47  import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
48  import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
49  import com.nimbusds.jwt.proc.DefaultJWTProcessor;
50  
51  /**
52   * If a JWT Bearer token is included with the request, validate the token and return the token subject as the principal.
53   *
54   * @author minfrin
55   */
56  public class JwtAuthenticator extends AuthenticatorBase {
57  
58      private final static Log log = LogFactory.getLog(JwtAuthenticator.class);
59  
60      /** Constant <code>BEARER="BEARER"</code> */
61      public static final String BEARER = "BEARER";
62  
63      /** Constant <code>ALLOW_PLAIN="media.pepperpot.jwt.allowPlain"</code> */
64      public static final String ALLOW_PLAIN = "media.pepperpot.jwt.allowPlain";
65      /** Constant <code>DEFAULT_ALLOW_PLAIN="Boolean.FALSE.toString()"</code> */
66      public static final String DEFAULT_ALLOW_PLAIN = Boolean.FALSE.toString();
67      /** Constant <code>JWS_SECRET_FILE="media.pepperpot.jws.SecretFile"</code> */
68      public static final String JWS_SECRET_FILE = "media.pepperpot.jws.SecretFile";
69      /** Constant <code>JWS_JWK_SET_FILE="media.pepperpot.jws.JwkSetFile"</code> */
70      public static final String JWS_JWK_SET_FILE = "media.pepperpot.jws.JwkSetFile";
71      /** Constant <code>JWS_REMOTE_JWK_SET_URL="media.pepperpot.jws.RemoteJwkSetUrl"</code> */
72      public static final String JWS_REMOTE_JWK_SET_URL = "media.pepperpot.jws.RemoteJwkSetUrl";
73      /** Constant <code>JWS_ALGORITHM="media.pepperpot.jwt.JWSAlgorithm"</code> */
74      public static final String JWS_ALGORITHM = "media.pepperpot.jwt.JWSAlgorithm";
75      /** Constant <code>JWE_SECRET_FILE="media.pepperpot.jwe.SecretFile"</code> */
76      public static final String JWE_SECRET_FILE = "media.pepperpot.jwe.SecretFile";
77      /** Constant <code>JWE_JWK_SET_FILE="media.pepperpot.jwe.JwkSetFile"</code> */
78      public static final String JWE_JWK_SET_FILE = "media.pepperpot.jwe.JwkSetFile";
79      /** Constant <code>JWE_REMOTE_JWK_SET_URL="media.pepperpot.jwe.RemoteJwkSetUrl"</code> */
80      public static final String JWE_REMOTE_JWK_SET_URL = "media.pepperpot.jwe.RemoteJwkSetUrl";
81      /** Constant <code>JWE_ALGORITHM="media.pepperpot.jwt.JWEAlgorithm"</code> */
82      public static final String JWE_ALGORITHM = "media.pepperpot.jwt.JWEAlgorithm";
83      /** Constant <code>JWE_ENCRYPTION_METHOD="media.pepperpot.jwt.JWEEncryptionMethod"</code> */
84      public static final String JWE_ENCRYPTION_METHOD = "media.pepperpot.jwt.JWEEncryptionMethod";
85      /** Constant <code>JWT_MAX_CLOCK_SKEW="media.pepperpot.jwt.MaxClockSkew"</code> */
86      public static final String JWT_MAX_CLOCK_SKEW = "media.pepperpot.jwt.MaxClockSkew";
87      /** Constant <code>JWT_MAX_CLOCK_SKEW_DEFAULT="60"</code> */
88      public static final String JWT_MAX_CLOCK_SKEW_DEFAULT = "60";
89      /** Constant <code>JWT_ACCEPTED_AUDIENCE="media.pepperpot.jwt.AcceptedAudience"</code> */
90      public static final String JWT_ACCEPTED_AUDIENCE = "media.pepperpot.jwt.AcceptedAudience";
91      /** Constant <code>JWT_REQUIRED_CLAIMS="media.pepperpot.jwt.RequiredClaims"</code> */
92      public static final String JWT_REQUIRED_CLAIMS = "media.pepperpot.jwt.RequiredClaims";
93      /** Constant <code>JWT_PROHIBITED_CLAIMS="media.pepperpot.jwt.ProhibitedClaims"</code> */
94      public static final String JWT_PROHIBITED_CLAIMS = "media.pepperpot.jwt.ProhibitedClaims";
95  
96      /** Properties passed to the authenticator. */
97      protected Properties properties = null;
98  
99      /**
100      * <p>
101      * Constructor for JwtAuthenticator.
102      * </p>
103      */
104     public JwtAuthenticator() {
105         super();
106         properties = System.getProperties();
107     }
108 
109     /**
110      * <p>
111      * Constructor for JwtAuthenticator.
112      * </p>
113      *
114      * @param properties
115      *            a {@link java.util.Properties} object.
116      */
117     public JwtAuthenticator(Properties properties) {
118         super();
119         this.properties = properties;
120     }
121 
122     /**
123      * Parse the Authorization header, and extract the sub from the first valid JWT found.
124      *
125      * @param request
126      *            a {@link org.apache.catalina.connector.Request} object.
127      * @param properties
128      *            a {@link java.util.Properties} object.
129      * @param claimName
130      *            a {@link java.lang.String} object.
131      *
132      * @return a {@link java.lang.String} object.
133      */
134     protected static String parseAuthorization(Properties properties, Request request, String claimName) {
135 
136         Enumeration<String> authorizations = request.getHeaders("Authorization");
137         if (authorizations == null || !authorizations.hasMoreElements()) {
138             if (log.isFatalEnabled()) {
139                 log.fatal("No Authorization header found in request: request=" + request + ", remoteHost="
140                         + request.getRemoteHost() + ", remoteIP=" + request.getRemoteAddr());
141             }
142             return null;
143         }
144 
145         List<String> statuses = new ArrayList<String>();
146 
147         while (authorizations.hasMoreElements()) {
148             String authorization = authorizations.nextElement();
149 
150             String[] split = authorization.split(" ", 2);
151             if (split == null || split.length != 2 || !"Bearer".equals(split[0])) {
152                 statuses.add("Authorization header found, but not a Bearer token");
153                 continue;
154             }
155             String jwt = split[1];
156 
157             if (jwt == null) {
158                 statuses.add("No JWT token found in Bearer Authentication header");
159                 continue;
160             }
161 
162             try {
163 
164                 String jwsSecretFile = properties.getProperty(JWS_SECRET_FILE);
165                 String jwsJwkSetFile = properties.getProperty(JWS_JWK_SET_FILE);
166                 String jwsRemoteJwkSetUrl = properties.getProperty(JWS_REMOTE_JWK_SET_URL);
167 
168                 String jweSecretFile = properties.getProperty(JWE_SECRET_FILE);
169                 String jweJwkSetFile = properties.getProperty(JWE_JWK_SET_FILE);
170                 String jweRemoteJwkSetUrl = properties.getProperty(JWE_REMOTE_JWK_SET_URL);
171 
172                 JWT parsed = JWTParser.parse(jwt);
173 
174                 JWTClaimsSet claims = null;
175 
176                 if (parsed instanceof PlainJWT) {
177 
178                     String allow = properties.getProperty(ALLOW_PLAIN, DEFAULT_ALLOW_PLAIN);
179                     if (!Boolean.TRUE.toString().equals(allow)) {
180                         statuses.add(
181                                 "Plain JWT tokens are not allowed by parameter -D'\" + ALLOW_PLAIN + \"', ignoring");
182                         continue;
183                     }
184 
185                     claims = parsed.getJWTClaimsSet();
186 
187                 } else if (parsed instanceof SignedJWT) {
188 
189                     ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<SecurityContext>();
190 
191                     JWKSource<SecurityContext> keySource = null;
192 
193                     if (!isBlank(jwsSecretFile)) {
194                         byte[] secret;
195                         try {
196                             secret = Files.readAllBytes(Paths.get(jwsSecretFile));
197                         } catch (IOException e) {
198                             statuses.add("JWT token is signed, but secret '" + jwsSecretFile + "' could not be read: "
199                                     + e.toString());
200                             continue;
201                         }
202                         keySource = new ImmutableSecret<SecurityContext>(secret);
203                     } else if (!isBlank(jwsJwkSetFile)) {
204                         try {
205                             keySource = new ImmutableJWKSet<SecurityContext>(JWKSet.load(new File(jwsJwkSetFile)));
206                         } catch (IOException e) {
207                             statuses.add("JWT token is signed, but remote JWK set '" + jwsJwkSetFile
208                                     + "' could not be read: " + e.toString());
209                             continue;
210                         }
211                     } else if (!isBlank(jwsRemoteJwkSetUrl)) {
212                         try {
213                             keySource = JWKSourceBuilder.<SecurityContext> create(new URL(jwsRemoteJwkSetUrl)).build();
214                         } catch (MalformedURLException e) {
215                             statuses.add("JWT token is signed, but remote JWK set '" + jwsRemoteJwkSetUrl
216                                     + "' was a malformed URL: " + e.toString());
217                             continue;
218                         }
219                     } else {
220                         statuses.add(
221                                 "JWT token is signed, but we have no secret or remote jwk set to verify it against");
222                         continue;
223                     }
224 
225                     String jwsAlgorithmString = properties.getProperty(JWS_ALGORITHM);
226                     JWSAlgorithm expectedJWSAlg = JWSAlgorithm.parse(jwsAlgorithmString);
227                     if (expectedJWSAlg == null) {
228                         statuses.add(
229                                 "JWT token is signed, but JWS algorithm was not recognised: " + jwsAlgorithmString);
230                         continue;
231                     }
232 
233                     JWSKeySelector<SecurityContext> keySelector = new JWSVerificationKeySelector<SecurityContext>(
234                             expectedJWSAlg, keySource);
235                     jwtProcessor.setJWSKeySelector(keySelector);
236 
237                     String jwtMaxClockSkewString = properties.getProperty(JWT_MAX_CLOCK_SKEW,
238                             JWT_MAX_CLOCK_SKEW_DEFAULT);
239                     String jwtAcceptedAudienceString = properties.getProperty(JWT_ACCEPTED_AUDIENCE);
240                     String jwtRequiredClaimsString = properties.getProperty(JWT_REQUIRED_CLAIMS);
241                     String jwtProhibitedClaimsString = properties.getProperty(JWT_PROHIBITED_CLAIMS);
242                     try {
243                         Set<String> jwtAcceptedAudience = jwtAcceptedAudienceString == null ? null
244                                 : Arrays.stream(jwtAcceptedAudienceString.split(",")).map(String::trim)
245                                         .collect(Collectors.toSet());
246                         Set<String> jwtRequiredClaims = jwtRequiredClaimsString == null ? null
247                                 : Arrays.stream(jwtRequiredClaimsString.split(",")).map(String::trim)
248                                         .collect(Collectors.toSet());
249                         Set<String> jwtProhibitedClaims = jwtProhibitedClaimsString == null ? null
250                                 : Arrays.stream(jwtProhibitedClaimsString.split(",")).map(String::trim)
251                                         .collect(Collectors.toSet());
252                         DefaultJWTClaimsVerifier<SecurityContext> claimsVerifier = new DefaultJWTClaimsVerifier<SecurityContext>(
253                                 jwtAcceptedAudience, null, jwtRequiredClaims, jwtProhibitedClaims);
254                         claimsVerifier.setMaxClockSkew(Integer.parseInt(jwtMaxClockSkewString));
255                         jwtProcessor.setJWTClaimsSetVerifier(claimsVerifier);
256                     } catch (NumberFormatException e) {
257                         statuses.add("JWT token is signed, but " + JWT_MAX_CLOCK_SKEW + " could not be parsed: "
258                                 + jwsAlgorithmString + " :" + e.toString());
259                         continue;
260                     }
261 
262                     SecurityContext ctx = null;
263 
264                     try {
265                         claims = jwtProcessor.process(parsed, ctx);
266                     } catch (BadJOSEException e) {
267                         statuses.add("JWT token was signed, but was formatted badly: " + e.toString());
268                         continue;
269                     } catch (JOSEException e) {
270                         statuses.add("JWT token was signed, but was could not be verified: " + e.toString());
271                         continue;
272                     }
273 
274                 } else if (parsed instanceof EncryptedJWT) {
275 
276                     ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<SecurityContext>();
277 
278                     JWKSource<SecurityContext> keySource = null;
279 
280                     /* use the jwsSecretFile as encryption secret, unless overridden below */
281                     if (!isBlank(jwsSecretFile)) {
282                         byte[] secret;
283                         try {
284                             secret = Files.readAllBytes(Paths.get(jwsSecretFile));
285                         } catch (IOException e) {
286                             statuses.add("JWT token is encrypted, but signature secret '" + jwsSecretFile
287                                     + "' could not be read: " + e.toString());
288                             continue;
289                         }
290                         keySource = new ImmutableSecret<SecurityContext>(secret);
291                     } else if (!isBlank(jwsJwkSetFile)) {
292                         try {
293                             keySource = new ImmutableJWKSet<SecurityContext>(JWKSet.load(new File(jwsJwkSetFile)));
294                         } catch (IOException e) {
295                             statuses.add("JWT token is encrypted, but signature remote JWK set '" + jwsJwkSetFile
296                                     + "' could not be read: " + e.toString());
297                             continue;
298                         }
299                     } else if (!isBlank(jwsRemoteJwkSetUrl)) {
300                         try {
301                             keySource = JWKSourceBuilder.<SecurityContext> create(new URL(jwsRemoteJwkSetUrl)).build();
302                         } catch (MalformedURLException e) {
303                             statuses.add("JWT token is encrypted, but signature remote JWK set '" + jwsRemoteJwkSetUrl
304                                     + "' could not be read: " + e.toString());
305                             continue;
306                         }
307                     }
308 
309                     JWSAlgorithm expectedJWSAlg = null;
310                     String jwsAlgorithmString = properties.getProperty(JWS_ALGORITHM);
311                     if (!isBlank(jwsAlgorithmString)) {
312                         expectedJWSAlg = JWSAlgorithm.parse(jwsAlgorithmString);
313                         if (expectedJWSAlg == null) {
314                             statuses.add("JWT token is encrypted, but signature JWS algorithm was not recognised: "
315                                     + jwsAlgorithmString);
316                             continue;
317                         }
318                     }
319 
320                     if (expectedJWSAlg != null && keySource == null) {
321                         statuses.add("JWT token is encrypted and signature JWS algorithm was set to '"
322                                 + jwsAlgorithmString + "', but none of '" + JWS_SECRET_FILE + "', '" + JWS_JWK_SET_FILE
323                                 + "', or '" + JWS_REMOTE_JWK_SET_URL + "' is set");
324                         continue;
325                     }
326 
327                     if (expectedJWSAlg != null) {
328                         JWSKeySelector<SecurityContext> keySelector = new JWSVerificationKeySelector<SecurityContext>(
329                                 expectedJWSAlg, keySource);
330                         jwtProcessor.setJWSKeySelector(keySelector);
331                     }
332 
333                     if (!isBlank(jweSecretFile)) {
334                         byte[] secret;
335                         try {
336                             secret = Files.readAllBytes(Paths.get(jweSecretFile));
337                         } catch (IOException e) {
338                             statuses.add("JWT token is encrypted, but encryption secret '" + jweSecretFile
339                                     + "' could not be read: " + e.toString());
340                             continue;
341                         }
342                         keySource = new ImmutableSecret<SecurityContext>(secret);
343                     } else if (!isBlank(jweJwkSetFile)) {
344                         try {
345                             keySource = new ImmutableJWKSet<SecurityContext>(JWKSet.load(new File(jweJwkSetFile)));
346                         } catch (IOException e) {
347                             statuses.add("JWT token is encrypted, but encryption remote JWK set '" + jweJwkSetFile
348                                     + "' could not be read: " + e.toString());
349                             continue;
350                         }
351                     } else if (!isBlank(jweRemoteJwkSetUrl)) {
352                         try {
353                             keySource = JWKSourceBuilder.<SecurityContext> create(new URL(jweRemoteJwkSetUrl)).build();
354                         } catch (MalformedURLException e) {
355                             statuses.add("JWT token is encrypted, but encryption remote JWK set '" + jweRemoteJwkSetUrl
356                                     + "' could not be read: " + e.toString());
357                             continue;
358                         }
359                     } else {
360                         statuses.add("JWT token is encrypted, but we have no '" + JWE_SECRET_FILE + "'. '"
361                                 + JWE_JWK_SET_FILE + "' or '" + JWE_REMOTE_JWK_SET_URL + "' to verify it against");
362                         continue;
363                     }
364 
365                     JWEAlgorithm expectedJWEAlg = null;
366                     String jweAlgorithmString = properties.getProperty(JWE_ALGORITHM);
367                     if (!isBlank(jweAlgorithmString)) {
368                         expectedJWEAlg = JWEAlgorithm.parse(jweAlgorithmString);
369                         if (expectedJWEAlg == null) {
370                             statuses.add("JWT token is encrypted, but JWE algorithm was not recognised: "
371                                     + jweAlgorithmString);
372                             continue;
373                         }
374                     } else {
375                         statuses.add(
376                                 "JWT token is encrypted, but JWE algorithm '" + JWE_ALGORITHM + "' was not specified");
377                         continue;
378                     }
379 
380                     EncryptionMethod expectedJWEEnc = null;
381                     String jweEncryptedMethodString = properties.getProperty(JWE_ENCRYPTION_METHOD);
382                     if (!isBlank(jweEncryptedMethodString)) {
383                         expectedJWEEnc = EncryptionMethod.parse(jweEncryptedMethodString);
384                         if (expectedJWEEnc == null) {
385                             statuses.add("JWT token is encrypted, but JWE encryption method was not recognised: "
386                                     + jweEncryptedMethodString);
387                             continue;
388                         }
389                     } else {
390                         statuses.add("JWT token is encrypted, but JWE encryption method '" + JWE_ENCRYPTION_METHOD
391                                 + "' was not specified");
392                         continue;
393                     }
394 
395                     JWEKeySelector<SecurityContext> jweKeySelector = new JWEDecryptionKeySelector<SecurityContext>(
396                             expectedJWEAlg, expectedJWEEnc, keySource);
397                     jwtProcessor.setJWEKeySelector(jweKeySelector);
398 
399                     String jwtMaxClockSkewString = properties.getProperty(JWT_MAX_CLOCK_SKEW,
400                             JWT_MAX_CLOCK_SKEW_DEFAULT);
401                     String jwtAcceptedAudienceString = properties.getProperty(JWT_ACCEPTED_AUDIENCE);
402                     String jwtRequiredClaimsString = properties.getProperty(JWT_REQUIRED_CLAIMS);
403                     String jwtProhibitedClaimsString = properties.getProperty(JWT_PROHIBITED_CLAIMS);
404                     try {
405                         Set<String> jwtAcceptedAudience = jwtAcceptedAudienceString == null ? null
406                                 : Arrays.stream(jwtAcceptedAudienceString.split(",")).map(String::trim)
407                                         .collect(Collectors.toSet());
408                         Set<String> jwtRequiredClaims = jwtRequiredClaimsString == null ? null
409                                 : Arrays.stream(jwtRequiredClaimsString.split(",")).map(String::trim)
410                                         .collect(Collectors.toSet());
411                         Set<String> jwtProhibitedClaims = jwtProhibitedClaimsString == null ? null
412                                 : Arrays.stream(jwtProhibitedClaimsString.split(",")).map(String::trim)
413                                         .collect(Collectors.toSet());
414                         DefaultJWTClaimsVerifier<SecurityContext> claimsVerifier = new DefaultJWTClaimsVerifier<SecurityContext>(
415                                 jwtAcceptedAudience, null, jwtRequiredClaims, jwtProhibitedClaims);
416                         claimsVerifier.setMaxClockSkew(Integer.parseInt(jwtMaxClockSkewString));
417                         jwtProcessor.setJWTClaimsSetVerifier(claimsVerifier);
418                     } catch (NumberFormatException e) {
419                         statuses.add("JWT token is signed, but " + JWT_MAX_CLOCK_SKEW + " could not be parsed: "
420                                 + jwsAlgorithmString + " :" + e.toString());
421                         continue;
422                     }
423 
424                     SecurityContext ctx = null;
425 
426                     try {
427                         claims = jwtProcessor.process(parsed, ctx);
428                     } catch (BadJOSEException e) {
429                         statuses.add("JWT token was signed, but was formatted badly: " + e.toString());
430                         continue;
431                     } catch (JOSEException e) {
432                         statuses.add("JWT token was signed, but was could not be verified: " + e.toString());
433                         continue;
434                     }
435 
436                 } else {
437                     statuses.add("JWT token is neither plain, signed, not encrypted");
438                     continue;
439                 }
440 
441                 if (claims == null) {
442                     statuses.add("JWT token found, but claims were missing");
443                     continue;
444                 }
445 
446                 final String claim = claims.getStringClaim(claimName);
447 
448                 if (claim == null) {
449                     statuses.add("JWT token found, but claim '" + claimName + "' was missing");
450                     continue;
451                 }
452 
453                 if (log.isInfoEnabled()) {
454                     log.info("JWT token accepted with " + claimName + " '" + claim + "': request=" + request
455                             + ", remoteHost=" + request.getRemoteHost() + ", remoteIP=" + request.getRemoteAddr());
456                 }
457 
458                 return claim;
459 
460             } catch (ParseException e) {
461                 statuses.add("JWT token found but could not be parsed: " + e.toString());
462             }
463 
464         }
465 
466         /* log out accumulated statuses */
467         for (String status : statuses) {
468             if (log.isFatalEnabled()) {
469                 log.fatal(status + ": request=" + request + ", remoteHost=" + request.getRemoteHost() + ", remoteIP="
470                         + request.getRemoteAddr());
471             }
472         }
473 
474         return null;
475 
476     }
477 
478     /** {@inheritDoc} */
479     @Override
480     protected String getAuthMethod() {
481         return BEARER;
482     }
483 
484     /** {@inheritDoc} */
485     @Override
486     protected boolean doAuthenticate(Request request, HttpServletResponse response) throws IOException {
487 
488         if (checkForCachedAuthentication(request, response, false)) {
489             return true;
490         }
491 
492         if (log.isDebugEnabled()) {
493             log.debug("...attempting to process JWT token in request: request=" + request + ", remoteHost="
494                     + request.getRemoteHost() + ", remoteIP=" + request.getRemoteAddr());
495         }
496 
497         final String sub = parseAuthorization(properties, request, "sub");
498 
499         if (sub != null) {
500 
501             Principal principal = context.getRealm().authenticate(sub);
502             if (principal != null) {
503                 register(request, response, principal, BEARER, sub, null);
504 
505                 return true;
506             }
507 
508         }
509 
510         return false;
511 
512     }
513 
514     private static boolean isBlank(String str) {
515         int strLen;
516         if (str == null || (strLen = str.length()) == 0) {
517             return true;
518         }
519         for (int i = 0; i < strLen; i++) {
520             if (Character.isWhitespace(str.charAt(i)) == false) {
521                 return false;
522             }
523         }
524         return true;
525     }
526 
527 }