package middleware import ( "context" "fmt" "log/slog" "time" "github.com/redis/go-redis/v9" ) const statePrefix = "silk:auth:" // StateStore 跨实例认证状态存储。 type StateStore interface { RevokeToken(ctx context.Context, id string, exp time.Time) error IsTokenRevoked(ctx context.Context, id string) (bool, error) CheckLoginLock(ctx context.Context, key string) (bool, time.Duration, error) RecordLoginFailure(ctx context.Context, key string) error RecordLoginSuccess(ctx context.Context, key string) error } var ( authState StateStore appEnv string ) // InitState 设置认证状态存储;store 为 nil 时认证相关接口保守失败。 func InitState(store StateStore, env string) { authState = store appEnv = env } // RedisState Redis 实现。 type RedisState struct { rdb *redis.Client prefix string } // NewRedisState 创建 Redis 状态存储。 func NewRedisState(rdb *redis.Client) *RedisState { return &RedisState{rdb: rdb, prefix: statePrefix} } func (s *RedisState) RevokeToken(ctx context.Context, id string, exp time.Time) error { ttl := time.Until(exp) if ttl <= 0 { return nil } return s.rdb.Set(ctx, s.prefix+"revoked:"+id, "1", ttl).Err() } func (s *RedisState) IsTokenRevoked(ctx context.Context, id string) (bool, error) { count, err := s.rdb.Exists(ctx, s.prefix+"revoked:"+id).Result() if err != nil { return false, err } return count > 0, nil } func (s *RedisState) CheckLoginLock(ctx context.Context, key string) (bool, time.Duration, error) { lockKey := s.prefix + "login-lock:" + key if _, err := s.rdb.Get(ctx, lockKey).Result(); err == redis.Nil { return false, 0, nil } else if err != nil { return false, 0, err } ttl, err := s.rdb.TTL(ctx, lockKey).Result() if err != nil { return false, 0, err } return true, ttl, nil } var loginFailureScript = redis.NewScript(` local count = redis.call('INCR', KEYS[1]) redis.call('EXPIRE', KEYS[1], ARGV[1]) if tonumber(count) >= tonumber(ARGV[2]) then redis.call('SET', KEYS[2], '1', 'PX', ARGV[3]) end return count `) func (s *RedisState) RecordLoginFailure(ctx context.Context, key string) error { return loginFailureScript.Run(ctx, s.rdb, []string{s.prefix + "login-failures:" + key, s.prefix + "login-lock:" + key}, int(failureWindow.Seconds()), maxFailures, int(lockDuration.Milliseconds()), ).Err() } func (s *RedisState) RecordLoginSuccess(ctx context.Context, key string) error { pipe := s.rdb.Pipeline() pipe.Del(ctx, s.prefix+"login-failures:"+key, s.prefix+"login-lock:"+key) _, err := pipe.Exec(ctx) return err } func stateUnavailable(operation string) error { msg := "Redis 状态服务不可用,无法" + operation if appEnv == "production" { slog.Error(msg) } return fmt.Errorf("%s", msg) }