added blacklist storage interface
This commit is contained in:
parent
339d6de8bd
commit
4c14116a41
3 changed files with 92 additions and 23 deletions
40
blacklist.go
Normal file
40
blacklist.go
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright (C) 2018 Marius Schellenberger
|
||||||
|
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
// Blacklist is the blacklisting storage interface
|
||||||
|
type Blacklist interface {
|
||||||
|
Add(string, int64)
|
||||||
|
Remove(string)
|
||||||
|
Check(string) bool
|
||||||
|
Map() MapBlacklist
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapBlacklist implements the Blacklist interface
|
||||||
|
type MapBlacklist map[string]int64
|
||||||
|
|
||||||
|
// NewMapBlacklist
|
||||||
|
func NewMapBlacklist() MapBlacklist {
|
||||||
|
return make(MapBlacklist)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a new token signature with expiration time to the blacklist
|
||||||
|
func (mb MapBlacklist) Add(sig string, exp int64) {
|
||||||
|
mb[sig] = exp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove deletes a token signature from the blacklist
|
||||||
|
func (mb MapBlacklist) Remove(sig string) {
|
||||||
|
delete(mb, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check returns true if a token signature is blacklisted and false otherwise
|
||||||
|
func (mb MapBlacklist) Check(sig string) (ok bool) {
|
||||||
|
_, ok = mb[sig]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map returns the blacklist in the form of a iterable map structure for cleanup
|
||||||
|
func (mb MapBlacklist) Map() MapBlacklist {
|
||||||
|
return mb
|
||||||
|
}
|
||||||
37
jwt.go
37
jwt.go
|
|
@ -46,16 +46,15 @@ type JWT struct {
|
||||||
|
|
||||||
// protects list
|
// protects list
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
blacklist bool
|
blacklist Blacklist
|
||||||
list map[string]int64
|
done chan struct{}
|
||||||
stop chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new JWT object with the given expiry timeout.
|
// New returns a new JWT object with the given expiry timeout.
|
||||||
// If the timeout is less or equal to zero the default expiry (12 hours) is used.
|
// If the timeout is less or equal to zero the default expiry (12 hours) is used.
|
||||||
// If blacklisting is enabled, the JWT object leaks a goroutine to garbage-collect expired blacklisted tokens.
|
// If blacklisting is enabled, the JWT object leaks a goroutine to garbage-collect expired blacklisted tokens.
|
||||||
// Call the Stop() method to exit the goroutine.
|
// Call the Stop() method to exit the goroutine.
|
||||||
func New(expiry time.Duration, blacklist bool) *JWT {
|
func New(expiry time.Duration, blacklist Blacklist) *JWT {
|
||||||
if expiry <= 0 {
|
if expiry <= 0 {
|
||||||
expiry = DefaultExpiry
|
expiry = DefaultExpiry
|
||||||
}
|
}
|
||||||
|
|
@ -66,9 +65,8 @@ func New(expiry time.Duration, blacklist bool) *JWT {
|
||||||
expiry: expiry,
|
expiry: expiry,
|
||||||
blacklist: blacklist,
|
blacklist: blacklist,
|
||||||
}
|
}
|
||||||
if blacklist {
|
if blacklist != nil {
|
||||||
jwt.list = make(map[string]int64)
|
jwt.done = make(chan struct{})
|
||||||
jwt.stop = make(chan struct{})
|
|
||||||
go jwt.clean()
|
go jwt.clean()
|
||||||
}
|
}
|
||||||
return jwt
|
return jwt
|
||||||
|
|
@ -83,7 +81,7 @@ func (jwt *JWT) sum(token string, h Hash) []byte {
|
||||||
// Invalidate checks if a token is already blacklisted
|
// Invalidate checks if a token is already blacklisted
|
||||||
// If the token is not blacklisted, it will get blacklisted
|
// If the token is not blacklisted, it will get blacklisted
|
||||||
func (jwt *JWT) Invalidate(t *Token) error {
|
func (jwt *JWT) Invalidate(t *Token) error {
|
||||||
if !jwt.blacklist {
|
if jwt.blacklist == nil {
|
||||||
return ErrBlacklistNotEnabled
|
return ErrBlacklistNotEnabled
|
||||||
}
|
}
|
||||||
if t == nil {
|
if t == nil {
|
||||||
|
|
@ -111,7 +109,7 @@ func (jwt *JWT) Invalidate(t *Token) error {
|
||||||
}
|
}
|
||||||
jwt.Lock()
|
jwt.Lock()
|
||||||
defer jwt.Unlock()
|
defer jwt.Unlock()
|
||||||
jwt.list[t.Sig()] = exp
|
jwt.blacklist.Add(t.Sig(), exp)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,28 +120,27 @@ func (jwt *JWT) blacklisted(sig string) error {
|
||||||
}
|
}
|
||||||
jwt.RLock()
|
jwt.RLock()
|
||||||
defer jwt.RUnlock()
|
defer jwt.RUnlock()
|
||||||
_, ok := jwt.list[sig]
|
if jwt.blacklist.Check(sig) {
|
||||||
if ok {
|
|
||||||
return ErrBlacklisted
|
return ErrBlacklisted
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean will look for expired blacklisted tokens and removes them
|
// clean looks for expired blacklisted tokens and removes them
|
||||||
func (jwt *JWT) clean() {
|
func (jwt *JWT) clean() {
|
||||||
for {
|
for {
|
||||||
t := time.NewTimer(time.Hour)
|
t := time.NewTimer(time.Hour)
|
||||||
select {
|
select {
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
case <-jwt.stop:
|
case <-jwt.done:
|
||||||
t.Stop()
|
t.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
now := time.Now().UTC().Unix()
|
now := time.Now().UTC().Unix()
|
||||||
jwt.Lock()
|
jwt.Lock()
|
||||||
for k, v := range jwt.list {
|
for k, v := range jwt.blacklist.Map() {
|
||||||
if now > v {
|
if now > v {
|
||||||
delete(jwt.list, k)
|
jwt.blacklist.Remove(k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jwt.Unlock()
|
jwt.Unlock()
|
||||||
|
|
@ -243,7 +240,7 @@ func (jwt *JWT) Verify(t *Token) error {
|
||||||
nbf = int64(n)
|
nbf = int64(n)
|
||||||
nbfOK = true
|
nbfOK = true
|
||||||
}
|
}
|
||||||
if expOK && jwt.blacklist {
|
if jwt.blacklist != nil {
|
||||||
err := jwt.blacklisted(t.Sig())
|
err := jwt.blacklisted(t.Sig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -264,14 +261,10 @@ func (jwt *JWT) Verify(t *Token) error {
|
||||||
// Stop will end the cleaner goroutinge execution
|
// Stop will end the cleaner goroutinge execution
|
||||||
// Calling Stop twice or more will panic
|
// Calling Stop twice or more will panic
|
||||||
func (jwt *JWT) Stop() error {
|
func (jwt *JWT) Stop() error {
|
||||||
if !jwt.blacklist {
|
if jwt.blacklist == nil {
|
||||||
return ErrBlacklistNotEnabled
|
return ErrBlacklistNotEnabled
|
||||||
}
|
}
|
||||||
select {
|
close(jwt.done)
|
||||||
case jwt.stop <- struct{}{}:
|
|
||||||
close(jwt.stop)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
38
jwt_test.go
38
jwt_test.go
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValidate(t *testing.T) {
|
func TestValidate(t *testing.T) {
|
||||||
jwt := New(time.Second, true)
|
jwt := New(time.Second, NewMapBlacklist())
|
||||||
token := NewToken(map[string]interface{}{
|
token := NewToken(map[string]interface{}{
|
||||||
"sub": "1234567890",
|
"sub": "1234567890",
|
||||||
"name": "John Doe",
|
"name": "John Doe",
|
||||||
|
|
@ -55,3 +55,39 @@ func TestValidate(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNoBlacklist(t *testing.T) {
|
||||||
|
jwt := New(time.Second, nil)
|
||||||
|
token := NewToken(map[string]interface{}{
|
||||||
|
"sub": "1234567890",
|
||||||
|
"name": "John Doe",
|
||||||
|
"admin": true,
|
||||||
|
"fizz": "buzz",
|
||||||
|
}, nil)
|
||||||
|
err := jwt.Sign(token)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
_, err = DecodeToken("")
|
||||||
|
if err == nil {
|
||||||
|
t.Error(errors.New("token is empty"))
|
||||||
|
}
|
||||||
|
nt, err := DecodeToken(token.String())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = jwt.Verify(nt)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 2)
|
||||||
|
err = jwt.Verify(nt)
|
||||||
|
if err == nil {
|
||||||
|
t.Error(errors.New("token should be expired"))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = jwt.Invalidate(nt)
|
||||||
|
if err == nil {
|
||||||
|
t.Error(errors.New("blacklisting should be disabled"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue