npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@ahoo-wang/fetcher-cosec

v3.4.1

Published

Enterprise-grade CoSec authentication integration for the Fetcher HTTP client with comprehensive security features including automatic token management, device tracking, and request attribution.

Downloads

2,212

Readme

@ahoo-wang/fetcher-cosec

npm version Build Status codecov License npm downloads npm bundle size Ask DeepWiki Storybook

企业级 CoSec 认证集成,为 Fetcher HTTP 客户端提供全面的安全功能,包括自动令牌管理、设备跟踪和请求归属。

CoSec 是专为企业应用设计的全面身份认证和授权框架。

此包为 Fetcher HTTP 客户端与 CoSec 认证框架提供了无缝集成,支持企业级安全功能的最小化配置。

🌟 功能特性

  • 🔐 自动认证:无缝 CoSec 认证与自动头部注入
  • 📱 设备管理:持久化设备 ID 管理,支持 localStorage 和后备存储
  • 🔄 令牌刷新:基于 401 响应的智能令牌刷新,包含重试逻辑
  • 🌈 请求归属:唯一请求 ID 生成,用于全面 API 跟踪
  • 💾 安全令牌存储:加密 JWT 令牌存储,支持可配置后端
  • 🛡️ 企业级安全:多租户支持、速率限制和安全监控
  • ⚡ 性能优化:最小开销,支持连接池和缓存
  • 🛠️ TypeScript 优先:完整类型定义,严格类型安全
  • 🔌 可插拔架构:模块化设计,易于集成和定制
  • ⚙️ 简化配置:使用 CoSecConfigurer 的一行设置,最小化配置开销

🚀 快速开始

安装

# 使用 npm
npm install @ahoo-wang/fetcher-cosec

# 使用 pnpm
pnpm add @ahoo-wang/fetcher-cosec

# 使用 yarn
yarn add @ahoo-wang/fetcher-cosec

基本设置

import { Fetcher } from '@ahoo-wang/fetcher';
import {
  AuthorizationRequestInterceptor,
  AuthorizationResponseInterceptor,
  DeviceIdStorage,
  TokenStorage,
  JwtTokenManager,
  CompositeToken,
  TokenRefresher,
} from '@ahoo-wang/fetcher-cosec';

// 创建 Fetcher 实例
const fetcher = new Fetcher({
  baseURL: 'https://api.example.com',
});

// 创建存储实例
const deviceIdStorage = new DeviceIdStorage();
const tokenStorage = new TokenStorage();

// 实现令牌刷新器
const tokenRefresher: TokenRefresher = {
  async refresh(token: CompositeToken): Promise<CompositeToken> {
    // 实现您的令牌刷新逻辑
    const response = await fetch('/api/auth/refresh', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        refreshToken: token.refreshToken,
      }),
    });

    if (!response.ok) {
      throw new Error('令牌刷新失败');
    }

    const tokens = await response.json();
    return {
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
    };
  },
};

// 创建 JWT 令牌管理器
const tokenManager = new JwtTokenManager(tokenStorage, tokenRefresher);

// 配置 CoSec 选项
const cosecOptions = {
  appId: 'your-app-id',
  tokenManager,
  deviceIdStorage,
};

// 添加 CoSec 请求拦截器
fetcher.interceptors.request.use(
  new AuthorizationRequestInterceptor(cosecOptions),
);

// 添加 CoSec 响应拦截器
fetcher.interceptors.response.use(
  new AuthorizationResponseInterceptor(cosecOptions),
);

🚀 简化设置(推荐)

为了获得更简单的设置体验,请使用 CoSecConfigurer 类,它会自动处理所有复杂的依赖创建和拦截器配置:

import { Fetcher } from '@ahoo-wang/fetcher';
import { CoSecConfigurer } from '@ahoo-wang/fetcher-cosec';

// 创建 Fetcher 实例
const fetcher = new Fetcher({
  baseURL: 'https://api.example.com',
});

