View Javadoc
1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.wss4j.common.cache;
21  
22  import java.io.IOException;
23  import java.time.Instant;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map.Entry;
30  import java.util.Set;
31  import java.util.SortedMap;
32  import java.util.TreeMap;
33  
34  /**
35   * A simple in-memory HashSet based cache to prevent against replay attacks. The default TTL is 5 minutes
36   * and the max TTL is 60 minutes.
37   */
38  public class MemoryReplayCache implements ReplayCache {
39  
40      public static final long DEFAULT_TTL = 60L * 5L;
41      public static final long MAX_TTL = DEFAULT_TTL * 12L;
42      private final SortedMap<Instant, List<String>> cache = new TreeMap<>();
43      private final Set<String> ids = Collections.synchronizedSet(new HashSet<>());
44  
45      /**
46       * Add the given identifier to the cache. It will be cached for a default amount of time.
47       * @param identifier The identifier to be added
48       */
49      public void add(String identifier) {
50          add(identifier, Instant.now().plusSeconds(DEFAULT_TTL));
51      }
52  
53      /**
54       * Add the given identifier to the cache to be cached for the given time
55       * @param identifier The identifier to be added
56       * @param expiry A custom expiry time for the identifier
57       */
58      public void add(String identifier, Instant expiry) {
59          if (identifier == null || identifier.length() == 0) {
60              return;
61          }
62  
63          Instant now = Instant.now();
64          Instant maxTTL = now.plusSeconds(MAX_TTL);
65          if (expiry == null || expiry.isBefore(now) || expiry.isAfter(maxTTL)) {
66              expiry = now.plusSeconds(DEFAULT_TTL);
67          }
68  
69          synchronized (cache) {
70              List<String> list = cache.get(expiry);
71              if (list == null) {
72                  list = new ArrayList<>(1);
73                  cache.put(expiry, list);
74              }
75              list.add(identifier);
76          }
77          ids.add(identifier);
78      }
79  
80      /**
81       * Return true if the given identifier is contained in the cache
82       * @param identifier The identifier to check
83       */
84      public boolean contains(String identifier) {
85          processTokenExpiry();
86  
87          if (identifier != null && identifier.length() != 0) {
88              return ids.contains(identifier);
89          }
90          return false;
91      }
92  
93      protected void processTokenExpiry() {
94          Instant current = Instant.now();
95          synchronized (cache) {
96              Iterator<Entry<Instant, List<String>>> it = cache.entrySet().iterator();
97              while (it.hasNext()) {
98                  Entry<Instant, List<String>> entry = it.next();
99                  if (entry.getKey().isBefore(current)) {
100                     for (String id : entry.getValue()) {
101                         ids.remove(id);
102                     }
103                     it.remove();
104                 } else {
105                     break;
106                 }
107             }
108         }
109     }
110 
111     @Override
112     public synchronized void close() throws IOException {
113         cache.clear();
114         ids.clear();
115     }
116 }