feat/rest
parent
216d62b326
commit
c7ec5eb105
15 changed files with 343 additions and 250 deletions
@ -1,4 +1,4 @@ |
|||||||
package jwt |
package token |
||||||
|
|
||||||
import ( |
import ( |
||||||
"bytes" |
"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