// 使用灵活的配置创建 CoSec 配置器
const configurer = new CoSecConfigurer({
  appId: 'your-app-id',

  // 可选:自定义存储实现
  tokenStorage: new TokenStorage(),
  deviceIdStorage: new DeviceIdStorage(),

  // 可选:令牌刷新器(启用认证拦截器)
  tokenRefresher: {
    refresh: async token => {
      // 实现您的令牌刷新逻辑
      const response = await fetch('/api/auth/refresh', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ refreshToken: token.refreshToken }),
      });

      if (!response.ok) {
        throw new Error('Token refresh failed');
      }

      const tokens = await response.json();
      return {
        accessToken: tokens.accessToken,
        refreshToken: tokens.refreshToken,
      };
    },
  },

  // 可选:自定义错误处理器(仅在提供时才添加拦截器)
  onUnauthorized: exchange => {
    console.error('未授权访问:', exchange.request.url);
    // 重定向到登录或根据需要处理
    window.location.href = '/login';
  },
  onForbidden: async exchange => {
    console.error('禁止访问:', exchange.request.url);
    // 显示权限错误
    alert('您没有权限访问此资源');
  },
});

// 使用一次调用应用所有 CoSec 拦截器
configurer.applyTo(fetcher);

// 现在您可以使用具有完整 CoSec 认证的 fetcher
const response = await fetcher.get('/protected-endpoint');

CoSecConfigurer 的优势

  • 灵活配置:支持完整认证设置或仅最小的 CoSec 头部
  • 自定义存储:可选的自定义 TokenStorage 和 DeviceIdStorage 实现
  • 条件拦截器:仅在提供 tokenRefresher 时才添加认证拦截器
  • 错误处理器控制:根据需要选择添加哪些错误拦截器
  • 类型安全:完整的 TypeScript 支持和智能默认值
  • 向后兼容:原始手动设置仍然有效

🔧 配置

CoSecOptions 接口

interface CoSecOptions
  extends AppIdCapable,
    DeviceIdStorageCapable,
    JwtTokenManagerCapable {
  // 继承自能力接口
}

CoSecOptions 接口组合了以下能力接口:

interface AppIdCapable {
  /**
   * 应用程序 ID,将在 CoSec-App-Id 头部中发送
   */
  appId: string;
}

interface DeviceIdStorageCapable {
  /**
   * 设备 ID 存储实例,用于管理设备标识
   */
  deviceIdStorage: DeviceIdStorage;
}

interface JwtTokenManagerCapable {
  /**
   * JWT 令牌管理器,用于处理令牌操作
   */
  tokenManager: JwtTokenManager;
}

添加的头部

拦截器会自动向请求添加以下头部:

  1. Authorization: Bearer <access-token> - Bearer 令牌认证
  2. CoSec-App-Id: <app-id> - 应用程序标识符
  3. CoSec-Device-Id: <device-id> - 设备标识符
  4. CoSec-Request-Id: <unique-request-id> - 唯一请求标识符

📚 API 参考

核心类

CoSecConfigurer

配置 CoSec 认证的推荐方式。提供简化的 API,自动创建和配置所有必要的拦截器和依赖项。

const configurer = new CoSecConfigurer({
  appId: 'your-app-id',

  // 可选:自定义存储实现
  tokenStorage: new TokenStorage('custom-prefix'),
  deviceIdStorage: new DeviceIdStorage('custom-prefix'),

  // 可选:令牌刷新器(启用认证拦截器)
  tokenRefresher: {
    refresh: async token => {
      // 您的令牌刷新实现
      return {
        accessToken: 'new-access-token',
        refreshToken: 'new-refresh-token',
      };
    },
  },

  // 可选错误处理器(仅在提供时才添加拦截器)
  onUnauthorized: exchange => {
    /* 处理 401 */
  },
  onForbidden: async exchange => {
    /* 处理 403 */
  },
});

configurer.applyTo(fetcher);

条件配置的拦截器:

始终添加:

  • CoSecRequestInterceptor - 添加 CoSec 头部(appId、deviceId、requestId)
  • ResourceAttributionRequestInterceptor - 添加租户/所有者路径参数

