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 2bd305d  KNOX-2603 - Caching passcode token verifications (#448)
2bd305d is described below

commit 2bd305d3aee25a5d15a78b3df310f552eaa87dd1
Author: Sandor Molnar <[email protected]>
AuthorDate: Mon May 17 19:53:49 2021 +0200

    KNOX-2603 - Caching passcode token verifications (#448)
---
 .../hadoopauth/filter/HadoopAuthFilterTest.java    |  2 +-
 .../federation/jwt/filter/AbstractJWTFilter.java   | 34 +++++++++++-------
 .../federation/jwt/filter/JWTFederationFilter.java |  3 +-
 .../jwt/filter/SignatureVerificationCache.java     | 28 +++++++--------
 .../provider/federation/AbstractJWTFilterTest.java |  8 +++--
 ...okenIDAsHTTPBasicCredsFederationFilterTest.java | 42 ++++++++++------------
 .../token/impl/AliasBasedTokenStateService.java    |  2 +-
 7 files changed, 62 insertions(+), 57 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 d4bbac6..20825a5 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
@@ -551,7 +551,7 @@ public class HadoopAuthFilterTest {
       
expect(filterConfig.getInitParameter(JWTFederationFilter.JWT_UNAUTHENTICATED_PATHS_PARAM)).andReturn(null).anyTimes();
       
expect(filterConfig.getInitParameter(AbstractJWTFilter.JWT_EXPECTED_ISSUER)).andReturn(null).anyTimes();
       
expect(filterConfig.getInitParameter(AbstractJWTFilter.JWT_EXPECTED_SIGALG)).andReturn(null).anyTimes();
-      
expect(filterConfig.getInitParameter(SignatureVerificationCache.JWT_VERIFIED_CACHE_MAX)).andReturn(null).anyTimes();
+      
expect(filterConfig.getInitParameter(SignatureVerificationCache.TOKENS_VERIFIED_CACHE_MAX)).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/filter/AbstractJWTFilter.java
 
b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
index 82b39c0..3a0179a 100644
--- 
a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
+++ 
b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
@@ -382,7 +382,7 @@ public abstract class AbstractJWTFilter implements Filter {
       try {
         if (tokenId != null) {
           if (tokenIsStillValid(tokenId)) {
-            if (validatePasscode(tokenId, passcode)) {
+            if (hasSignatureBeenVerified(passcode) || 
validatePasscode(tokenId, passcode)) {
               return true;
             } else {
               log.wrongPasscodeToken(tokenId);
@@ -390,6 +390,10 @@ public abstract class AbstractJWTFilter implements Filter {
             }
           } else {
             log.tokenHasExpired(Tokens.getTokenIDDisplayText(tokenId));
+            // Explicitly evict the record of this token's signature 
verification (if present).
+            // There is no value in keeping this record for expired tokens, 
and explicitly removing them may prevent
+            // records for other valid tokens from being prematurely evicted 
from the cache.
+            removeSignatureVerificationRecord(passcode);
             handleValidationError(request, response, 
HttpServletResponse.SC_BAD_REQUEST,
                     "Bad request: token has expired");
           }
@@ -412,7 +416,11 @@ public abstract class AbstractJWTFilter implements Filter {
     final TokenMetadata tokenMetadata = 
tokenStateService.getTokenMetadata(tokenId);
     final String userName = tokenMetadata == null ? "" : 
tokenMetadata.getUserName();
     final byte[] storedPasscode = tokenMetadata == null ? null : 
tokenMetadata.getPasscode().getBytes(UTF_8);
-    return Arrays.equals(tokenMAC.hash(tokenId, issueTime, userName, 
passcode).getBytes(UTF_8), storedPasscode);
+    final boolean validPasscode = Arrays.equals(tokenMAC.hash(tokenId, 
issueTime, userName, passcode).getBytes(UTF_8), storedPasscode);
+    if (validPasscode) {
+      recordSignatureVerification(passcode);
+    }
+    return validPasscode;
   }
 
   protected boolean verifyTokenSignature(final JWT token) {
@@ -459,32 +467,32 @@ public abstract class AbstractJWTFilter implements Filter 
{
   }
 
   /**
-   * Determine if the specified JWT signature has previously been successfully 
verified.
+   * Determine if the specified JWT or Passcode token signature has previously 
been successfully verified.
    *
-   * @param jwt A serialized JWT String.
+   * @param token A serialized JWT String or Passcode token.
    *
    * @return true, if the specified token has been previously verified; 
Otherwise, false.
    */
-  protected boolean hasSignatureBeenVerified(final String jwt) {
-    return signatureVerificationCache.hasSignatureBeenVerified(jwt);
+  protected boolean hasSignatureBeenVerified(final String token) {
+    return signatureVerificationCache.hasSignatureBeenVerified(token);
   }
 
   /**
-   * Record a successful JWT signature verification.
+   * Record a successful JWT or Passcode token signature verification.
    *
-   * @param jwt The serialized String for a JWT which has been successfully 
verified.
+   * @param token The serialized String for a JWT or Passcode token which has 
been successfully verified.
    */
-  protected void recordSignatureVerification(final String jwt) {
-    signatureVerificationCache.recordSignatureVerification(jwt);
+  protected void recordSignatureVerification(final String token) {
+    signatureVerificationCache.recordSignatureVerification(token);
   }
 
   /**
    * Explicitly evict the signature verification record for the specified JWT 
from the cache if it exists.
    *
-   * @param jwt The serialized String for a JWT whose signature verification 
record should be evicted.
+   * @param token The serialized String for a JWT or Passcode token whose 
signature verification record should be evicted.
    */
-  protected void removeSignatureVerificationRecord(final String jwt) {
-    signatureVerificationCache.removeSignatureVerificationRecord(jwt);
+  protected void removeSignatureVerificationRecord(final String token) {
+    signatureVerificationCache.removeSignatureVerificationRecord(token);
   }
 
   protected abstract void handleValidationError(HttpServletRequest request, 
HttpServletResponse response, int status,
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 b5d34b7..687ff34 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
@@ -143,7 +143,8 @@ public class JWTFederationFilter extends AbstractJWTFilter {
       } else if (TokenType.Passcode.equals(tokenType)) {
         // Validate the token based on the server-managed metadata
         // The received token value must be a Base64 encoded value of 
Base64(tokenId)::Base64(rawPasscode)
-        String tokenId = null, passcode = null;
+        String tokenId = null;
+        String passcode = null;
         try {
           final String[] base64DecodedTokenIdAndPasscode = 
decodeBase64(tokenValue).split("::");
           tokenId = decodeBase64(base64DecodedTokenIdAndPasscode[0]);
diff --git 
a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SignatureVerificationCache.java
 
b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SignatureVerificationCache.java
index 2e62300..246b6c9 100644
--- 
a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SignatureVerificationCache.java
+++ 
b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SignatureVerificationCache.java
@@ -30,8 +30,8 @@ import java.util.concurrent.ConcurrentHashMap;
  */
 public class SignatureVerificationCache {
 
-    public static final String JWT_VERIFIED_CACHE_MAX = 
"jwt.verified.cache.max";
-    public static final int    JWT_VERIFIED_CACHE_MAX_DEFAULT = 250;
+    public static final String TOKENS_VERIFIED_CACHE_MAX = 
"tokens.verified.cache.max";
+    private static final int   TOKENS_VERIFIED_CACHE_MAX_DEFAULT = 250;
 
     static final String DEFAULT_CACHE_ID = "default-cache";
 
@@ -71,9 +71,9 @@ public class SignatureVerificationCache {
      * @param config The configuration of the provider employing this cache.
      */
     private void initializeVerifiedTokensCache(final FilterConfig config) {
-        int maxCacheSize = JWT_VERIFIED_CACHE_MAX_DEFAULT;
+        int maxCacheSize = TOKENS_VERIFIED_CACHE_MAX_DEFAULT;
 
-        String configValue = config.getInitParameter(JWT_VERIFIED_CACHE_MAX);
+        String configValue = 
config.getInitParameter(TOKENS_VERIFIED_CACHE_MAX);
         if (configValue != null && !configValue.isEmpty()) {
             try {
                 maxCacheSize = Integer.parseInt(configValue);
@@ -86,32 +86,32 @@ public class SignatureVerificationCache {
     }
 
     /**
-     * Determine if the specified JWT's signature has previously been 
successfully verified.
+     * Determine if the specified token's signature has previously been 
successfully verified.
      *
-     * @param jwt A serialized JWT.
+     * @param token A serialized JWT or Passcode token.
      *
      * @return true, if the specified token has been previously verified; 
Otherwise, false.
      */
-    public boolean hasSignatureBeenVerified(final String jwt) {
-        return (verifiedTokens.getIfPresent(jwt) != null);
+    public boolean hasSignatureBeenVerified(final String token) {
+        return (verifiedTokens.getIfPresent(token) != null);
     }
 
     /**
      * Record a successful token signature verification.
      *
-     * @param jwt A serialized JWT for which the signature has been 
successfully verified.
+     * @param token A serialized JWT or Passcode token for which the signature 
has been successfully verified.
      */
-    public void recordSignatureVerification(final String jwt) {
-        verifiedTokens.put(jwt, true);
+    public void recordSignatureVerification(final String token) {
+        verifiedTokens.put(token, true);
     }
 
     /**
      * Explicitly evict the signature verification record from the cache if it 
exists.
      *
-     * @param jwt The serialized JWT for which the associated signature 
verification record should be evicted.
+     * @param token The serialized JWT or Passcode token for which the 
associated signature verification record should be evicted.
      */
-    public void removeSignatureVerificationRecord(final String jwt) {
-         verifiedTokens.asMap().remove(jwt);
+    public void removeSignatureVerificationRecord(final String token) {
+         verifiedTokens.asMap().remove(token);
     }
 
     /**
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 aad5048..18b802d 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
@@ -75,6 +75,7 @@ import static org.junit.Assert.fail;
 public abstract class AbstractJWTFilterTest  {
   protected static final String SERVICE_URL = 
"https://localhost:8888/resource";;
   private static final String dnTemplate = 
"CN={0},OU=Test,O=Hadoop,L=Test,ST=Test,C=US";
+  protected static final String PASSCODE_CLAIM = "passcode";
 
   protected AbstractJWTFilter handler;
   protected static RSAPublicKey publicKey;
@@ -802,7 +803,7 @@ public abstract class AbstractJWTFilterTest  {
 
       Properties props = getProperties();
       props.put(AbstractJWTFilter.JWT_EXPECTED_SIGALG, "RS512");
-      props.put(SignatureVerificationCache.JWT_VERIFIED_CACHE_MAX, "1");
+      props.put(SignatureVerificationCache.TOKENS_VERIFIED_CACHE_MAX, "1");
       props.put(TestFilterConfig.TOPOLOGY_NAME_PROP, 
"jwt-verification-optimization-test");
       handler.init(new TestFilterConfig(props));
       Assert.assertEquals("Expected no token verification calls yet.",
@@ -856,7 +857,7 @@ public abstract class AbstractJWTFilterTest  {
 
       Properties props = getProperties();
       props.put(AbstractJWTFilter.JWT_EXPECTED_SIGALG, "RS512");
-      props.put(SignatureVerificationCache.JWT_VERIFIED_CACHE_MAX, "1");
+      props.put(SignatureVerificationCache.TOKENS_VERIFIED_CACHE_MAX, "1");
       props.put(TestFilterConfig.TOPOLOGY_NAME_PROP, "jwt-eviction-test");
       handler.init(new TestFilterConfig(props));
       Assert.assertEquals("Expected no token verification calls yet.",
@@ -1009,7 +1010,8 @@ public abstract class AbstractJWTFilterTest  {
             .audience(aud)
             .expirationTime(expires)
             .notBeforeTime(nbf)
-            .claim("scope", "openid");
+            .claim("scope", "openid")
+            .claim(PASSCODE_CLAIM, UUID.randomUUID().toString());
     if (knoxId != null) {
       builder.claim(JWTToken.KNOX_ID_CLAIM, knoxId);
     }
diff --git 
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/TokenIDAsHTTPBasicCredsFederationFilterTest.java
 
b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/TokenIDAsHTTPBasicCredsFederationFilterTest.java
index cbd28b3..a65888f 100644
--- 
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/TokenIDAsHTTPBasicCredsFederationFilterTest.java
+++ 
b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/TokenIDAsHTTPBasicCredsFederationFilterTest.java
@@ -18,7 +18,21 @@
  */
 package org.apache.knox.gateway.provider.federation;
 
-import com.nimbusds.jwt.SignedJWT;
+import static org.junit.Assert.fail;
+
+import java.nio.charset.StandardCharsets;
+import java.text.ParseException;
+import java.time.Instant;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.codec.digest.HmacAlgorithms;
@@ -37,22 +51,7 @@ import org.easymock.EasyMock;
 import org.junit.Assert;
 import org.junit.Test;
 
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import java.nio.charset.StandardCharsets;
-import java.text.ParseException;
-import java.time.Instant;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.TimeUnit;
-
-import static org.junit.Assert.fail;
+import com.nimbusds.jwt.SignedJWT;
 
 @SuppressWarnings({"PMD.JUnit4TestShouldUseBeforeAnnotation", 
"PMD.JUnit4TestShouldUseTestAnnotation"})
 public class TokenIDAsHTTPBasicCredsFederationFilterTest extends 
JWTAsHTTPBasicCredsFederationFilterTest {
@@ -74,7 +73,7 @@ public class TokenIDAsHTTPBasicCredsFederationFilterTest 
extends JWTAsHTTPBasicC
       try {
         final long issueTime = System.currentTimeMillis() - 
TimeUnit.MINUTES.toMillis(5);
         final String subject = (String) 
jwt.getJWTClaimsSet().getClaim(JWTToken.SUBJECT);
-        final String passcode = UUID.randomUUID().toString();
+        final String passcode = (String) 
jwt.getJWTClaimsSet().getClaims().get(PASSCODE_CLAIM);
         addTokenState(jwt, issueTime, subject, passcode);
         setTokenOnRequest(request, TestJWTFederationFilter.PASSCODE, 
generatePasscodeField(getTokenId(jwt), passcode));
       } catch(ParseException e) {
@@ -101,7 +100,7 @@ public class TokenIDAsHTTPBasicCredsFederationFilterTest 
extends JWTAsHTTPBasicC
       try {
         final long issueTime = System.currentTimeMillis() - 
TimeUnit.MINUTES.toMillis(5);
         final String subject = (String) 
jwt.getJWTClaimsSet().getClaim(JWTToken.SUBJECT);
-        final String passcode = UUID.randomUUID().toString();
+        final String passcode = (String) 
jwt.getJWTClaimsSet().getClaims().get(PASSCODE_CLAIM);
         addTokenState(jwt, issueTime, subject, passcode);
         setTokenOnRequest(request, authUsername, 
generatePasscodeField(getTokenId(jwt), passcode));
       } catch(ParseException e) {
@@ -339,11 +338,6 @@ public class TokenIDAsHTTPBasicCredsFederationFilterTest 
extends JWTAsHTTPBasicC
     }
 
     @Override
-    public void testVerificationOptimization() throws Exception {
-        // Override to disable N/A test
-    }
-
-    @Override
     public void testExpiredTokensEvictedFromSignatureVerificationCache() 
throws Exception {
         // Override to disable N/A test
     }
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
index 1e76662..c41eb60 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/AliasBasedTokenStateService.java
@@ -353,7 +353,7 @@ public class AliasBasedTokenStateService extends 
DefaultTokenStateService implem
       }
       issueTime = convertCharArrayToLong(issueTimeStr);
       // Update the in-memory cache to avoid subsequent keystore look-ups for 
the same state
-      super.setIssueTime(tokenId, issueTime);
+      setIssueTimeInMemory(tokenId, issueTime);
     } catch (UnknownTokenException e) {
       throw e;
     } catch (Exception e) {

Reply via email to