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 <dblev...@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")); } }