仅在提供 tokenRefresher 时添加:

  • AuthorizationRequestInterceptor - 添加 Bearer 令牌认证
  • AuthorizationResponseInterceptor - 处理 401 响应时的令牌刷新

仅在提供相应处理器时添加:

  • UnauthorizedErrorInterceptor - 处理 401 未授权错误
  • ForbiddenErrorInterceptor - 处理 403 禁止错误

AuthorizationRequestInterceptor

自动向传出 HTTP 请求添加 CoSec 认证头部。

const interceptor = new AuthorizationRequestInterceptor({
  appId: 'your-app-id',
  tokenManager: jwtTokenManager,
  deviceIdStorage: deviceIdStorage,
});

添加的头部:

  • Authorization: Bearer <access-token>
  • CoSec-App-Id: <app-id>
  • CoSec-Device-Id: <device-id>
  • CoSec-Request-Id: <unique-request-id>

AuthorizationResponseInterceptor

处理接收到 401 未授权响应时的自动令牌刷新。

const interceptor = new AuthorizationResponseInterceptor({
  appId: 'your-app-id',
  tokenManager: jwtTokenManager,
  deviceIdStorage: deviceIdStorage,
});

功能特性:

  • 自动重试带刷新令牌的请求
  • 失败重试的指数退避
  • 可配置的重试限制

JwtTokenManager

管理 JWT 令牌生命周期,包括验证、刷新和存储。

const tokenManager = new JwtTokenManager(tokenStorage, tokenRefresher);

// 检查令牌是否有效
const isValid = await tokenManager.isValid();

// 手动刷新令牌
await tokenManager.refresh();

// 获取当前令牌
const token = tokenManager.getToken();

TokenStorage

安全令牌存储,支持 localStorage 后端和后备存储。

const tokenStorage = new TokenStorage('optional-prefix');

// 存储复合令牌
tokenStorage.set({
  accessToken: 'eyJ...',
  refreshToken: 'eyJ...',
});

// 检索令牌
const token = tokenStorage.get();

// 移除存储的令牌
tokenStorage.remove();

// 检查令牌是否存在
const exists = tokenStorage.exists();

DeviceIdStorage

管理 localStorage 中的持久化设备标识。

const deviceStorage = new DeviceIdStorage('optional-prefix');

// 获取或创建设备 ID
const deviceId = await deviceStorage.getOrCreate();

// 设置特定设备 ID
deviceStorage.set('custom-device-id');

// 获取当前设备 ID
const currentId = deviceStorage.get();

// 清除存储的设备 ID
deviceStorage.clear();

// 生成新的设备 ID(不存储)
const newId = deviceStorage.generateDeviceId();

TokenRefresher

实现自定义令牌刷新逻辑的接口。

interface TokenRefresher {
  refresh(token: CompositeToken): Promise<CompositeToken>;
}

class CustomTokenRefresher implements TokenRefresher {
  async refresh(token: CompositeToken): Promise<CompositeToken> {
    const response = await fetch('/api/auth/refresh', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ refreshToken: token.refreshToken }),
    });

    if (!response.ok) {
      throw new Error('令牌刷新失败');
    }

    const newTokens = await response.json();
    return {
      accessToken: newTokens.accessToken,
      refreshToken: newTokens.refreshToken,
    };
  }
}

接口与类型

令牌类型

interface AccessToken {
  readonly value: string;
}

interface RefreshToken {
  readonly value: string;
}

interface CompositeToken {
  readonly accessToken: string;
  readonly refreshToken: string;
}

JWT 令牌类型

interface JwtPayload {
  readonly sub?: string;
  readonly exp?: number;
  readonly iat?: number;
  readonly iss?: string;
  [key: string]: any;
}

interface JwtToken {
  readonly header: JwtHeader;
  readonly payload: JwtPayload;
  readonly signature: string;
  readonly raw: string;
}

配置类型

interface CoSecOptions
  extends AppIdCapable,
    DeviceIdStorageCapable,
    JwtTokenManagerCapable {}

