package authutil import ( "context" "fmt" "github.com/go-micro/plugins/v4/auth/jwt" "github.com/go-redsync/redsync/v4" "github.com/go-redsync/redsync/v4/redis/goredis/v9" "github.com/google/uuid" goredislib "github.com/redis/go-redis/v9" "github.com/sirupsen/logrus" "go-micro.dev/v4/auth" "os" "os/signal" "sghgogs.com/micro/common" "sghgogs.com/micro/common/errorcode" "strconv" "strings" "sync" "syscall" "time" ) var ( JWTAuthService *JWTAuth jwtAuthServiceOnce sync.Once ) const ( expiry = time.Second * time.Duration(24*3600) // expiry = time.Second * time.Duration(1*3600) ) type JWTAuth struct { Mu *redsync.Redsync Client *goredislib.Client Auth auth.Auth namespace string Enable bool } func NewJWTAuth(client *goredislib.Client, namespace string, enable bool) *JWTAuth { jwtAuthServiceOnce.Do(func() { newAuth := jwt.NewAuth( auth.Namespace(namespace), auth.PrivateKey("LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS3dJQkFBS0NBZ0VBOFNiSlA1WGJFaWRSbTViMnNOcExHbzJlV2ZVNU9KZTBpemdySHdEOEg3RjZQa1BkCi9SbDkvMXBNVjdNaU8zTEh3dGhIQzJCUllxcisxd0Zkb1pDR0JZckxhWHVYRnFLMHZ1WmhQcUUzYXpqdUlIUXUKMEJIL2xYUU1xeUVxRjVNSTJ6ZWpDNHpNenIxNU9OK2dFNEpuaXBqcC9DZGpPUEFEbUpHK0JKOXFlRS9RUGVtLwptVWRJVC9MYUY3a1F4eVlLNVZLbitOZ09Xek1sektBQXBDbjdUVEtCVWU4RlpHNldTWDdMVjBlTEdIc29pYnhsCm85akRqbFk1b0JPY3pmcWVOV0hLNUdYQjdRd3BMTmg5NDZQelpucW9hcFdVZStZL1JPaUhpekpUY3I1Wk1TTDUKd2xFcThoTmhtaG01Tk5lL08rR2dqQkROU2ZVaDA2K3E0bmdtYm1OWDVoODM4QmJqUmN5YzM2ZHd6NkpVK2R1bwpSdFFoZ2lZOTEwcFBmOWJhdVhXcXdVQ1VhNHFzSHpqS1IwTC9OMVhYQXlsQ0RqeWVnWnp6Y093MkNIOFNrZkZVCnJnTHJQYkVCOWVnY0drMzgrYnBLczNaNlJyNSt0bkQxQklQSUZHTGVJMFVPQzAreGlCdjBvenhJRE9GbldhOVUKVEdEeFV4OG9qOFZJZVJuV0RxNk1jMWlKcDhVeWNpQklUUnR3NGRabzcweG1mbmVJV3pyM0tTTmFoU29nSmRSMApsYVF6QXVQM2FpV1hJTXAyc2M4U2MrQmwrTGpYbUJveEJyYUJIaDlLa0pKRWNnQUZ3czJib2pDbEpPWXhvRi9YCmdGS1NzSW5IRHJIVk95V1BCZTNmYWRFYzc3YituYi9leE96cjFFcnhoR2c5akZtcmtPK3M0eEdodjZNQ0F3RUEKQVFLQ0FnRUFqUzc1Q2VvUlRRcUtBNzZaaFNiNGEzNVlKRENtcEpSazFsRTNKYnFzNFYxRnhXaDBjZmJYeG9VMgpSdTRRYjUrZWhsdWJGSFQ2a1BxdG9uRWhRVExjMUNmVE9WbHJOb3hocDVZM2ZyUmlQcnNnNXcwK1R3RUtrcFJUCnltanJQTXdQbGxCM2U0NmVaYmVXWGc3R3FFVmptMGcxVFRRK0tocVM4R0w3VGJlTFhRN1ZTem9ydTNCNVRKMVEKeEN6TVB0dnQ2eDYrU3JrcmhvZG1iT3VNRkpDam1TbWxmck9pZzQ4Zkc3NUpERHRObXpLWHBEUVJpYUNodFJhVQpQRHpmUTlTamhYdFFqdkZvWFFFT3BqdkZVRjR2WldNUWNQNUw1VklDM3JRSWp4MFNzQTN6S0FwakVUbjJHNjN2CktZby8zVWttbzhkUCtGRHA3NCs5a3pLNHFFaFJycEl3bEtiN0VOZWtDUXZqUFl1K3pyKzMyUXdQNTJ2L2FveWQKdjJJaUY3M2laTU1vZDhhYjJuQStyVEI2T0cvOVlSYk5kV21tay9VTi9jUHYrN214TmZ6Y1d1ZU1XcThxMXh4eAptNTNpR0NSQ29PQ1lDQk4zcUFkb1JwYW5xd3lCOUxrLzFCQjBHUld3MjgxK3VhNXNYRnZBVDBKeTVURnduMncvClU1MlJKWFlNOXVhMFBvd214b0RDUWRuNFZYVkdNZGdXaHN4aXhHRlYwOUZObWJJQWJaN0xaWGtkS1gzc1ZVbTcKWU1WYWIzVVo2bEhtdXYzT1NzcHNVUlRqN1hiRzZpaVVlaDU1aW91OENWbnRndWtFcnEzQTQwT05FVzhjNDBzOQphVTBGaSs4eWZpQTViaVZHLzF0bWlucUVERkhuQStnWk1xNEhlSkZxcWZxaEZKa1JwRGtDZ2dFQkFQeGR1NGNKCm5Da1duZDdPWFlHMVM3UDdkVWhRUzgwSDlteW9uZFc5bGFCQm84RWRPeTVTZzNOUmsxQ2pNZFZ1a3FMcjhJSnkKeStLWk15SVpvSlJvbllaMEtIUUVMR3ZLbzFOS2NLQ1FJbnYvWHVCdFJpRzBVb1pQNVkwN0RpRFBRQWpYUjlXUwpBc0EzMmQ1eEtFOC91Y3h0MjVQVzJFakNBUmtVeHQ5d0tKazN3bC9JdXVYRlExTDdDWjJsOVlFUjlHeWxUbzhNCmxXUEY3YndtUFV4UVNKaTNVS0FjTzZweTVUU1lkdWQ2aGpQeXJwSXByNU42VGpmTlRFWkVBeU9LbXVpOHVkUkoKMUg3T3RQVEhGZElKQjNrNEJnRDZtRE1HbjB2SXBLaDhZN3NtRUZBbFkvaXlCZjMvOHk5VHVMb1BycEdqR3RHbgp4Y2RpMHFud2p0SGFNbFVDZ2dFQkFQU2Z0dVFCQ2dTU2JLUSswUEFSR2VVeEQyTmlvZk1teENNTmdHUzJ5Ull3CjRGaGV4ZWkwMVJoaFk1NjE3UjduR1dzb0czd1RQa3dvRTJtbE1aQkoxeWEvUU9RRnQ3WG02OVl0RGh0T2FWbDgKL0o4dlVuSTBtWmxtT2pjTlRoYnVPZDlNSDlRdGxIRUMxMlhYdHJNb3Fsb0U2a05TT0pJalNxYm9wcDRXc1BqcApvZTZ0Nkdyd1RhOHBHeUJWWS90Mi85Ym5ORHVPVlpjODBaODdtY2gzcDNQclBqU3h5di9saGxYMFMwYUdHTkhTCk1XVjdUa25OaGo1TWlIRXFnZ1pZemtBWTkyd1JoVENnU1A2M0VNcitUWXFudXVuMXJHbndPYm95TDR2aFRpV0UKcU42UDNCTFlCZ1FpMllDTDludEJrOEl6RHZyd096dW5GVnhhZ0g5SVVoY0NnZ0VCQUwzQXlLa1BlOENWUmR6cQpzL284VkJDZmFSOFhhUGRnSGxTek1BSXZpNXEwNENqckRyMlV3MHZwTVdnM1hOZ0xUT3g5bFJpd3NrYk9SRmxHCmhhd3hRUWlBdkk0SE9WTlBTU0R1WHVNTG5USTQ0S0RFNlMrY2cxU0VMS2pWbDVqcDNFOEpkL1RJMVpLc0xBQUsKZTNHakM5UC9ZbE8xL21ndW4xNjVkWk01cFAwWHBPb2FaeFV2RHFFTktyekR0V1g0RngyOTZlUzdaSFJodFpCNwovQ2t1VUhlcmxrN2RDNnZzdWhTaTh2eTM3c0tPbmQ0K3c4cVM4czhZYVZxSDl3ZzVScUxxakp0bmJBUnc3alVDCm9KQ053M1hNdnc3clhaYzRTbnhVQUNMRGJNV2lLQy9xL1ZGWW9oTEs2WkpUVkJscWd5cjBSYzBRWmpDMlNJb0kKMjRwRWt3VUNnZ0VCQUpqb0FJVVNsVFY0WlVwaExXN3g4WkxPa01UWjBVdFFyd2NPR0hSYndPUUxGeUNGMVFWNQppejNiR2s4SmZyZHpVdk1sTmREZm9uQXVHTHhQa3VTVEUxWlg4L0xVRkJveXhyV3dvZ0cxaUtwME11QTV6em90CjROai9DbUtCQVkvWnh2anA5M2RFS21aZGxWQkdmeUFMeWpmTW5MWUovZXh5L09YSnhPUktZTUttSHg4M08zRWsKMWhvb0FwbTZabTIzMjRGME1iVU1ham5Idld2ZjhHZGJTNk5zcHd4L0dkbk1tYVMrdUJMVUhVMkNLbmc1bEIwVAp4OWJITmY0dXlPbTR0dXRmNzhCd1R5V3UreEdrVW0zZ2VZMnkvR1hqdDZyY2l1ajFGNzFDenZzcXFmZThTcDdJCnd6SHdxcTNzVHR5S2lCYTZuYUdEYWpNR1pKYSt4MVZJV204Q2dnRUJBT001ajFZR25Ba0pxR0czQWJSVDIvNUMKaVVxN0loYkswOGZsSGs5a2YwUlVjZWc0ZVlKY3dIRXJVaE4rdWQyLzE3MC81dDYra0JUdTVZOUg3bkpLREtESQpoeEg5SStyamNlVkR0RVNTRkluSXdDQ1lrOHhOUzZ0cHZMV1U5b0pibGFKMlZsalV2NGRFWGVQb0hkREh1Zk9ZClVLa0lsV2E3Uit1QzNEOHF5U1JrQnFLa3ZXZ1RxcFNmTVNkc1ZTeFIzU2Q4SVhFSHFjTDNUNEtMWGtYNEdEamYKMmZOSTFpZkx6ekhJMTN3Tk5IUTVRNU9SUC9pell2QzVzZkx4U2ZIUXJiMXJZVkpKWkI5ZjVBUjRmWFpHSVFsbApjMG8xd0JmZFlqMnZxVDlpR09IQnNSSTlSL2M2RzJQcUt3aFRpSzJVR2lmVFNEUVFuUkF6b2tpQVkrbE8vUjQ9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg=="), auth.PublicKey("LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUE4U2JKUDVYYkVpZFJtNWIyc05wTApHbzJlV2ZVNU9KZTBpemdySHdEOEg3RjZQa1BkL1JsOS8xcE1WN01pTzNMSHd0aEhDMkJSWXFyKzF3RmRvWkNHCkJZckxhWHVYRnFLMHZ1WmhQcUUzYXpqdUlIUXUwQkgvbFhRTXF5RXFGNU1JMnplakM0ek16cjE1T04rZ0U0Sm4KaXBqcC9DZGpPUEFEbUpHK0JKOXFlRS9RUGVtL21VZElUL0xhRjdrUXh5WUs1VktuK05nT1d6TWx6S0FBcENuNwpUVEtCVWU4RlpHNldTWDdMVjBlTEdIc29pYnhsbzlqRGpsWTVvQk9jemZxZU5XSEs1R1hCN1F3cExOaDk0NlB6ClpucW9hcFdVZStZL1JPaUhpekpUY3I1Wk1TTDV3bEVxOGhOaG1obTVOTmUvTytHZ2pCRE5TZlVoMDYrcTRuZ20KYm1OWDVoODM4QmJqUmN5YzM2ZHd6NkpVK2R1b1J0UWhnaVk5MTBwUGY5YmF1WFdxd1VDVWE0cXNIempLUjBMLwpOMVhYQXlsQ0RqeWVnWnp6Y093MkNIOFNrZkZVcmdMclBiRUI5ZWdjR2szOCticEtzM1o2UnI1K3RuRDFCSVBJCkZHTGVJMFVPQzAreGlCdjBvenhJRE9GbldhOVVUR0R4VXg4b2o4VkllUm5XRHE2TWMxaUpwOFV5Y2lCSVRSdHcKNGRabzcweG1mbmVJV3pyM0tTTmFoU29nSmRSMGxhUXpBdVAzYWlXWElNcDJzYzhTYytCbCtMalhtQm94QnJhQgpIaDlLa0pKRWNnQUZ3czJib2pDbEpPWXhvRi9YZ0ZLU3NJbkhEckhWT3lXUEJlM2ZhZEVjNzdiK25iL2V4T3pyCjFFcnhoR2c5akZtcmtPK3M0eEdodjZNQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo="), ) pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...) // Create an instance of redisync to be used to obtain a mutual exclusion // lock. rs := redsync.New(pool) JWTAuthService = &JWTAuth{ Mu: rs, Client: client, Auth: newAuth, namespace: namespace, Enable: enable, } handleSignals(client) }) return JWTAuthService } // GenerateToken 示例:生成令牌 func (svc *JWTAuth) GenerateToken(userID int64, provider, withType, secret string, scopes []string, md map[string]string) (*auth.Account, error) { var account *auth.Account lock := svc.Mu.NewMutex(fmt.Sprintf("generate-token-lock-%d", userID)) // 获取锁,保证原子性 if err := lock.Lock(); err != nil { return account, err } defer lock.Unlock() // 很重要 md["expiry"] = fmt.Sprintf("%d", time.Now().Add(expiry).Unix()) generate, err := svc.Auth.Generate( fmt.Sprintf("%d", userID), auth.WithType(withType), auth.WithProvider(provider), auth.WithScopes(strings.Join(scopes, ",")), auth.WithSecret(secret), auth.WithMetadata(md), ) if err != nil { code := common.FailedToGenerateTokenErrorCode return account, errorcode.New(svc.namespace, common.ErrorMessage[code], int32(code)) } return generate, nil } func (svc *JWTAuth) RefreshToken(token string) (*auth.Token, error) { var authToken *auth.Token lock := svc.Mu.NewMutex("refresh-generate-token-lock") // 获取锁,保证原子性 if err := lock.Lock(); err != nil { return authToken, err } defer lock.Unlock() inspect, err := svc.Auth.Inspect(token) if err != nil { return authToken, err } inspect.Metadata["expiry"] = fmt.Sprintf("%d", time.Now().Add(expiry).Unix()) generate, err := svc.Auth.Generate( inspect.Metadata["id"], auth.WithType("user"), auth.WithProvider("system"), auth.WithScopes(strings.Join(inspect.Scopes, ",")), auth.WithSecret(inspect.Metadata["password"]), auth.WithMetadata(inspect.Metadata), ) if err != nil { code := common.FailedToGenerateTokenErrorCode return authToken, errorcode.New(svc.namespace, common.ErrorMessage[code], int32(code)) } userId, _ := strconv.ParseInt(inspect.Metadata["id"], 10, 64) return svc.Token(userId, generate.Secret) } // Token 重新刷新token func (svc *JWTAuth) Token(userID int64, accessToken string) (*auth.Token, error) { var authToken *auth.Token lock := svc.Mu.NewMutex(fmt.Sprintf("token-lock-%d", userID)) // 获取锁,保证原子性 if err := lock.Lock(); err != nil { return authToken, err } defer lock.Unlock() return svc.Auth.Token( auth.WithExpiry(expiry), auth.WithCredentials(fmt.Sprintf("%d", userID), accessToken), auth.WithToken(accessToken), ) } // Inspect 检测token 有效期 func (svc *JWTAuth) Inspect(accessToken string) (*auth.Account, error) { var account *auth.Account lock := svc.Mu.NewMutex(fmt.Sprintf("inspect-lock-%d", uuid.New())) // 获取锁,保证原子性 if err := lock.Lock(); err != nil { return account, err } defer lock.Unlock() return svc.Auth.Inspect(accessToken) } // StoreToken 存储令牌到Redis func (svc *JWTAuth) StoreToken(ctx context.Context, ID int64, name, accessToken string) error { lock := svc.Mu.NewMutex(fmt.Sprintf("store-token-lock-%d", ID)) // 获取锁 if err := lock.Lock(); err != nil { return err } access := fmt.Sprintf("auth:access-token:%v:%s", ID, name) err := svc.Client.Set(ctx, access, accessToken, expiry).Err() if err != nil { err = errorcode.New(svc.namespace, common.ErrorMessage[common.FailedToStoreTokenErrorCode], int32(common.FailedToStoreTokenErrorCode)) logrus.Error(err) return err } return nil } // StoreRevoke 从Redis中删除令牌 func (svc *JWTAuth) StoreRevoke(ctx context.Context, ID int64, name string) error { lock := svc.Mu.NewMutex(fmt.Sprintf("store-revoke-lock-%d", ID)) // 获取锁 if err := lock.Lock(); err != nil { return err } defer lock.Unlock() access := fmt.Sprintf("auth:access-token:%v:%s", ID, name) err := svc.Client.Del(ctx, access).Err() if err != nil { err = errorcode.New(svc.namespace, common.ErrorMessage[common.TokenDeletionFailedErrorCode], int32(common.TokenDeletionFailedErrorCode)) logrus.Error(err) return err } return nil } // StoreVerify 检查是否在有效期内 func (svc *JWTAuth) StoreVerify(ID int64, name string, accessToken string) bool { lock := svc.Mu.NewMutex(fmt.Sprintf("store-verify-lock-%d", ID)) // 获取锁 if err := lock.Lock(); err != nil { // 处理获取锁失败的情况 return false } defer lock.Unlock() token := fmt.Sprintf("auth:access-token:%v:%s", ID, name) return svc.Client.Get(context.Background(), token).Val() == accessToken } // Blacklist 将令牌添加到 Redis 黑名单并设置过期时间。 func (svc *JWTAuth) Blacklist(accessToken string) error { lock := svc.Mu.NewMutex(fmt.Sprintf("blacklist-lock-%d", uuid.New())) // 获取锁 if err := lock.Lock(); err != nil { return err } defer lock.Unlock() // 将令牌添加到 Redis,并设置过期时间 return svc.Client.Set(context.Background(), accessToken, "blacklisted", expiry).Err() } // IsBlacklisted 检查令牌是否在黑名单中。 func (svc *JWTAuth) IsBlacklisted(accessToken string) (bool, error) { lock := svc.Mu.NewMutex(fmt.Sprintf("is-blacklisted-lock-%d", uuid.New())) // 获取锁 if err := lock.Lock(); err != nil { // 处理获取锁失败的情况 return false, err } defer lock.Unlock() // 检查令牌是否存在于 Redis 中 _, err := svc.Client.Get(context.Background(), accessToken).Result() if err == goredislib.Nil { return false, nil } else if err != nil { return false, err } return true, nil } // CleanupExpiredTokens 定期清理 Redis 黑名单中的过期令牌。 func (svc *JWTAuth) CleanupExpiredTokens(interval time.Duration) { ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ticker.C: svc.cleanupExpiredTokens() } } } func (svc *JWTAuth) cleanupExpiredTokens() { lock := svc.Mu.NewMutex("cleanup-expired-tokens-lock") // 获取锁 if err := lock.Lock(); err != nil { // 处理获取锁失败的情况 return } defer lock.Unlock() // 遍历并从 Redis 中删除过期的键 iter := svc.Client.Scan(context.Background(), 0, "*", 0).Iterator() for iter.Next(context.Background()) { key := iter.Val() ttl := svc.Client.TTL(context.Background(), key).Val() if ttl.Seconds() <= 0 { svc.Client.Del(context.Background(), key) } } } // handleSignals 在应用程序退出时处理信号 func handleSignals(client *goredislib.Client) { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) go func() { <-c fmt.Println("Closing Redis connection...") if err := client.Close(); err != nil { fmt.Println("Error closing Redis connection:", err) } else { fmt.Println("Redis connection closed.") } os.Exit(0) }() }