|
|
|
package token
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/golang-jwt/jwt/v4"
|
|
|
|
"github.com/golang-jwt/jwt/v4/request"
|
|
|
|
"github.com/tal-tech/go-zero/core/timex"
|
|
|
|
)
|
|
|
|
|
|
|
|
const claimHistoryResetDuration = time.Hour * 24
|
|
|
|
|
|
|
|
type (
|
|
|
|
// ParseOption defines the method to customize a TokenParser.
|
|
|
|
ParseOption func(parser *TokenParser)
|
|
|
|
|
|
|
|
// A TokenParser is used to parse tokens.
|
|
|
|
TokenParser struct {
|
|
|
|
resetTime time.Duration
|
|
|
|
resetDuration time.Duration
|
|
|
|
history sync.Map
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// NewTokenParser returns a TokenParser.
|
|
|
|
func NewTokenParser(opts ...ParseOption) *TokenParser {
|
|
|
|
parser := &TokenParser{
|
|
|
|
resetTime: timex.Now(),
|
|
|
|
resetDuration: claimHistoryResetDuration,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, opt := range opts {
|
|
|
|
opt(parser)
|
|
|
|
}
|
|
|
|
|
|
|
|
return parser
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseToken parses token from given r, with passed in secret and prevSecret.
|
|
|
|
func (tp *TokenParser) ParseToken(r *http.Request, secret, prevSecret string) (*jwt.Token, error) {
|
|
|
|
var token *jwt.Token
|
|
|
|
var err error
|
|
|
|
|
|
|
|
if len(prevSecret) > 0 {
|
|
|
|
count := tp.loadCount(secret)
|
|
|
|
prevCount := tp.loadCount(prevSecret)
|
|
|
|
|
|
|
|
var first, second string
|
|
|
|
if count > prevCount {
|
|
|
|
first = secret
|
|
|
|
second = prevSecret
|
|
|
|
} else {
|
|
|
|
first = prevSecret
|
|
|
|
second = secret
|
|
|
|
}
|
|
|
|
|
|
|
|
token, err = tp.doParseToken(r, first)
|
|
|
|
if err != nil {
|
|
|
|
token, err = tp.doParseToken(r, second)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tp.incrementCount(second)
|
|
|
|
} else {
|
|
|
|
tp.incrementCount(first)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
token, err = tp.doParseToken(r, secret)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return token, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tp *TokenParser) doParseToken(r *http.Request, secret string) (*jwt.Token, error) {
|
|
|
|
return request.ParseFromRequest(r, request.AuthorizationHeaderExtractor,
|
|
|
|
func(token *jwt.Token) (interface{}, error) {
|
|
|
|
return []byte(secret), nil
|
|
|
|
}, request.WithParser(newParser()))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tp *TokenParser) incrementCount(secret string) {
|
|
|
|
now := timex.Now()
|
|
|
|
if tp.resetTime+tp.resetDuration < now {
|
|
|
|
tp.history.Range(func(key, value interface{}) bool {
|
|
|
|
tp.history.Delete(key)
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
value, ok := tp.history.Load(secret)
|
|
|
|
if ok {
|
|
|
|
atomic.AddUint64(value.(*uint64), 1)
|
|
|
|
} else {
|
|
|
|
var count uint64 = 1
|
|
|
|
tp.history.Store(secret, &count)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tp *TokenParser) loadCount(secret string) uint64 {
|
|
|
|
value, ok := tp.history.Load(secret)
|
|
|
|
if ok {
|
|
|
|
return *value.(*uint64)
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithResetDuration returns a func to customize a TokenParser with reset duration.
|
|
|
|
func WithResetDuration(duration time.Duration) ParseOption {
|
|
|
|
return func(parser *TokenParser) {
|
|
|
|
parser.resetDuration = duration
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newParser() *jwt.Parser {
|
|
|
|
return &jwt.Parser{
|
|
|
|
UseJSONNumber: true,
|
|
|
|
}
|
|
|
|
}
|