interface AppIdCapable {
  readonly appId: string;
}

interface DeviceIdStorageCapable {
  readonly deviceIdStorage: DeviceIdStorage;
}

interface JwtTokenManagerCapable {
  readonly tokenManager: JwtTokenManager;
}

响应类型

interface AuthorizeResult {
  readonly authorized: boolean;
  readonly reason: string;
}

// 预定义的授权结果
const AuthorizeResults = {
  ALLOW: { authorized: true, reason: 'Allow' },
  EXPLICIT_DENY: { authorized: false, reason: 'Explicit Deny' },
  IMPLICIT_DENY: { authorized: false, reason: 'Implicit Deny' },
  TOKEN_EXPIRED: { authorized: false, reason: 'Token Expired' },
  TOO_MANY_REQUESTS: { authorized: false, reason: 'Too Many Requests' },
} as const;

🔗 内置拦截器

CoSec 包提供了多个专门的拦截器,用于不同的认证和授权场景:

请求拦截器

AuthorizationRequestInterceptor

用途:向传出请求添加 JWT Bearer 令牌认证头部。

添加的头部

  • Authorization: Bearer <access-token>

使用场景:API 请求的标准 JWT 认证。

const interceptor = new AuthorizationRequestInterceptor({
  appId: 'your-app-id',
  tokenManager: jwtTokenManager,
  deviceIdStorage: deviceStorage,
});

CoSecRequestInterceptor

用途:向请求添加基本的 CoSec 标识头部。

添加的头部

  • CoSec-App-Id: <app-id>
  • CoSec-Device-Id: <device-id>
  • CoSec-Request-Id: <unique-request-id>

使用场景:设备跟踪和请求归属,无需完整的 JWT 认证。

const interceptor = new CoSecRequestInterceptor({
  appId: 'your-app-id',
  deviceIdStorage: deviceStorage,
});

ResourceAttributionRequestInterceptor

用途:从 JWT 令牌声明中自动注入租户和所有者 ID 路径参数。

功能:从 JWT 载荷中提取 tenantIdsub(所有者 ID),并将其添加到 URL 路径参数中。

使用场景:具有租户范围资源的 SaaS 应用。

const interceptor = new ResourceAttributionRequestInterceptor({
  tenantId: 'tenantId', // 租户 ID 的路径参数名称
  ownerId: 'ownerId', // 所有者 ID 的路径参数名称
  tokenStorage: tokenStorage,
});

响应拦截器

AuthorizationResponseInterceptor

用途:在收到 401 未授权响应时处理自动令牌刷新。

功能

  • 检测 401 响应
  • 使用配置的 TokenRefresher 尝试令牌刷新
  • 使用新令牌重试原始请求
  • 刷新失败时的指数退避

使用场景:无缝令牌刷新,无需用户干预。

const interceptor = new AuthorizationResponseInterceptor({
  appId: 'your-app-id',
  tokenManager: jwtTokenManager,
  deviceIdStorage: deviceStorage,
});

错误拦截器

UnauthorizedErrorInterceptor

用途:通过自定义回调逻辑提供认证失败的集中处理。

功能

  • 检测 401 响应和 RefreshTokenError 异常
  • 调用自定义回调进行错误处理
  • 允许应用实现登录重定向、令牌清理等

使用场景:自定义认证错误处理和用户体验流程。

const interceptor = new UnauthorizedErrorInterceptor({
  onUnauthorized: exchange => {
    console.log('认证失败于:', exchange.request.url);
    // 重定向到登录页面或显示错误消息
    window.location.href = '/login';
  },
});

ForbiddenErrorInterceptor

用途:通过自定义回调逻辑提供授权失败(403 禁止访问)的集中处理。

功能

  • 检测 403 禁止访问响应
  • 调用自定义回调进行权限错误处理
  • 允许应用实现访问请求流程、权限显示等

使用场景:自定义授权错误处理、权限管理和用户指导。

