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

robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-rampart.git


The following commit(s) were added to refs/heads/master by this push:
     new da0ff646 RAMPART-337: retire expired tokens from SimpleTokenStore to 
bound memory
da0ff646 is described below

commit da0ff64656814e24c0926395c4dfbee331c07593
Author: Robert Lazarski <[email protected]>
AuthorDate: Tue Jun 9 14:09:53 2026 -1000

    RAMPART-337: retire expired tokens from SimpleTokenStore to bound memory
    
    Issued tokens were added to the in-memory token store but expired tokens 
were
    only marked EXPIRED, never removed, so under sustained 
STS/SecureConversation
    load the store grew without bound and could exhaust the heap.
    
    SimpleTokenStore.add() now opportunistically purges tokens whose expiry time
    elapsed more than a configurable grace period ago (default 5 minutes). The 
grace
    period keeps recently-expired tokens available so an in-flight message that 
still
    references one does not fail with "Unsupported key identification" (the 
failure
    mode noted on the issue when expired tokens were deleted immediately). 
Tokens
    with no expiry are never purged.
    
    Adds SimpleTokenStoreTest coverage for retirement and for grace-period 
retention.
    Verified with a full clean -Papache-release verify (all modules, all tests
    including the 9 policy samples) on JDK 25.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
---
 .../org/apache/rahas/SimpleTokenStoreTest.java     | 37 ++++++++++++
 .../java/org/apache/rahas/SimpleTokenStore.java    | 65 ++++++++++++++++++++--
 2 files changed, 96 insertions(+), 6 deletions(-)

diff --git 
a/modules/rampart-tests/src/test/java/org/apache/rahas/SimpleTokenStoreTest.java
 
b/modules/rampart-tests/src/test/java/org/apache/rahas/SimpleTokenStoreTest.java
index 98cce915..d479396b 100644
--- 
a/modules/rampart-tests/src/test/java/org/apache/rahas/SimpleTokenStoreTest.java
+++ 
b/modules/rampart-tests/src/test/java/org/apache/rahas/SimpleTokenStoreTest.java
@@ -136,6 +136,43 @@ public class SimpleTokenStoreTest extends TestCase {
         }
     }
 
