feat/rest
parent
216d62b326
commit
c7ec5eb105
15 changed files with 343 additions and 250 deletions
@ -1,4 +1,4 @@ |
||||
package jwt |
||||
package token |
||||
|
||||
import ( |
||||
"bytes" |
@ -0,0 +1,48 @@ |
||||
package token |
||||
|
||||
import ( |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
type ( |
||||
Store interface { |
||||
Set(key string, val any, duration time.Duration) error |
||||
Get(key string) (any, bool) |
||||
} |
||||
node struct { |
||||
data any |
||||
expired time.Time |
||||
} |
||||
defaultStore struct { |
||||
idMap map[string]*node |
||||
lock sync.RWMutex |
||||
} |
||||
) |
||||
|
||||
func (d *defaultStore) Get(key string) (any, bool) { |
||||
d.lock.RLock() |
||||
defer d.lock.RUnlock() |
||||
if v, ok := d.idMap[key]; ok { |
||||
if time.Now().After(v.expired) { |
||||
return v.data, true |
||||
} |
||||
} |
||||
return nil, false |
||||
} |
||||
|
||||
func (d *defaultStore) Set(key string, val any, duration time.Duration) error { |
||||
d.lock.Lock() |
||||
defer d.lock.Unlock() |
||||
d.idMap[key] = &node{ |
||||
data: val, |
||||
expired: time.Now().Add(duration), |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func newStore() Store { |
||||
return &defaultStore{ |
||||
idMap: make(map[string]*node), |
||||
} |
||||
} |
@ -0,0 +1,157 @@ |
||||
package token |
||||
|
||||
import ( |
||||
"fmt" |
||||
"git.diulo.com/mogfee/kit/errors" |
||||
"git.diulo.com/mogfee/kit/internal/xuuid" |
||||
"github.com/golang-jwt/jwt/v5" |
||||
"time" |
||||
) |
||||
|
||||
// iss : jwt签发者
|
||||
// sub : 主题名称
|
||||
// aud : 面向的用户,一般都是通过ip或者域名控制
|
||||
// exp : jwt的有效时间(过期时间),这个有效时间必须要大于签发时间,对于交互接口来说,建议是预设5秒
|
||||
// nbf : 在什么时候jwt开始生效(在此之前不可用)
|
||||
// iat : jwt的签发时间
|
||||
// jti : 唯一标识,主要用来回避被重复使用攻击
|
||||
|
||||
type ( |
||||
Options func(opt *tokenServer) |
||||
|
||||
tokenServer struct { |
||||
prefix string |
||||
key string |
||||
expire time.Duration |
||||
generateKey func() string |
||||
store Store |
||||
} |
||||
) |
||||
|
||||
var ( |
||||
ErrTokenExpired = errors.Unauthorized("TOKEN_EXPIRED", "") |
||||
ErrTokenError = errors.Unauthorized("TOKEN_ERROR", "") |
||||
) |
||||
|
||||
func WithPrefix(prefix string) Options { |
||||
return func(opt *tokenServer) { |
||||
opt.prefix = prefix |
||||
} |
||||
} |
||||
func WithExpire(expire time.Duration) Options { |
||||
return func(opt *tokenServer) { |
||||
opt.expire = expire |
||||
} |
||||
} |
||||
func WithGenerateKey(fn func() string) Options { |
||||
return func(opt *tokenServer) { |
||||
opt.generateKey = fn |
||||
} |
||||
} |
||||
func WithStore(store Store) Options { |
||||
return func(opt *tokenServer) { |
||||
opt.store = store |
||||
} |
||||
} |
||||
func NewTokenService(key string, opts ...Options) *tokenServer { |
||||
srv := &tokenServer{ |
||||
key: key, |
||||
generateKey: func() string { |
||||
return xuuid.UUID() |
||||
}, |
||||
store: newStore(), |
||||
prefix: "token:", |
||||
} |
||||
for _, opt := range opts { |
||||
opt(srv) |
||||
} |
||||
return srv |
||||
} |
||||
func (t *tokenServer) getStoreKey(uniqId string) string { |
||||
return fmt.Sprintf("%s%s", t.prefix, uniqId) |
||||
} |
||||
func (t *tokenServer) Generate(info any) (string, error) { |
||||
token, uniqId, err := t.generate() |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
if err = t.store.Set(t.getStoreKey(uniqId), info, t.expire); err != nil { |
||||
return "", err |
||||
} |
||||
return token, nil |
||||
} |
||||
|
||||
func (t *tokenServer) generate() (token string, uniqId string, err error) { |
||||
uniqId = t.generateKey() |
||||
var tokenStr string |
||||
ctime := time.Now().Unix() |
||||
tokenStr, err = jwt.NewWithClaims(jwt.SigningMethodHS256, &jwt.MapClaims{ |
||||
//jwt签发者
|
||||
//"iss": "auth.diulo.com",
|
||||
//主题名称
|
||||
//"sub": "www.diulo.com",
|
||||
// 面向的用户,一般都是通过ip或者域名控制
|
||||
//"aud": "api.diulo.com",
|
||||
"aud": fmt.Sprint(uniqId), |
||||
//jwt的有效时间(过期时间),这个有效时间必须要大于签发时间,对于交互接口来说,建议是预设5秒
|
||||
//"exp": ctime + 7200,
|
||||
//在什么时候jwt开始生效(在此之前不可用)
|
||||
"nbf": ctime, |
||||
//jwt的签发时间
|
||||
"iat": ctime, |
||||
//唯一标识,主要用来回避被重复使用攻击
|
||||
//"jti": fmt.Sprint(uniqId),
|
||||
}).SignedString([]byte(t.key)) |
||||
if err != nil { |
||||
return |
||||
} |
||||
token, err = Encrypt(tokenStr, []byte(t.key), t.key) |
||||
return |
||||
} |
||||
|
||||
func (t *tokenServer) Refresh(token string) error { |
||||
uniqId, info, err := t.parse(token) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return t.store.Set(uniqId, info, t.expire) |
||||
} |
||||
func (t *tokenServer) Parse(tokenStr string) (any, error) { |
||||
_, info, err := t.parse(tokenStr) |
||||
return info, err |
||||
} |
||||
func (t *tokenServer) parse(tokenStr string) (string, any, error) { |
||||
var res any |
||||
if tokenStr == "" { |
||||
return "", res, nil |
||||
} |
||||
str, err := Decrypt(tokenStr, []byte(t.key), t.key) |
||||
if err != nil { |
||||
return "", res, ErrTokenError |
||||
} |
||||
token, err := jwt.Parse(str, func(token *jwt.Token) (interface{}, error) { |
||||
return []byte(t.key), nil |
||||
}) |
||||
if err != nil { |
||||
if errors.Is(err, jwt.ErrTokenExpired) { |
||||
return "", res, ErrTokenExpired |
||||
} |
||||
return "", res, ErrTokenError |
||||
} |
||||
|
||||
if token.Valid { |
||||
audience, err := token.Claims.GetAudience() |
||||
if err != nil { |
||||
return "", res, ErrTokenError |
||||
} |
||||
if len(audience) > 0 { |
||||
if info, ok := t.store.Get(audience[0]); ok { |
||||
return audience[0], info, nil |
||||
} |
||||
} |
||||
return "", res, nil |
||||
} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) { |
||||
return "", res, ErrTokenExpired |
||||
} |
||||
return "", res, ErrTokenError |
||||
} |
@ -0,0 +1,24 @@ |
||||
package token |
||||
|
||||
import ( |
||||
"fmt" |
||||
"testing" |
||||
) |
||||
|
||||
func TestGenerateToken(t *testing.T) { |
||||
srv := NewTokenService("sfe023f_9fd&fwfl") |
||||
token, err := srv.Generate(map[string]any{ |
||||
"userId": 111, |
||||
}) |
||||
if err != nil { |
||||
t.Error(err) |
||||
return |
||||
} |
||||
|
||||
if err := srv.Refresh(token); err != nil { |
||||
t.Error(err) |
||||
return |
||||
} |
||||
fmt.Println(srv.Parse(token)) |
||||
|
||||
} |
@ -1,56 +0,0 @@ |
||||
package jwt |
||||
|
||||
import ( |
||||
"context" |
||||
"git.diulo.com/mogfee/kit/errors" |
||||
"strings" |
||||
) |
||||
|
||||
type JwtDefault struct { |
||||
} |
||||
|
||||
//func (j *JwtDefault) GetToken(ctx context.Context, key string) (tokenStr string) {
|
||||
// arr := strings.Split(key, ":")
|
||||
// if len(arr) != 2 {
|
||||
// return ""
|
||||
// }
|
||||
// switch arr[0] {
|
||||
// case "cookie":
|
||||
// if tr, ok := transport.FromServerContext(ctx); ok {
|
||||
// if tr1, ok := tr.(http.Transporter); ok {
|
||||
// if co, err := tr1.Request().Cookie(arr[1]); err == nil {
|
||||
// return co.Value
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// case "header":
|
||||
// if tr, ok := transport.FromServerContext(ctx); ok {
|
||||
// return tr.RequestHeader().Get(arr[1])
|
||||
// }
|
||||
// case "query":
|
||||
// if tr, ok := transport.FromServerContext(ctx); ok {
|
||||
// if ht, ok := tr.(http.Transporter); ok {
|
||||
// return ht.Request().URL.Query().Get(arr[1])
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return ""
|
||||
//}
|
||||
|
||||
func (j *JwtDefault) ParseToken(ctx context.Context, key string, token string) (*UserInfo, error) { |
||||
return Parse(key, token) |
||||
} |
||||
|
||||
func (j *JwtDefault) Validate(ctx context.Context, permission string, permissions []string) error { |
||||
allowPers := strings.Split(permission, "|") |
||||
allowMap := make(map[string]bool, len(allowPers)) |
||||
for _, v := range allowPers { |
||||
allowMap[v] = true |
||||
} |
||||
for _, v := range permissions { |
||||
if allowMap[v] { |
||||
return nil |
||||
} |
||||
} |
||||
return errors.Forbidden("TOKEN_PERMISSION_BAD", "权限不足") |
||||
} |
@ -1,16 +0,0 @@ |
||||
package jwt |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"strings" |
||||
"testing" |
||||
) |
||||
|
||||
func TestA(t *testing.T) { |
||||
str := `fS6HZv4HoMo+OnaNsLuM7O4Kx9L4UrM2TdnJB/J5qK75mJiEsuTyELYxaZXkFMnqre4A1B/pzzpFNKwB4k2M2tBcrSAakYU4I+cOFRcy7ANJdjis529x8Du89Mh16ZAViCHNVs+Rp6qHFK/hjdLVEkFY7Ws2t++cu4rF+DQacs9yccoh2wTCVweNOIrGz0fOaEgVroprhP4xvvfVUj293ovCv9T+mF9qHJYmswEMOu1+UMLLf3EyBVXgxnNrHzvX` |
||||
str = strings.ReplaceAll(str, " ", "+") |
||||
|
||||
tt := &JwtDefault{} |
||||
fmt.Println(tt.ParseToken(context.Background(), "sfe023f_9fd&fwfl", str)) |
||||
} |
@ -1,87 +0,0 @@ |
||||
package jwt |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"git.diulo.com/mogfee/kit/errors" |
||||
"git.diulo.com/mogfee/kit/internal/xuuid" |
||||
"github.com/golang-jwt/jwt/v5" |
||||
"time" |
||||
) |
||||
|
||||
// iss : jwt签发者
|
||||
// sub : 主题名称
|
||||
// aud : 面向的用户,一般都是通过ip或者域名控制
|
||||
// exp : jwt的有效时间(过期时间),这个有效时间必须要大于签发时间,对于交互接口来说,建议是预设5秒
|
||||
// nbf : 在什么时候jwt开始生效(在此之前不可用)
|
||||
// iat : jwt的签发时间
|
||||
// jti : 唯一标识,主要用来回避被重复使用攻击
|
||||
|
||||
type UserInfo struct { |
||||
UserId string |
||||
UserName string |
||||
UserType string |
||||
Permissions []string |
||||
UniqueId string |
||||
} |
||||
|
||||
func GetToken(key string, info *UserInfo) (token string, uniqId string, err error) { |
||||
uniqId = xuuid.UUID() |
||||
info.UniqueId = uniqId |
||||
var tokenStr string |
||||
ctime := time.Now().Unix() |
||||
tokenStr, err = jwt.NewWithClaims(jwt.SigningMethodHS256, &jwt.MapClaims{ |
||||
//jwt签发者
|
||||
//"iss": "auth.diulo.com",
|
||||
//主题名称
|
||||
//"sub": "www.diulo.com",
|
||||
// 面向的用户,一般都是通过ip或者域名控制
|
||||
//"aud": "api.diulo.com",
|
||||
//jwt的有效时间(过期时间),这个有效时间必须要大于签发时间,对于交互接口来说,建议是预设5秒
|
||||
"exp": ctime + 7200, |
||||
//在什么时候jwt开始生效(在此之前不可用)
|
||||
"nbf": ctime, |
||||
//jwt的签发时间
|
||||
"iat": ctime, |
||||
//唯一标识,主要用来回避被重复使用攻击
|
||||
"jti": uniqId, |
||||
"info": info, |
||||
}).SignedString([]byte(key)) |
||||
if err != nil { |
||||
return |
||||
} |
||||
token, err = Encrypt(tokenStr, []byte(key), key) |
||||
return |
||||
} |
||||
|
||||
func Parse(key string, tokenStr string) (*UserInfo, error) { |
||||
if tokenStr == "" { |
||||
return &UserInfo{}, nil |
||||
} |
||||
str, err := Decrypt(tokenStr, []byte(key), key) |
||||
if err != nil { |
||||
return nil, errors.Unauthorized("TOKEN_ERROR", err.Error()) |
||||
} |
||||
token, err := jwt.Parse(str, func(token *jwt.Token) (interface{}, error) { |
||||
return []byte(key), nil |
||||
}) |
||||
if err != nil { |
||||
if errors.Is(err, jwt.ErrTokenExpired) { |
||||
return nil, errors.Unauthorized("TOKEN_EXPIRED", "") |
||||
} |
||||
return nil, errors.Unauthorized("TOKEN_ERROR", err.Error()) |
||||
} |
||||
|
||||
if token.Valid { |
||||
row := struct { |
||||
Info *UserInfo |
||||
}{} |
||||
b, _ := json.Marshal(token.Claims) |
||||
if err = json.Unmarshal(b, &row); err != nil { |
||||
return nil, errors.Unauthorized("TOKEN_ERROR", err.Error()) |
||||
} |
||||
return row.Info, nil |
||||
} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) { |
||||
return nil, errors.Unauthorized("TOKEN_EXPIRED", "") |
||||
} |
||||
return nil, errors.Unauthorized("TOKEN_ERROR", "") |
||||
} |
Loading…
Reference in new issue