const interceptor = new ForbiddenErrorInterceptor({
  onForbidden: async exchange => {
    console.log('访问被禁止于:', exchange.request.url);
    // 显示权限错误或重定向到访问请求页面
    showPermissionError('您没有权限访问此资源');
  },
});

拦截器顺序与执行

拦截器按以下默认顺序执行:

  1. 请求阶段

    • AuthorizationRequestInterceptor(添加 Bearer 令牌)
    • CoSecRequestInterceptor(添加 CoSec 头部)
    • ResourceAttributionRequestInterceptor(添加路径参数)
  2. 响应阶段

    • AuthorizationResponseInterceptor(处理令牌刷新)
  3. 错误阶段

    • UnauthorizedErrorInterceptor(处理 401 认证错误)
    • ForbiddenErrorInterceptor(处理 403 权限错误)

注意:可以使用 order 属性自定义拦截器执行顺序。order 值越高,在链中执行越晚。

🛠️ 示例

完整认证设置

import { Fetcher } from '@ahoo-wang/fetcher';
import {
  AuthorizationRequestInterceptor,
  AuthorizationResponseInterceptor,
  DeviceIdStorage,
  TokenStorage,
  JwtTokenManager,
  TokenRefresher,
  CompositeToken,
} from '@ahoo-wang/fetcher-cosec';

// 创建存储实例
const deviceIdStorage = new DeviceIdStorage();
const tokenStorage = new TokenStorage();

// 实现令牌刷新器
const tokenRefresher: TokenRefresher = {
  async refresh(token: CompositeToken): Promise<CompositeToken> {
    const response = await fetch('/api/auth/refresh', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${token.accessToken}`,
      },
      body: JSON.stringify({
        refreshToken: token.refreshToken,
      }),
    });

    if (!response.ok) {
      throw new Error(`令牌刷新失败: ${response.status}`);
    }

    const tokens = await response.json();
    return {
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
    };
  },
};

// 创建 JWT 令牌管理器
const tokenManager = new JwtTokenManager(tokenStorage, tokenRefresher);

// 创建 fetcher 并添加 CoSec 拦截器
const secureFetcher = new Fetcher({
  baseURL: 'https://api.example.com',
});

// 添加请求拦截器用于认证头部
secureFetcher.interceptors.request.use(
  new AuthorizationRequestInterceptor({
    appId: 'my-app-id',
    tokenManager,
    deviceIdStorage,
  }),
);

// 添加响应拦截器用于令牌刷新
secureFetcher.interceptors.response.use(
  new AuthorizationResponseInterceptor({
    appId: 'my-app-id',
    tokenManager,
    deviceIdStorage,
  }),
);

// 现在所有请求都会自动进行认证
const userProfile = await secureFetcher.get('/api/user/profile');
const userPosts = await secureFetcher.get('/api/user/posts');

高级令牌刷新与重试逻辑

import {
  TokenRefresher,
  CompositeToken,
  JwtTokenManager,
  TokenStorage,
} from '@ahoo-wang/fetcher-cosec';

class ResilientTokenRefresher implements TokenRefresher {
  private maxRetries = 3;
  private baseDelay = 1000; // 1 秒

  async refresh(token: CompositeToken): Promise<CompositeToken> {
    let lastError: Error;

    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      try {
        // 指数退避与抖动
        if (attempt > 1) {
          const delay = Math.pow(2, attempt - 1) * this.baseDelay;
          const jitter = Math.random() * 0.1 * delay;
          await new Promise(resolve => setTimeout(resolve, delay + jitter));
        }

        const response = await fetch('/api/auth/refresh', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'X-Retry-Attempt': attempt.toString(),
          },
          body: JSON.stringify({
            refreshToken: token.refreshToken,
            deviceId: await this.getDeviceId(),
          }),
        });

        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        const newTokens = await response.json();

        // 验证令牌结构
        if (!newTokens.accessToken || !newTokens.refreshToken) {
          throw new Error('令牌响应结构无效');
        }

        return {
          accessToken: newTokens.accessToken,
          refreshToken: newTokens.refreshToken,
        };
      } catch (error) {
        lastError = error as Error;
        console.warn(`令牌刷新尝试 ${attempt}/${this.maxRetries} 失败:`, error);

        // 不要对认证错误(401/403)重试
        if (error instanceof Response) {
          const status = error.status;
          if (status === 401 || status === 403) {
            throw error;
          }
        }

        // 最后一次尝试不要重试
        if (attempt === this.maxRetries) {
          break;
        }
      }
    }

    throw lastError!;
  }

  private async getDeviceId(): Promise<string> {
    // 实现获取当前设备 ID
    const deviceStorage = new DeviceIdStorage();
    return await deviceStorage.getOrCreate();
  }
}

// 使用
const tokenStorage = new TokenStorage();
const tokenRefresher = new ResilientTokenRefresher();
const tokenManager = new JwtTokenManager(tokenStorage, tokenRefresher);

多租户认证

import { Fetcher } from '@ahoo-wang/fetcher';
import {
  AuthorizationRequestInterceptor,
  AuthorizationResponseInterceptor,
  DeviceIdStorage,
  TokenStorage,
  JwtTokenManager,
  TokenRefresher,
  CompositeToken,
} from '@ahoo-wang/fetcher-cosec';

// 租户配置接口
interface TenantConfig {
  id: string;
  name: string;
  appId: string;
  baseURL: string;
  refreshEndpoint: string;
  tokenStoragePrefix?: string;
}

// 租户注册表用于管理多个租户
class TenantRegistry {
  private tenants = new Map<string, TenantConfig>();
  private fetchers = new Map<string, Fetcher>();

  registerTenant(config: TenantConfig): void {
    // 为隔离使用租户 ID 作为存储前缀
    const storagePrefix = config.tokenStoragePrefix || `tenant-${config.id}`;
    config.tokenStoragePrefix = storagePrefix;
    this.tenants.set(config.id, config);
  }

  getFetcher(tenantId: string): Fetcher {
    if (this.fetchers.has(tenantId)) {
      return this.fetchers.get(tenantId)!;
    }

    const config = this.tenants.get(tenantId);
    if (!config) {
      throw new Error(`租户 '${tenantId}' 未注册`);
    }

    const fetcher = this.createTenantFetcher(config);
    this.fetchers.set(tenantId, fetcher);
    return fetcher;
  }

  private createTenantFetcher(config: TenantConfig): Fetcher {
    const fetcher = new Fetcher({
      baseURL: config.baseURL,
    });

    // 每个租户的隔离存储
    const tokenStorage = new TokenStorage(config.tokenStoragePrefix);
    const deviceStorage = new DeviceIdStorage(config.tokenStoragePrefix);

    // 租户特定的令牌刷新器
    const tokenRefresher: TokenRefresher = {
      async refresh(token: CompositeToken): Promise<CompositeToken> {
        const response = await fetch(
          `${config.baseURL}${config.refreshEndpoint}`,
          {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'X-Tenant-ID': config.id,
            },
            body: JSON.stringify({
              refreshToken: token.refreshToken,
            }),
          },
        );

        if (!response.ok) {
          throw new Error(`租户 ${config.id} 令牌刷新失败`);
        }

        const tokens = await response.json();
        return {
          accessToken: tokens.accessToken,
          refreshToken: tokens.refreshToken,
        };
      },
    };

    const tokenManager = new JwtTokenManager(tokenStorage, tokenRefresher);

    // 添加带有租户上下文的 CoSec 拦截器
    fetcher.interceptors.request.use(
      new AuthorizationRequestInterceptor({
        appId: config.appId,
        tokenManager,
        deviceIdStorage: deviceStorage,
      }),
    );

    fetcher.interceptors.response.use(
      new AuthorizationResponseInterceptor({
        appId: config.appId,
        tokenManager,
        deviceIdStorage: deviceStorage,
      }),
    );

    return fetcher;
  }

  // 租户登出的清理方法
  async logoutTenant(tenantId: string): Promise<void> {
    const config = this.tenants.get(tenantId);
    if (config) {
      const tokenStorage = new TokenStorage(config.tokenStoragePrefix);
      tokenStorage.remove();

      const deviceStorage = new DeviceIdStorage(config.tokenStoragePrefix);
      deviceStorage.clear();

      this.fetchers.delete(tenantId);
    }
  }
}

// 使用示例
const tenantRegistry = new TenantRegistry();

// 注册多个租户
tenantRegistry.registerTenant({
  id: 'enterprise-a',
  name: '企业 A',
  appId: 'app-enterprise-a',
  baseURL: 'https://api.enterprise-a.com',
  refreshEndpoint: '/auth/refresh',
});

tenantRegistry.registerTenant({
  id: 'enterprise-b',
  name: '企业 B',
  appId: 'app-enterprise-b',
  baseURL: 'https://api.enterprise-b.com',
  refreshEndpoint: '/auth/refresh',
});

// 使用租户特定的 fetcher
const tenantAFetcher = tenantRegistry.getFetcher('enterprise-a');
const tenantBFetcher = tenantRegistry.getFetcher('enterprise-b');

// 每个租户维护完全隔离的认证状态
const profileA = await tenantAFetcher.get('/user/profile');
const profileB = await tenantBFetcher.get('/user/profile');

// 登出特定租户
await tenantRegistry.logoutTenant('enterprise-a');

🧪 测试

包包含所有组件的全面测试覆盖:

# 运行所有测试
pnpm test

# 运行测试并生成覆盖率报告
pnpm test --coverage

# 开发期间以监视模式运行测试
pnpm test --watch

# 运行特定测试文件
pnpm test tokenStorage.test.ts

# 运行集成测试
pnpm test:it

测试覆盖

  • 单元测试:独立组件测试与模拟
  • 集成测试:端到端认证流程
  • 安全测试:令牌验证和安全场景
  • 性能测试:基准测试和内存泄漏检测

测试工具

import {
  createMockJwtToken,
  createExpiredJwtToken,
  MockTokenStorage,
  MockDeviceStorage,
} from '@ahoo-wang/fetcher-cosec/test-utils';

// 创建测试令牌
const validToken = createMockJwtToken({ sub: 'user123' });
const expiredToken = createExpiredJwtToken();

// 用于隔离测试的模拟存储
const tokenStorage = new MockTokenStorage();
const deviceStorage = new MockDeviceStorage();

🌐 CoSec 框架集成

此包为 CoSec 认证框架 提供无缝集成,支持企业级安全功能:

关键集成点

  • 集中认证:连接到 CoSec 的认证服务器
  • 设备管理:自动设备注册和跟踪
  • 令牌生命周期:完整的 JWT 令牌管理与刷新能力
  • 安全策略:强制执行 CoSec 安全策略和规则
  • 审计日志:全面的请求归属和日志记录

架构概览

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   应用程序      │────│  Fetcher CoSec   │────│     CoSec       │
│                 │    │   集成           │    │   框架          │
└─────────────────┘    └──────────────────┘    └─────────────────┘
         │                       │                       │
         └─ HTTP 请求            └─ 认证头部            └─ 令牌验证
         └─ 响应处理             └─ 令牌刷新            └─ 设备跟踪
         └─ 错误恢复             └─ 安全策略            └─ 审计日志

有关 CoSec 框架的详细信息和高级配置选项,请访问 CoSec GitHub 仓库

🤝 贡献

我们欢迎贡献!请查看我们的 贡献指南 了解:

  • 开发设置:开始使用代码库
  • 代码标准:TypeScript、linting 和测试指南
  • 拉取请求流程:如何提交更改
  • 问题报告:错误报告和功能请求

开发命令

# 安装依赖
pnpm install

# 启动开发服务器
pnpm dev

# 运行 linting 和类型检查
pnpm lint
pnpm typecheck

# 运行测试套件
pnpm test

# 构建包
pnpm build

📄 许可证

根据 Apache License, Version 2.0 授权。详见 LICENSE

🙏 致谢