You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by db...@apache.org on 2022/09/10 02:35:54 UTC

[tomee] branch main updated: TOMEE-3950 Support for JWT token cookies

This is an automated email from the ASF dual-hosted git repository.

dblevins pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomee.git


The following commit(s) were added to refs/heads/main by this push:
     new f53be6f4fb TOMEE-3950 Support for JWT token cookies
     new e07945cb7f Merge branch 'main' of github.com:apache/tomee into main
f53be6f4fb is described below

commit f53be6f4fb6d0b26ad84a50ab4c00d609fcf72a6
Author: David Blevins <db...@tomitribe.com>
AuthorDate: Fri Sep 9 19:32:14 2022 -0700

    TOMEE-3950 Support for JWT token cookies
---
 .../apache/tomee/microprofile/jwt/MPJWTFilter.java | 63 ++++++++++++++++++----
 .../jwt/config/JWTAuthConfiguration.java           | 11 +++-
 .../jwt/config/JWTAuthConfigurationProperties.java | 11 +++-
 3 files changed, 72 insertions(+), 13 deletions(-)

diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
index adee42c135..23b086f3e0 100644
--- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
@@ -25,6 +25,7 @@ import jakarta.servlet.FilterConfig;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.ServletRequest;
 import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.Cookie;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletRequestWrapper;
 import jakarta.servlet.http.HttpServletResponse;
@@ -71,6 +72,7 @@ import java.util.concurrent.Callable;
 import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 // async is supported because we only need to do work on the way in
 //@WebFilter(asyncSupported = true, urlPatterns = "/*")
@@ -243,6 +245,25 @@ public class MPJWTFilter implements Filter {
         }
     }
 
+    private static class MissingTokenCookieException extends MPJWTException {
+
+        private final String cookieName;
+
+        public MissingTokenCookieException(final String authorizationHeader) {
+            this.cookieName = authorizationHeader;
+        }
+
+        @Override
+        public int getStatus() {
+            return HttpServletResponse.SC_UNAUTHORIZED;
+        }
+
+        @Override
+        public String getMessage() {
+            return String.format("Cookie of name '%s' holding a JWT was not found.", cookieName);
+        }
+    }
+
     private static class InvalidTokenException extends MPJWTException {
 
         private final String token;
@@ -293,17 +314,39 @@ public class MPJWTFilter implements Filter {
             }
 
             final String headerName = jwtAuthConfiguration.getHeaderName();
-            final String authorizationHeader = httpServletRequest.getHeader(headerName);
-            if (authorizationHeader == null || authorizationHeader.isEmpty()) {
-                throw new MissingAuthorizationHeaderException();
-            }
+            final String token;
+
+            if ("cookie".equals(headerName)) {
+                final String cookieName = jwtAuthConfiguration.getCookieName();
+
+                if (httpServletRequest.getCookies() == null) {
+                    throw new MissingTokenCookieException(cookieName);
+                }
+
+                final Cookie tokenCookie = Stream.of(httpServletRequest.getCookies())
+                        .filter(cookie -> cookieName.equals(cookie.getName().toLowerCase()))
+                        .findFirst()
+                        .orElse(null);
+
+                if (tokenCookie == null) {
+                    throw new MissingTokenCookieException(cookieName);
+                }
 
-            final String headerScheme = (jwtAuthConfiguration.getHeaderScheme() + " ").toLowerCase(Locale.ENGLISH);
-            if (headerScheme.trim().length() > 0 && !authorizationHeader.toLowerCase(Locale.ENGLISH).startsWith(headerScheme)) {
-                throw new BadAuthorizationPrefixException(authorizationHeader);
+                token = tokenCookie.getValue();
+            } else {
+                final String authorizationHeader = httpServletRequest.getHeader(headerName);
+                if (authorizationHeader == null || authorizationHeader.isEmpty()) {
+                    throw new MissingAuthorizationHeaderException();
+                }
+
+                final String headerScheme = (jwtAuthConfiguration.getHeaderScheme() + " ").toLowerCase(Locale.ENGLISH);
+                if (headerScheme.trim().length() > 0 && !authorizationHeader.toLowerCase(Locale.ENGLISH).startsWith(headerScheme)) {
+                    throw new BadAuthorizationPrefixException(authorizationHeader);
+                }
+
+                token = authorizationHeader.substring(headerScheme.length());
             }
 
-            final String token = authorizationHeader.substring(headerScheme.length());
             try {
                 jsonWebToken = parse(token, jwtAuthConfiguration);
 
@@ -369,13 +412,13 @@ public class MPJWTFilter implements Filter {
                     builder.setVerificationKeyResolver(new JwksVerificationKeyResolver(asJwks(authContextInfo.getPublicKeys())));
                 }
 
-                if (authContextInfo.getDecryptKeys().size() == 1){
+                if (authContextInfo.getDecryptKeys().size() == 1) {
                     final Key decryptionKey = authContextInfo.getDecryptKeys().values().iterator().next();
                     builder.setDecryptionKey(decryptionKey);
                 } else if (authContextInfo.getDecryptKeys().size() > 1) {
                     builder.setDecryptionKeyResolver(new JwksDecryptionKeyResolver(asJwks(authContextInfo.getDecryptKeys())));
                 }
-                
+
 
                 final JwtConsumer jwtConsumer = builder.build();
                 final JwtContext jwtContext = jwtConsumer.process(token);
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfiguration.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfiguration.java
index cd66709355..b41a808a91 100644
--- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfiguration.java
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfiguration.java
@@ -40,6 +40,7 @@ public class JWTAuthConfiguration {
     private String headerName = "Authorization";
     private String headerScheme = "Bearer";
     private boolean allowNoExpiryClaim = false;
+    private String cookieName = "Bearer";
 
     private JWTAuthConfiguration(final Key publicKey, final String issuer, final boolean allowNoExpiryClaim, final String[] audiences) {
         this.publicKeys = Collections.singletonMap(DEFAULT_KEY, publicKey);
@@ -48,7 +49,7 @@ public class JWTAuthConfiguration {
         this.audiences = audiences;
     }
 
-    private JWTAuthConfiguration(final Map<String, Key> publicKeys, final String issuer, final boolean allowNoExpiryClaim, final String[] audiences, final Map<String, Key> decryptKeys) {
+    public JWTAuthConfiguration(final Map<String, Key> publicKeys, final String issuer, final boolean allowNoExpiryClaim, final String[] audiences, final Map<String, Key> decryptKeys, final String header, final String cookie) {
         if (publicKeys == null) {
             this.publicKeys = Collections.EMPTY_MAP;
         } else if (publicKeys.size() == 1) {
@@ -67,6 +68,8 @@ public class JWTAuthConfiguration {
         this.issuer = issuer;
         this.allowNoExpiryClaim = allowNoExpiryClaim;
         this.audiences = audiences;
+        this.headerName = header;
+        this.cookieName = cookie;
     }
 
     public static JWTAuthConfiguration authConfiguration(final Key publicKey, final String issuer, final boolean allowNoExpiryClaim) {
@@ -82,7 +85,11 @@ public class JWTAuthConfiguration {
     }
 
     public static JWTAuthConfiguration authConfiguration(final Map<String, Key> publicKeys, final String issuer, final boolean allowNoExpiryClaim, final String[] audiences, final Map<String, Key> decryptKeys) {
-        return new JWTAuthConfiguration(publicKeys, issuer, allowNoExpiryClaim, audiences, decryptKeys);
+        return new JWTAuthConfiguration(publicKeys, issuer, allowNoExpiryClaim, audiences, decryptKeys, null, null);
+    }
+
+    public String getCookieName() {
+        return cookieName;
     }
 
     public String[] getAudiences() {
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationProperties.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationProperties.java
index 76ad9c9003..cf204b7aaf 100644
--- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationProperties.java
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthConfigurationProperties.java
@@ -34,6 +34,8 @@ import java.util.Optional;
 import static org.eclipse.microprofile.jwt.config.Names.AUDIENCES;
 import static org.eclipse.microprofile.jwt.config.Names.DECRYPTOR_KEY_LOCATION;
 import static org.eclipse.microprofile.jwt.config.Names.ISSUER;
+import static org.eclipse.microprofile.jwt.config.Names.TOKEN_COOKIE;
+import static org.eclipse.microprofile.jwt.config.Names.TOKEN_HEADER;
 import static org.eclipse.microprofile.jwt.config.Names.VERIFIER_PUBLIC_KEY;
 import static org.eclipse.microprofile.jwt.config.Names.VERIFIER_PUBLIC_KEY_LOCATION;
 
@@ -101,7 +103,14 @@ public class JWTAuthConfigurationProperties {
 
         final Boolean allowNoExp = config.getOptionalValue("mp.jwt.tomee.allow.no-exp", Boolean.class).orElse(false);
 
-        return JWTAuthConfiguration.authConfiguration(publicKeys, getIssuer().orElse(null), allowNoExp, audiences.toArray(new String[0]), decryptkeys);
+        return new JWTAuthConfiguration(
+                publicKeys,
+                getIssuer().orElse(null),
+                allowNoExp,
+                audiences.toArray(new String[0]),
+                decryptkeys,
+                config.getOptionalValue(TOKEN_HEADER, String.class).map(String::toLowerCase).orElse("authorization"),
+                config.getOptionalValue(TOKEN_COOKIE, String.class).map(String::toLowerCase).orElse("bearer"));
     }
 
 }