/
cache.go
157 lines (138 loc) · 3.57 KB
/
cache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Tideland Go REST Server Library - JSON Web Token - Cache
//
// Copyright (C) 2016-2017 Frank Mueller / Tideland / Oldenburg / Germany
//
// All rights reserved. Use of this source code is governed
// by the new BSD license.
package jwt
//--------------------
// IMPORTS
//--------------------
import (
"sync"
"time"
"github.com/tideland/golib/loop"
)
//--------------------
// CACHE
//--------------------
// Cache provides a caching for tokens so that these
// don't have to be decoded or verified multiple times.
type Cache interface {
// Get tries to retrieve a token from the cache.
Get(token string) (JWT, bool)
// Put adds a token to the cache.
Put(jwt JWT) int
// Cleanup manually tells the cache to cleanup.
Cleanup()
// Stop tells the cache to end working.
Stop() error
}
// cacheEntry manages a token and its access time.
type cacheEntry struct {
jwt JWT
accessed time.Time
}
// cache implements Cache.
type cache struct {
mutex sync.Mutex
entries map[string]*cacheEntry
ttl time.Duration
leeway time.Duration
interval time.Duration
maxEntries int
cleanupc chan time.Duration
loop loop.Loop
}
// NewCache creates a new JWT caching. The ttl value controls
// the time a cached token may be unused before cleanup. The
// leeway is used for the time validation of the token itself.
// The duration of the interval controls how often the background
// cleanup is running. Final configuration parameter is the maximum
// number of entries inside the cache. If these grow too fast the
// ttl will be temporarily reduced for cleanup.
func NewCache(ttl, leeway, interval time.Duration, maxEntries int) Cache {
c := &cache{
entries: map[string]*cacheEntry{},
ttl: ttl,
leeway: leeway,
interval: interval,
maxEntries: maxEntries,
cleanupc: make(chan time.Duration, 5),
}
c.loop = loop.Go(c.backendLoop, "jwt", "cache")
return c
}
// Get implements the Cache interface.
func (c *cache) Get(token string) (JWT, bool) {
c.mutex.Lock()
defer c.mutex.Unlock()
entry, ok := c.entries[token]
if !ok {
return nil, false
}
if entry.jwt.IsValid(c.leeway) {
entry.accessed = time.Now()
return entry.jwt, true
}
// Remove invalid token.
delete(c.entries, token)
return nil, false
}
// Put implements the Cache interface.
func (c *cache) Put(jwt JWT) int {
c.mutex.Lock()
defer c.mutex.Unlock()
if jwt.IsValid(c.leeway) {
c.entries[jwt.String()] = &cacheEntry{jwt, time.Now()}
lenEntries := len(c.entries)
if lenEntries > c.maxEntries {
ttl := int64(c.ttl) / int64(lenEntries) * int64(c.maxEntries)
c.cleanupc <- time.Duration(ttl)
}
}
return len(c.entries)
}
// Cleanup implements the Cache interface.
func (c *cache) Cleanup() {
c.cleanupc <- c.ttl
}
// Stop implements the Cache interface.
func (c *cache) Stop() error {
return c.loop.Stop()
}
// backendLoop runs a cleaning session every five minutes.
func (c *cache) backendLoop(l loop.Loop) error {
defer func() {
// Cleanup entries map after stop or error.
c.entries = nil
}()
ticker := time.NewTicker(c.interval)
for {
select {
case <-l.ShallStop():
return nil
case ttl := <-c.cleanupc:
c.cleanup(ttl)
case <-ticker.C:
c.cleanup(c.ttl)
}
}
}
// cleanup checks for invalid or unused tokens.
func (c *cache) cleanup(ttl time.Duration) {
c.mutex.Lock()
defer c.mutex.Unlock()
valids := map[string]*cacheEntry{}
now := time.Now()
for token, entry := range c.entries {
if entry.jwt.IsValid(c.leeway) {
if entry.accessed.Add(ttl).After(now) {
// Everything fine.
valids[token] = entry
}
}
}
c.entries = valids
}
// EOF