You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2022/08/25 18:05:31 UTC
[knox] branch master updated: KNOX-2794 - Added cookie auth support into JWT federation provider (#623)
This is an automated email from the ASF dual-hosted git repository.
smolnar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new eaa77e33e KNOX-2794 - Added cookie auth support into JWT federation provider (#623)
eaa77e33e is described below
commit eaa77e33ef0c8c883cc220e18ded06ef6789e7d9
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Thu Aug 25 20:05:06 2022 +0200
KNOX-2794 - Added cookie auth support into JWT federation provider (#623)
---
.../hadoopauth/filter/HadoopAuthFilterTest.java | 2 +
.../provider/federation/jwt/JWTMessages.java | 3 +
.../federation/jwt/filter/JWTFederationFilter.java | 75 ++++++++++++++++++++++
.../jwt/filter/SSOCookieFederationFilter.java | 2 +-
.../provider/federation/AbstractJWTFilterTest.java | 8 +++
.../federation/JWTFederationFilterTest.java | 73 ++++++++++++++++++++-
6 files changed, 159 insertions(+), 4 deletions(-)
diff --git a/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java b/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java
index 25a504f3a..e466ea54e 100644
--- a/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java
+++ b/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java
@@ -566,6 +566,8 @@ public class HadoopAuthFilterTest {
expect(filterConfig.getInitParameter(AbstractJWTFilter.JWT_EXPECTED_SIGALG)).andReturn(null).anyTimes();
expect(filterConfig.getInitParameter(JWTFederationFilter.ALLOWED_JWS_TYPES)).andReturn(null).anyTimes();
expect(filterConfig.getInitParameter(SignatureVerificationCache.TOKENS_VERIFIED_CACHE_MAX)).andReturn(null).anyTimes();
+ expect(filterConfig.getInitParameter(JWTFederationFilter.KNOX_TOKEN_USE_COOKIE)).andReturn(null).anyTimes();
+ expect(filterConfig.getInitParameter(JWTFederationFilter.KNOX_TOKEN_COOKIE_NAME)).andReturn(null).anyTimes();
}
final ServletContext servletContext = createMock(ServletContext.class);
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/JWTMessages.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/JWTMessages.java
index 54f4bf721..bdded53f4 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/JWTMessages.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/JWTMessages.java
@@ -46,6 +46,9 @@ public interface JWTMessages {
@Message( level = MessageLevel.WARN, text = "Expected Bearer token is missing." )
void missingBearerToken();
+ @Message( level = MessageLevel.WARN, text = "Expected valid cookie is missing." )
+ void missingValidCookie();
+
@Message( level = MessageLevel.INFO, text = "Unable to verify token: {0}" )
void unableToVerifyToken(@StackTrace( level = MessageLevel.ERROR) Exception e);
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
index 60914d6e0..64ac556d0 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
@@ -24,6 +24,7 @@ import java.io.IOException;
import java.text.ParseException;
import java.util.Base64;
import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Stream;
@@ -34,9 +35,11 @@ import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.provider.federation.jwt.JWTMessages;
@@ -46,6 +49,7 @@ import org.apache.knox.gateway.services.security.token.impl.JWT;
import org.apache.knox.gateway.services.security.token.impl.JWTToken;
import org.apache.knox.gateway.util.AuthFilterUtils;
import org.apache.knox.gateway.util.CertificateUtils;
+import org.apache.knox.gateway.util.CookieUtils;
import com.nimbusds.jose.JOSEObjectType;
@@ -69,6 +73,13 @@ public class JWTFederationFilter extends AbstractJWTFilter {
public static final String BASIC = "Basic";
public static final String TOKEN = "Token";
public static final String PASSCODE = "Passcode";
+
+ //cookie verification support
+ public static final String KNOX_TOKEN_USE_COOKIE = "knox.token.use.cookie";
+ public static final String KNOX_TOKEN_COOKIE_NAME = "knox.token.cookie.name";
+ private boolean useCookie; //defaults to false
+ private String cookieName;
+
private String paramName;
private Set<String> unAuthenticatedPaths = new HashSet<>(20);
@@ -102,6 +113,13 @@ public class JWTFederationFilter extends AbstractJWTFilter {
allowedJwsTypes.add(JOSEObjectType.JWT);
}
+ //cookie auth support
+ final String useCookieParam = filterConfig.getInitParameter(KNOX_TOKEN_USE_COOKIE);
+ useCookie = StringUtils.isBlank(useCookieParam) ? false : Boolean.parseBoolean(useCookieParam);
+
+ final String cookieNameParam = filterConfig.getInitParameter(KNOX_TOKEN_COOKIE_NAME);
+ cookieName = StringUtils.isBlank(cookieNameParam) ? SSOCookieFederationFilter.DEFAULT_SSO_COOKIE_NAME : cookieNameParam;
+
// expected claim
String oidcPrincipalclaim = filterConfig.getInitParameter(TOKEN_PRINCIPAL_CLAIM);
if (oidcPrincipalclaim != null) {
@@ -136,6 +154,22 @@ public class JWTFederationFilter extends AbstractJWTFilter {
continueWithAnonymousSubject(request, response, chain);
return;
}
+
+ if (useCookie) {
+ try {
+ if (authenticateWithCookies((HttpServletRequest) request, (HttpServletResponse) response, chain)) {
+ // if there was a valid cookie authentication was handled, there is no point in
+ // going forward to check the JWT path in the header
+ return;
+ }
+ } catch (NoValidCookiesException e) {
+ log.missingValidCookie();
+ handleValidationError((HttpServletRequest) request, (HttpServletResponse) response, HttpServletResponse.SC_UNAUTHORIZED,
+ "There is no valid cookie found");
+ return;
+ }
+ }
+
final Pair<TokenType, String> wireToken = getWireToken(request);
if (wireToken != null && wireToken.getLeft() != null && wireToken.getRight() != null) {
@@ -229,6 +263,36 @@ public class JWTFederationFilter extends AbstractJWTFilter {
return parsed;
}
+ /*
+ * Attempts to authenticate using session cookies.
+ */
+ private boolean authenticateWithCookies(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+ throws NoValidCookiesException, ServletException, IOException {
+ final List<Cookie> relevantCookies = CookieUtils.getCookiesForName(request, cookieName);
+ for (Cookie ssoCookie : relevantCookies) {
+ try {
+ final JWT token = new JWTToken(ssoCookie.getValue());
+ if (validateToken(request, response, chain, token)) {
+ final Subject subject = createSubjectFromToken(token);
+ continueWithEstablishedSecurityContext(subject, request, response, chain);
+ // we found a valid cookie we don't need to keep checking anymore
+ return true;
+ }
+ } catch (ParseException | UnknownTokenException ignore) {
+ // Ignore the error since cookie was invalid
+ // Fall through to keep checking if there are more cookies
+ }
+ }
+
+ if (!relevantCookies.isEmpty()) {
+ // No valid cookies found but cookie was present so reject this request and do
+ // no further processing
+ throw new NoValidCookiesException();
+ }
+
+ return false;
+ }
+
@Override
protected void handleValidationError(HttpServletRequest request, HttpServletResponse response, int status,
String error) throws IOException {
@@ -268,4 +332,15 @@ public class JWTFederationFilter extends AbstractJWTFilter {
}
}
+ /**
+ * An exception indicating that cookies are present, but none of them contain a
+ * valid JWT.
+ */
+ @SuppressWarnings("serial")
+ private class NoValidCookiesException extends Exception {
+ NoValidCookiesException() {
+ super("None of the presented cookies are valid.");
+ }
+ }
+
}
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
index aa712490b..cf6767b6f 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
@@ -63,7 +63,7 @@ public class SSOCookieFederationFilter extends AbstractJWTFilter {
public static final String X_FORWARDED_PROTO = "X-Forwarded-Proto";
private static final String ORIGINAL_URL_QUERY_PARAM = "originalUrl=";
- private static final String DEFAULT_SSO_COOKIE_NAME = "hadoop-jwt";
+ public static final String DEFAULT_SSO_COOKIE_NAME = "hadoop-jwt";
/* A semicolon separated list of paths that need to bypass authentication */
private static final String SSO_UNAUTHENTICATED_PATHS_PARAM = "sso.unauthenticated.path.list";
diff --git a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java
index 8a2bffb35..7219a6a73 100644
--- a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java
+++ b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java
@@ -980,6 +980,10 @@ public abstract class AbstractJWTFilterTest {
return props;
}
+ protected SignedJWT getJWT(String issuer, String sub, Date expires) throws Exception {
+ return getJWT(issuer, sub, expires, privateKey);
+ }
+
protected SignedJWT getJWT(String issuer, String sub, Date expires, RSAPrivateKey privateKey)
throws Exception {
return getJWT(issuer, sub, expires, new Date(), privateKey, JWSAlgorithm.RS256.getName());
@@ -1069,6 +1073,10 @@ public abstract class AbstractJWTFilterTest {
subject = Subject.getSubject( AccessController.getContext() );
}
+
+ public Subject getSubject() {
+ return subject;
+ }
}
protected interface TokenVerificationCounter {
diff --git a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterTest.java b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterTest.java
index 20966bccc..f17897384 100644
--- a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterTest.java
+++ b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterTest.java
@@ -17,13 +17,23 @@
*/
package org.apache.knox.gateway.provider.federation;
-import com.nimbusds.jwt.SignedJWT;
+import static org.apache.knox.gateway.provider.federation.jwt.filter.AbstractJWTFilter.JWT_DEFAULT_ISSUER;
+import static org.apache.knox.gateway.provider.federation.jwt.filter.SSOCookieFederationFilter.DEFAULT_SSO_COOKIE_NAME;
+import static org.junit.Assert.assertEquals;
+
+import java.util.Date;
+import java.util.Properties;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import com.nimbusds.jwt.SignedJWT;
@SuppressWarnings("PMD.TestClassWithoutTestCases")
public class JWTFederationFilterTest extends AbstractJWTFilterTest {
@@ -72,4 +82,61 @@ public class JWTFederationFilterTest extends AbstractJWTFilterTest {
EasyMock.verify(response);
}
+
+ @Test
+ public void testCookieAuthSupportValidCookie() throws Exception {
+ testCookieAuthSupport(true);
+ }
+
+ @Test
+ public void testCookieAuthSupportInvalidCookie() throws Exception {
+ testCookieAuthSupport(false);
+ }
+
+ @Test
+ public void testCookieAuthSupportCustomCookieName() throws Exception {
+ testCookieAuthSupport(true, "customCookie");
+ }
+
+ private void testCookieAuthSupport(boolean validCookie) throws Exception {
+ testCookieAuthSupport(validCookie, null);
+ }
+
+ private void testCookieAuthSupport(boolean validCookie, String customCookieName) throws Exception {
+ final Properties properties = getProperties();
+ properties.put(JWTFederationFilter.KNOX_TOKEN_USE_COOKIE, "true");
+ if (customCookieName != null) {
+ properties.put(JWTFederationFilter.KNOX_TOKEN_COOKIE_NAME, customCookieName);
+ }
+ handler.init(new TestFilterConfig(properties));
+
+ final String subject = "bob";
+ final HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
+ final SignedJWT jwt = getJWT(JWT_DEFAULT_ISSUER, subject, new Date(System.currentTimeMillis() + 60000));
+ final Cookie[] cookies = new Cookie[1];
+ final Cookie cookie = EasyMock.createNiceMock(Cookie.class);
+ EasyMock.expect(cookie.getValue()).andReturn(jwt.serialize());
+ final String cookieName = validCookie ? (customCookieName == null ? DEFAULT_SSO_COOKIE_NAME : customCookieName) : "dummyCookie";
+ EasyMock.expect(cookie.getName()).andReturn(cookieName).anyTimes();
+ cookies[0] = cookie;
+ EasyMock.expect(request.getCookies()).andReturn(cookies).anyTimes();
+
+ final HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
+ if (!validCookie) {
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ EasyMock.expectLastCall().once();
+ }
+ EasyMock.replay(request, response, cookie);
+
+ final TestFilterChain chain = new TestFilterChain();
+ handler.doFilter(request, response, chain);
+
+ if (validCookie) {
+ assertEquals(1, chain.getSubject().getPrincipals().size());
+ assertEquals(subject, chain.getSubject().getPrincipals().iterator().next().getName());
+ } else {
+ EasyMock.verify(response);
+ }
+ }
+
}