From 0db1affc108fa207f47f4805c3e2cf7444664d8b Mon Sep 17 00:00:00 2001 From: "daniel.w" Date: Tue, 27 Aug 2024 13:35:08 +0800 Subject: [PATCH] add order data --- jwt/claims.go | 67 +++++++++++++++++++++++++++++++++++++++ jwt/define.go | 51 +++++++++++++++++++++++++++++ jwt/go.mod | 5 +++ jwt/token.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+) create mode 100644 jwt/claims.go create mode 100644 jwt/define.go create mode 100644 jwt/go.mod create mode 100644 jwt/token.go diff --git a/jwt/claims.go b/jwt/claims.go new file mode 100644 index 0000000..af9f1a7 --- /dev/null +++ b/jwt/claims.go @@ -0,0 +1,67 @@ +package jwt + +type DataClaims map[string]string + +const ( + idCode = "id" + roleCode = "role" + deviceIdCode = "device_id" + scopeCode = "scope" + uidCode = "uid" +) + +// ============ 使用具體的 setter ============ + +// Set 通用的 setter 方法 +func (c DataClaims) Set(key, value string) { + c[key] = value +} + +func (c DataClaims) SetID(id string) { + c.Set(idCode, id) +} + +func (c DataClaims) SetRole(role string) { + c.Set(roleCode, role) +} + +func (c DataClaims) SetDeviceID(deviceID string) { + c.Set(deviceIdCode, deviceID) +} + +func (c DataClaims) SetScope(scope string) { + c.Set(scopeCode, scope) +} + +func (c DataClaims) SetUID(uid string) { + c.Set(uidCode, uid) +} + +// ============ 使用具體的 getter ============ + +func (c DataClaims) Get(key string) string { + if val, ok := c[key]; ok { + return val + } + return "" +} + +func (c DataClaims) Scope() { + c.Get(scopeCode) +} + +func (c DataClaims) Role() string { + return c.Get(roleCode) +} + +func (c DataClaims) ID() string { + return c.Get(idCode) +} + +func (c DataClaims) DeviceID() string { + return c.Get(deviceIdCode) +} + +func (c DataClaims) UID() string { + return c.Get(uidCode) +} diff --git a/jwt/define.go b/jwt/define.go new file mode 100644 index 0000000..0d54cd1 --- /dev/null +++ b/jwt/define.go @@ -0,0 +1,51 @@ +package jwt + +import ( + "github.com/golang-jwt/jwt/v4" + "time" +) + +type Token struct { + ID string `json:"id"` + UID string `json:"uid"` + DeviceID string `json:"device_id"` + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + AccessCreateAt time.Time `json:"access_create_at"` + RefreshToken string `json:"refresh_token"` + RefreshExpiresIn int `json:"refresh_expires_in"` + RefreshCreateAt time.Time `json:"refresh_create_at"` +} + +func (t *Token) AccessTokenExpires() time.Duration { + return time.Duration(t.ExpiresIn) * time.Second +} + +func (t *Token) RefreshTokenExpires() time.Duration { + return time.Duration(t.RefreshExpiresIn) * time.Second +} + +func (t *Token) RefreshTokenExpiresUnix() int64 { + return time.Now().Add(t.RefreshTokenExpires()).Unix() +} + +func (t *Token) IsExpires() bool { + return t.AccessCreateAt.Add(t.AccessTokenExpires()).Before(time.Now()) +} + +func (t *Token) RedisExpiredSec() int64 { + sec := time.Unix(int64(t.ExpiresIn), 0).Sub(time.Now().UTC()) + + return int64(sec.Seconds()) +} + +func (t *Token) RedisRefreshExpiredSec() int64 { + sec := time.Unix(int64(t.RefreshExpiresIn), 0).Sub(time.Now().UTC()) + + return int64(sec.Seconds()) +} + +type Claims struct { + jwt.RegisteredClaims + Data interface{} `json:"data"` +} diff --git a/jwt/go.mod b/jwt/go.mod new file mode 100644 index 0000000..ebde9c2 --- /dev/null +++ b/jwt/go.mod @@ -0,0 +1,5 @@ +module code.30cm.net/digimon/library-go/jwt + +go 1.22.3 + +require github.com/golang-jwt/jwt/v4 v4.5.0 diff --git a/jwt/token.go b/jwt/token.go new file mode 100644 index 0000000..049e3a5 --- /dev/null +++ b/jwt/token.go @@ -0,0 +1,88 @@ +package jwt + +import ( + "fmt" + "github.com/golang-jwt/jwt/v4" + "time" +) + +func GenerateAccessToken(token Token, data any, sign string, issuer string) (string, error) { + claim := Claims{ + Data: data, + RegisteredClaims: jwt.RegisteredClaims{ + ID: token.ID, + ExpiresAt: jwt.NewNumericDate(time.Unix(int64(token.ExpiresIn), 0)), + Issuer: issuer, + }, + } + + accessToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claim). + SignedString([]byte(sign)) + if err != nil { + return "", err + } + + return accessToken, nil +} + +func ParseToken(accessToken string, secret string, validate bool) (jwt.MapClaims, error) { + // 跳過驗證的解析 + var token *jwt.Token + var err error + + if validate { + token, err = jwt.Parse(accessToken, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("token unexpected signing method: %v", token.Header["alg"]) + } + return []byte(secret), nil + }) + if err != nil { + return jwt.MapClaims{}, err + } + } else { + parser := jwt.NewParser(jwt.WithoutClaimsValidation()) + token, err = parser.Parse(accessToken, func(token *jwt.Token) (interface{}, error) { + return []byte(secret), nil + }) + if err != nil { + return jwt.MapClaims{}, err + } + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok && token.Valid { + return jwt.MapClaims{}, fmt.Errorf("token valid error") + } + + return claims, nil +} + +func ParseClaims(accessToken string, secret string, validate bool) (DataClaims, error) { + claimMap, err := ParseToken(accessToken, secret, validate) + if err != nil { + return DataClaims{}, err + } + + claimsData, ok := claimMap["data"].(map[string]any) + if ok { + return convertMap(claimsData), nil + } + + return DataClaims{}, fmt.Errorf("get data from claim map error") +} + +func convertMap(input map[string]interface{}) map[string]string { + output := make(map[string]string) + for key, value := range input { + switch v := value.(type) { + case string: + output[key] = v + case fmt.Stringer: + output[key] = v.String() + default: + output[key] = fmt.Sprintf("%v", value) + } + } + return output +}