add: user auth accomplished
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
type TokenPayload struct {
|
||||
UserId string
|
||||
UserId int64
|
||||
IsAdmin bool
|
||||
}
|
||||
|
||||
@@ -33,6 +34,7 @@ var (
|
||||
errInvalidToken = errors.New("invalid token claims")
|
||||
errTokenNotInCache = errors.New("token not found in cache")
|
||||
errNoRedisClient = errors.New("redis client not configured")
|
||||
errInvalidUserID = errors.New("invalid user id")
|
||||
// errExpiredToken = errors.New("token expired")
|
||||
)
|
||||
|
||||
@@ -74,8 +76,7 @@ func (m *JwtManager) New(ctx context.Context, payload *TokenPayload) (string, er
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 存储 token 到 Redis,TTL 为 30 天
|
||||
userKey := tokenCachePrefixUser + payload.UserId
|
||||
userKey := tokenCachePrefixUser + strconv.FormatInt(claims.UserId, 10)
|
||||
tokenKey := tokenCachePrefixToken + tokenString
|
||||
|
||||
tokenData, _ := json.Marshal(payload)
|
||||
@@ -105,12 +106,12 @@ func (m *JwtManager) Valid(ctx context.Context, tokenString string) (*TokenPaylo
|
||||
// 检查 token 是否在 Redis 中
|
||||
tokenKey := tokenCachePrefixToken + tokenString
|
||||
tokenData, err := m.redisCluster.Get(ctx, tokenKey).Result()
|
||||
if err != nil && err != redis.Nil {
|
||||
if err != nil && !errors.Is(err, redis.Nil) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var payload TokenPayload
|
||||
if err == redis.Nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return nil, errTokenNotInCache
|
||||
}
|
||||
|
||||
@@ -125,6 +126,20 @@ func (m *JwtManager) Valid(ctx context.Context, tokenString string) (*TokenPaylo
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||
if _, renewErr := m.Renew(ctx, tokenString); renewErr != nil {
|
||||
return nil, renewErr
|
||||
}
|
||||
|
||||
if token != nil {
|
||||
if claims, ok := token.Claims.(*Claims); ok {
|
||||
return &claims.TokenPayload, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -146,7 +161,7 @@ func (m *JwtManager) Renew(ctx context.Context, tokenString string) (string, err
|
||||
tokenKey := tokenCachePrefixToken + tokenString
|
||||
tokenData, err := m.redisCluster.Get(ctx, tokenKey).Result()
|
||||
if err != nil {
|
||||
if err == redis.Nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return "", errTokenNotInCache
|
||||
}
|
||||
return "", err
|
||||
@@ -159,15 +174,15 @@ func (m *JwtManager) Renew(ctx context.Context, tokenString string) (string, err
|
||||
}
|
||||
|
||||
// 删除旧 token 记录
|
||||
userKey := tokenCachePrefixUser + payload.UserId
|
||||
userKey := tokenCachePrefixUser + strconv.FormatInt(payload.UserId, 10)
|
||||
m.redisCluster.Del(ctx, tokenKey, userKey)
|
||||
|
||||
// 生成新 token
|
||||
return m.New(ctx, &payload)
|
||||
}
|
||||
|
||||
// extract payload from token without validating expiration (used for auto-renewal)
|
||||
func (m *JwtManager) Extract(ctx context.Context, tokenString string) (*TokenPayload, error) {
|
||||
// Extract payload from token without validating expiration (used for auto-renewal)
|
||||
func (m *JwtManager) Extract(_ context.Context, tokenString string) (*TokenPayload, error) {
|
||||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(m.secretKey), nil
|
||||
})
|
||||
@@ -184,7 +199,7 @@ func (m *JwtManager) Extract(ctx context.Context, tokenString string) (*TokenPay
|
||||
return &claims.TokenPayload, nil
|
||||
}
|
||||
|
||||
// check if token exists in Redis (i.e. is valid and not revoked)
|
||||
// Exists check if token exists in Redis (i.e. is valid and not revoked)
|
||||
func (m *JwtManager) Exists(ctx context.Context, tokenString string) (bool, error) {
|
||||
if m.redisCluster == nil {
|
||||
return false, errNoRedisClient
|
||||
@@ -199,12 +214,12 @@ func (m *JwtManager) Exists(ctx context.Context, tokenString string) (bool, erro
|
||||
return exists > 0, nil
|
||||
}
|
||||
|
||||
// extract payload from JWT claims
|
||||
// ClaimsToPayload extract payload from JWT claims
|
||||
func (m *JwtManager) ClaimsToPayload(claims *Claims) *TokenPayload {
|
||||
return &claims.TokenPayload
|
||||
}
|
||||
|
||||
// revoke token by deleting both user -> token and token -> payload keys from Redis
|
||||
// Revoke revoke token by deleting both user -> token and token -> payload keys from Redis
|
||||
func (m *JwtManager) Revoke(ctx context.Context, tokenString string) error {
|
||||
if m.redisCluster == nil {
|
||||
return errNoRedisClient
|
||||
@@ -215,7 +230,7 @@ func (m *JwtManager) Revoke(ctx context.Context, tokenString string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
userKey := tokenCachePrefixUser + payload.UserId
|
||||
userKey := tokenCachePrefixUser + strconv.FormatInt(payload.UserId, 10)
|
||||
tokenKey := tokenCachePrefixToken + tokenString
|
||||
|
||||
pipe := m.redisCluster.Pipeline()
|
||||
@@ -225,19 +240,48 @@ func (m *JwtManager) Revoke(ctx context.Context, tokenString string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *JwtManager) GetUserToken(ctx context.Context, userID string) (string, error) {
|
||||
func (m *JwtManager) GetUserToken(ctx context.Context, userID int64) (string, error) {
|
||||
if m.redisCluster == nil {
|
||||
return "", errNoRedisClient
|
||||
}
|
||||
//userID, err := strconv.FormatInt(userID, 10)
|
||||
id := strconv.FormatInt(userID, 10)
|
||||
|
||||
userKey := tokenCachePrefixUser + userID
|
||||
userKey := tokenCachePrefixUser + id
|
||||
token, err := m.redisCluster.Get(ctx, userKey).Result()
|
||||
if err != nil {
|
||||
if err == redis.Nil {
|
||||
return "", fmt.Errorf("user %s has no token", userID)
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return "", fmt.Errorf("user %v has no token", userID)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// Logout 按用户登出:删除 user->token 和 token->payload 两类缓存数据
|
||||
func (m *JwtManager) Logout(ctx context.Context, userID int64) error {
|
||||
if m.redisCluster == nil {
|
||||
return errNoRedisClient
|
||||
}
|
||||
|
||||
if userID <= 0 {
|
||||
return errInvalidUserID
|
||||
}
|
||||
|
||||
userKey := tokenCachePrefixUser + strconv.FormatInt(userID, 10)
|
||||
tokenString, err := m.redisCluster.Get(ctx, userKey).Result()
|
||||
if err != nil && !errors.Is(err, redis.Nil) {
|
||||
return err
|
||||
}
|
||||
|
||||
pipe := m.redisCluster.Pipeline()
|
||||
pipe.Del(ctx, userKey)
|
||||
if !errors.Is(err, redis.Nil) && tokenString != "" {
|
||||
tokenKey := tokenCachePrefixToken + tokenString
|
||||
pipe.Del(ctx, tokenKey)
|
||||
}
|
||||
|
||||
_, execErr := pipe.Exec(ctx)
|
||||
return execErr
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user