+    public void testExpiredTokensAreRetired() {
+        // RAMPART-337: long-expired tokens must not accumulate in the store.
+        SimpleTokenStore store = new SimpleTokenStore();
+        store.setExpiredTokenGracePeriodMillis(0);
+        try {
+            // Two tokens that expired a minute ago, plus one still valid.
+            Token expired1 = getTestToken("expired-1", new 
Date(System.currentTimeMillis() - 60000));
+            Token expired2 = getTestToken("expired-2", new 
Date(System.currentTimeMillis() - 60000));
+            store.add(expired1);
+            store.add(expired2);
+            // Adding a fresh token triggers retirement of the expired ones.
+            Token valid = getTestToken("valid-1", new 
Date(System.currentTimeMillis() + 60000));
+            store.add(valid);
+
+            String[] ids = store.getTokenIdentifiers();
+            assertEquals("Expired tokens should have been retired from the 
store", 1, ids.length);
+            assertEquals("Only the valid token should remain", "valid-1", 
ids[0]);
+        } catch (TrustException e) {
+            fail(e.getMessage());
+        }
+    }
+
+    public void testExpiredTokensWithinGracePeriodAreKept() {
+        // A token that just expired must survive while within the grace 
period,
+        // so in-flight messages referencing it do not fail.
+        SimpleTokenStore store = new SimpleTokenStore();
+        store.setExpiredTokenGracePeriodMillis(5 * 60 * 1000L);
+        try {
+            store.add(getTestToken("just-expired", new 
Date(System.currentTimeMillis() - 1000)));
+            store.add(getTestToken("valid", new 
Date(System.currentTimeMillis() + 60000)));
+            assertEquals("Recently-expired token must be kept within the grace 
period",
+                         2, store.getTokenIdentifiers().length);
+        } catch (TrustException e) {
+            fail(e.getMessage());
+        }
+    }
+
     private Token getTestToken(String tokenId)
         throws TrustException {
         return getTestToken(tokenId, new Date());
diff --git 
a/modules/rampart-trust/src/main/java/org/apache/rahas/SimpleTokenStore.java 
b/modules/rampart-trust/src/main/java/org/apache/rahas/SimpleTokenStore.java
index 7af8f1e1..e7e0e2b9 100644
--- a/modules/rampart-trust/src/main/java/org/apache/rahas/SimpleTokenStore.java
+++ b/modules/rampart-trust/src/main/java/org/apache/rahas/SimpleTokenStore.java
@@ -47,17 +47,38 @@ public class SimpleTokenStore implements TokenStorage, 
Serializable {
      */
      protected final ReadWriteLock readWriteLock = new 
ReentrantReadWriteLock();
      
-     protected final Lock readLock = readWriteLock.readLock(); 
-     
+     protected final Lock readLock = readWriteLock.readLock();
+
      protected final Lock writeLock = readWriteLock.writeLock();
 
+    /**
+     * Default grace period (5 minutes) after a token's expiry before it 
becomes
+     * eligible for removal from the store.
+     */
+    public static final long DEFAULT_EXPIRED_TOKEN_GRACE_PERIOD_MILLIS = 5 * 
60 * 1000L;
+
+    /**
+     * Tokens whose expiry time elapsed more than this many milliseconds ago 
are
+     * purged from the store when a new token is added, so that expired tokens 
do
+     * not accumulate indefinitely and exhaust the heap (RAMPART-337).
+     * <p>
+     * The grace period deliberately keeps recently-expired tokens around for a
+     * while: removing a token the instant it expires can break an in-flight
+     * message that still references it, which previously surfaced as
+     * "The signature or decryption was invalid (Unsupported key 
identification)".
+     */
+    private long expiredTokenGracePeriodMillis = 
DEFAULT_EXPIRED_TOKEN_GRACE_PERIOD_MILLIS;
+
     public void add(Token token) throws TrustException {
-               
+
         if (token != null && !"".equals(token.getId()) && token.getId() != 
null) {
-            
+
             writeLock.lock();
-            
+
             try {
+                // Opportunistically retire long-expired tokens so the store 
does
+                // not grow without bound (RAMPART-337).
+                removeExpiredTokens();
                 if (this.tokens.keySet().size() == 0
                     || (this.tokens.keySet().size() > 0 && !this.tokens
                         .keySet().contains(token.getId()))) {
@@ -69,7 +90,39 @@ public class SimpleTokenStore implements TokenStorage, 
Serializable {
             } finally {
                 writeLock.unlock();
             }
-        }           
+        }
+    }
+
+    /**
+     * Removes tokens whose expiry time elapsed more than
+     * {@link #getExpiredTokenGracePeriodMillis()} milliseconds ago. Tokens 
with
+     * no expiry time are never removed. Callers must hold the write lock.
+     */
+    private void removeExpiredTokens() {
+        long now = System.currentTimeMillis();
+        for (Iterator iterator = this.tokens.values().iterator(); 
iterator.hasNext();) {
+            Token token = (Token) iterator.next();
+            if (token.getExpires() != null
+                && token.getExpires().getTime() + 
expiredTokenGracePeriodMillis < now) {
+                iterator.remove();
+            }
+        }
+    }
+
+    /**
+     * The grace period, in milliseconds, applied after a token's expiry 
before it
+     * is eligible for removal from the store.
+     */
+    public long getExpiredTokenGracePeriodMillis() {
+        return expiredTokenGracePeriodMillis;
+    }
+
+    /**
+     * Sets the grace period, in milliseconds, applied after a token's expiry
+     * before it is eligible for removal from the store.
+     */
+    public void setExpiredTokenGracePeriodMillis(long 
expiredTokenGracePeriodMillis) {
+        this.expiredTokenGracePeriodMillis = expiredTokenGracePeriodMillis;
     }
 
     public void update(Token token) throws TrustException {

Reply via email to