1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.bremersee.security.authentication;
18
19 import java.time.Duration;
20 import java.util.Objects;
21 import java.util.Optional;
22 import java.util.Set;
23 import java.util.Timer;
24 import java.util.TimerTask;
25 import java.util.function.BiFunction;
26 import javax.validation.constraints.NotNull;
27 import lombok.Setter;
28 import lombok.extern.slf4j.Slf4j;
29 import org.springframework.beans.factory.DisposableBean;
30 import org.springframework.cache.Cache;
31 import org.springframework.cache.concurrent.ConcurrentMapCache;
32 import org.springframework.util.StringUtils;
33
34
35
36
37
38
39 @Slf4j
40 public class AccessTokenCacheImpl implements AccessTokenCache, DisposableBean {
41
42 private Timer internalCacheTimer;
43
44 private final Cache cache;
45
46 private final Duration expirationTimeThreshold;
47
48 private final String keyPrefix;
49
50 @Setter
51 @NotNull
52 private BiFunction<String, Duration, Boolean> expiredBiFn = AccessTokenCache::isExpired;
53
54
55
56
57 @SuppressWarnings("unused")
58 public AccessTokenCacheImpl() {
59 this(null, null);
60 }
61
62
63
64
65
66
67
68 public AccessTokenCacheImpl(Duration expirationTimeThreshold, String keyPrefix) {
69 this(null, expirationTimeThreshold, keyPrefix);
70 }
71
72
73
74
75
76
77 @SuppressWarnings("unused")
78 public AccessTokenCacheImpl(Cache cache) {
79 this(cache, null, null);
80 }
81
82
83
84
85
86
87
88
89 public AccessTokenCacheImpl(
90 Cache cache,
91 Duration expirationTimeThreshold,
92 String keyPrefix) {
93 if (cache != null) {
94 log.info("Creating access token cache with given cache.");
95 this.cache = cache;
96 } else {
97 log.info("Creating access token cache with internal in memory cache.");
98 this.cache = createInternalCache();
99 }
100 this.expirationTimeThreshold = Objects
101 .requireNonNullElseGet(expirationTimeThreshold, () -> Duration.ofSeconds(20L));
102 this.keyPrefix = keyPrefix;
103 }
104
105 private ConcurrentMapCache createInternalCache() {
106 final long period = 1000L * 60L * 30L;
107 ConcurrentMapCache internalCache = new ConcurrentMapCache(CACHE_NAME);
108 internalCacheTimer = new Timer();
109 internalCacheTimer.schedule(new TimerTask() {
110 @Override
111 public void run() {
112 Set<Object> keys = internalCache.getNativeCache().keySet();
113 log.trace("Removing obsolete jwt entries from internal cache (sze = {}).", keys.size());
114 keys.forEach(key -> findAccessToken(String.valueOf(key))
115 .filter(token -> expiredBiFn.apply(token, expirationTimeThreshold))
116 .ifPresent(token -> internalCache.evict(key)));
117 }
118 }, period, period);
119 return internalCache;
120 }
121
122 private String addKeyPrefix(String givenKey) {
123 if (StringUtils.hasText(keyPrefix) && !givenKey.startsWith(keyPrefix)) {
124 return keyPrefix + givenKey;
125 }
126 return givenKey;
127 }
128
129 @Override
130 public Optional<String> findAccessToken(String key) {
131 try {
132 return Optional.ofNullable(cache.get(addKeyPrefix(key), String.class))
133 .filter(token -> !expiredBiFn.apply(token, expirationTimeThreshold));
134
135 } catch (RuntimeException e) {
136 log.error("Getting access token from cache failed.", e);
137 return Optional.empty();
138 }
139 }
140
141 @Override
142 public void putAccessToken(String key, String accessToken) {
143 try {
144 cache.put(addKeyPrefix(key), accessToken);
145
146 } catch (RuntimeException e) {
147 log.error("Putting access token into the cache failed.", e);
148 }
149 }
150
151 @Override
152 public void destroy() {
153 if (internalCacheTimer != null) {
154 internalCacheTimer.cancel();
155 }
156 }
157
158 @Override
159 public String toString() {
160 return "AccessTokenCacheImpl {cache = "
161 + (internalCacheTimer != null ? "INTERNAL" : "EXTERNAL")
162 + ", keyPrefix = " + keyPrefix
163 + ", expirationTimeThreshold (in secs) = " + expirationTimeThreshold.toSeconds()
164 + '}';
165 }
166 }