moved sync out of JWT and added new secret reader
This commit is contained in:
parent
4c14116a41
commit
9b4adfe863
3 changed files with 82 additions and 33 deletions
49
blacklist.go
49
blacklist.go
|
|
@ -2,39 +2,60 @@
|
||||||
|
|
||||||
package jwt
|
package jwt
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
// Blacklist is the blacklisting storage interface
|
// Blacklist is the blacklisting storage interface
|
||||||
type Blacklist interface {
|
type Blacklist interface {
|
||||||
Add(string, int64)
|
Add(string, int64)
|
||||||
Remove(string)
|
Remove(string)
|
||||||
Check(string) bool
|
Check(string) bool
|
||||||
Map() MapBlacklist
|
Map() BlacklistMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// MapBlacklist implements the Blacklist interface
|
// BlacklistMap is the blacklist map structure
|
||||||
type MapBlacklist map[string]int64
|
type BlacklistMap map[string]int64
|
||||||
|
|
||||||
// NewMapBlacklist
|
// MemBlacklist implements the Blacklist interface
|
||||||
func NewMapBlacklist() MapBlacklist {
|
type MemBlacklist struct {
|
||||||
return make(MapBlacklist)
|
// protects list
|
||||||
|
sync.RWMutex
|
||||||
|
list BlacklistMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMemBlacklist
|
||||||
|
func NewMemBlacklist() *MemBlacklist {
|
||||||
|
return &MemBlacklist{list: make(BlacklistMap)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds a new token signature with expiration time to the blacklist
|
// Add adds a new token signature with expiration time to the blacklist
|
||||||
func (mb MapBlacklist) Add(sig string, exp int64) {
|
func (mb MemBlacklist) Add(sig string, exp int64) {
|
||||||
mb[sig] = exp
|
mb.Lock()
|
||||||
|
mb.list[sig] = exp
|
||||||
|
mb.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove deletes a token signature from the blacklist
|
// Remove deletes a token signature from the blacklist
|
||||||
func (mb MapBlacklist) Remove(sig string) {
|
func (mb MemBlacklist) Remove(sig string) {
|
||||||
delete(mb, sig)
|
mb.Lock()
|
||||||
|
delete(mb.list, sig)
|
||||||
|
mb.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check returns true if a token signature is blacklisted and false otherwise
|
// Check returns true if a token signature is blacklisted and false otherwise
|
||||||
func (mb MapBlacklist) Check(sig string) (ok bool) {
|
func (mb MemBlacklist) Check(sig string) (ok bool) {
|
||||||
_, ok = mb[sig]
|
mb.RLock()
|
||||||
|
_, ok = mb.list[sig]
|
||||||
|
mb.RUnlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map returns the blacklist in the form of a iterable map structure for cleanup
|
// Map returns the blacklist in the form of a iterable map structure for cleanup
|
||||||
func (mb MapBlacklist) Map() MapBlacklist {
|
func (mb MemBlacklist) Map() (list BlacklistMap) {
|
||||||
return mb
|
list = make(BlacklistMap)
|
||||||
|
mb.RLock()
|
||||||
|
for k, v := range mb.list {
|
||||||
|
list[k] = v
|
||||||
|
}
|
||||||
|
mb.RUnlock()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
37
jwt.go
37
jwt.go
|
|
@ -9,8 +9,8 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -19,11 +19,15 @@ const (
|
||||||
Header = "Authorization"
|
Header = "Authorization"
|
||||||
// DefaultExpiry is the default token expiration time
|
// DefaultExpiry is the default token expiration time
|
||||||
DefaultExpiry = time.Hour * 12
|
DefaultExpiry = time.Hour * 12
|
||||||
|
// KeySize is the secret key size
|
||||||
|
KeySize = 64
|
||||||
|
|
||||||
typ = "JWT"
|
typ = "JWT"
|
||||||
keySize = 64
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultSecretReader is the default secret key generator
|
||||||
|
var DefaultSecretReader = rand.Reader
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNoJWT = errors.New("not a json web token")
|
ErrNoJWT = errors.New("not a json web token")
|
||||||
ErrEmptyToken = errors.New("token is empty")
|
ErrEmptyToken = errors.New("token is empty")
|
||||||
|
|
@ -37,6 +41,7 @@ var (
|
||||||
ErrMissingTokenParts = errors.New("missing token parts")
|
ErrMissingTokenParts = errors.New("missing token parts")
|
||||||
ErrEmptySignature = errors.New("token signature is empty")
|
ErrEmptySignature = errors.New("token signature is empty")
|
||||||
ErrTokenIsNil = errors.New("token is nil")
|
ErrTokenIsNil = errors.New("token is nil")
|
||||||
|
ErrInvalidKeySize = errors.New("invalid secret key size")
|
||||||
)
|
)
|
||||||
|
|
||||||
// JWT represents the JSON Web Token signing and blacklisting infrastructure
|
// JWT represents the JSON Web Token signing and blacklisting infrastructure
|
||||||
|
|
@ -44,8 +49,6 @@ type JWT struct {
|
||||||
key []byte
|
key []byte
|
||||||
expiry time.Duration
|
expiry time.Duration
|
||||||
|
|
||||||
// protects list
|
|
||||||
sync.RWMutex
|
|
||||||
blacklist Blacklist
|
blacklist Blacklist
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
@ -54,12 +57,22 @@ type JWT struct {
|
||||||
// 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 Blacklist) *JWT {
|
func New(expiry time.Duration, blacklist Blacklist, secret io.Reader) (*JWT, error) {
|
||||||
if expiry <= 0 {
|
if expiry <= 0 {
|
||||||
expiry = DefaultExpiry
|
expiry = DefaultExpiry
|
||||||
}
|
}
|
||||||
key := make([]byte, keySize)
|
if secret == nil {
|
||||||
rand.Read(key)
|
secret = DefaultSecretReader
|
||||||
|
}
|
||||||
|
secret = io.LimitReader(secret, KeySize)
|
||||||
|
key := make([]byte, KeySize)
|
||||||
|
i, err := secret.Read(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("secret reader error: " + err.Error())
|
||||||
|
}
|
||||||
|
if i < KeySize {
|
||||||
|
return nil, ErrInvalidKeySize
|
||||||
|
}
|
||||||
jwt := &JWT{
|
jwt := &JWT{
|
||||||
key: key,
|
key: key,
|
||||||
expiry: expiry,
|
expiry: expiry,
|
||||||
|
|
@ -69,7 +82,7 @@ func New(expiry time.Duration, blacklist Blacklist) *JWT {
|
||||||
jwt.done = make(chan struct{})
|
jwt.done = make(chan struct{})
|
||||||
go jwt.clean()
|
go jwt.clean()
|
||||||
}
|
}
|
||||||
return jwt
|
return jwt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jwt *JWT) sum(token string, h Hash) []byte {
|
func (jwt *JWT) sum(token string, h Hash) []byte {
|
||||||
|
|
@ -107,8 +120,6 @@ func (jwt *JWT) Invalidate(t *Token) error {
|
||||||
if err := jwt.blacklisted(t.Sig()); err != nil {
|
if err := jwt.blacklisted(t.Sig()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
jwt.Lock()
|
|
||||||
defer jwt.Unlock()
|
|
||||||
jwt.blacklist.Add(t.Sig(), exp)
|
jwt.blacklist.Add(t.Sig(), exp)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -118,8 +129,6 @@ func (jwt *JWT) blacklisted(sig string) error {
|
||||||
if sig == "" {
|
if sig == "" {
|
||||||
return ErrEmptySignature
|
return ErrEmptySignature
|
||||||
}
|
}
|
||||||
jwt.RLock()
|
|
||||||
defer jwt.RUnlock()
|
|
||||||
if jwt.blacklist.Check(sig) {
|
if jwt.blacklist.Check(sig) {
|
||||||
return ErrBlacklisted
|
return ErrBlacklisted
|
||||||
}
|
}
|
||||||
|
|
@ -137,13 +146,11 @@ func (jwt *JWT) clean() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
now := time.Now().UTC().Unix()
|
now := time.Now().UTC().Unix()
|
||||||
jwt.Lock()
|
|
||||||
for k, v := range jwt.blacklist.Map() {
|
for k, v := range jwt.blacklist.Map() {
|
||||||
if now > v {
|
if now > v {
|
||||||
jwt.blacklist.Remove(k)
|
jwt.blacklist.Remove(k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jwt.Unlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
29
jwt_test.go
29
jwt_test.go
|
|
@ -3,20 +3,24 @@
|
||||||
package jwt
|
package jwt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValidate(t *testing.T) {
|
func TestValidate(t *testing.T) {
|
||||||
jwt := New(time.Second, NewMapBlacklist())
|
jwt, err := New(time.Second, NewMemBlacklist(), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
token := NewToken(map[string]interface{}{
|
token := NewToken(map[string]interface{}{
|
||||||
"sub": "1234567890",
|
"sub": "1234567890",
|
||||||
"name": "John Doe",
|
"name": "John Doe",
|
||||||
"admin": true,
|
"admin": true,
|
||||||
"fizz": "buzz",
|
"fizz": "buzz",
|
||||||
}, nil)
|
}, nil)
|
||||||
err := jwt.Sign(token)
|
err = jwt.Sign(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
@ -57,14 +61,17 @@ func TestValidate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoBlacklist(t *testing.T) {
|
func TestNoBlacklist(t *testing.T) {
|
||||||
jwt := New(time.Second, nil)
|
jwt, err := New(time.Second, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
token := NewToken(map[string]interface{}{
|
token := NewToken(map[string]interface{}{
|
||||||
"sub": "1234567890",
|
"sub": "1234567890",
|
||||||
"name": "John Doe",
|
"name": "John Doe",
|
||||||
"admin": true,
|
"admin": true,
|
||||||
"fizz": "buzz",
|
"fizz": "buzz",
|
||||||
}, nil)
|
}, nil)
|
||||||
err := jwt.Sign(token)
|
err = jwt.Sign(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
@ -91,3 +98,17 @@ func TestNoBlacklist(t *testing.T) {
|
||||||
t.Error(errors.New("blacklisting should be disabled"))
|
t.Error(errors.New("blacklisting should be disabled"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEmptySecretReader(t *testing.T) {
|
||||||
|
_, err := New(time.Second, nil, new(bytes.Buffer))
|
||||||
|
if err == nil {
|
||||||
|
t.Error(errors.New("error should be secret reader error"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidSecretReader(t *testing.T) {
|
||||||
|
_, err := New(time.Second, nil, bytes.NewBufferString("123"))
|
||||||
|
if err != ErrInvalidKeySize {
|
||||||
|
t.Error(errors.New("error should be invalid key size"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue