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 🙏

© 2026 – Pkg Stats / Ryan Hefner

nestjs-sa-token

v1.0.3

Published

A powerful and lightweight authorization framework for NestJS

Readme

SA-Token for NestJS

轻量级、强大的 NestJS 权限认证框架

License: MIT Node.js NestJS

简介

SA-Token for NestJS 是一个基于 Sa-Token 设计理念的 NestJS 权限认证框架,提供了完整的登录鉴权、权限校验、角色管理、Session 管理、账号封禁、二级认证等功能。支持内存和 Redis 两种持久化方式,可灵活扩展。

特性

  • 登录鉴权 — 单端登录、多端登录、同端互斥登录、多端共用 Token
  • 权限校验 — 注解式/编程式权限验证,支持 AND/OR 模式
  • 角色管理 — 角色校验,支持多角色组合判断
  • Session 管理 — Account-Session / Token-Session 双层会话体系
  • 踢人下线 — 根据账号或设备踢出在线用户
  • 账号封禁 — 按服务类型封禁,支持封禁等级与时间
  • 二级认证 — 敏感操作二次验证(如修改密码、转账等)
  • Token 策略 — 支持 UUID、Simple-UUID、Random、JWT 等多种 Token 风格
  • 路由拦截 — 声明式路由匹配与权限校验链
  • 持久化 — 内置内存实现,可选 Redis 实现(可自定义 DAO)
  • 自动续签 — 可配置的 Token 活跃超时自动续期

快速开始

安装

npm install nestjs-sa-token

如需使用 Redis 持久化:

npm install ioredis

注册模块

AppModule 中引入 SaTokenModule

// app.module.ts
import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { SaTokenModule, SaRouterMiddleware, SaTokenDaoRedis } from 'nestjs-sa-token';
import { AppController } from './app.controller';
import { RedisModule } from './redis/redis.module';
import Redis from 'ioredis';
import { StpInterfaceImpl } from './auth/stp-interface.impl';
@Module({
  imports: [
    // 1. 配置模块
    ConfigModule.forRoot({
      isGlobal: true,
    }),

    // 2. Redis 模块
    RedisModule,

    // 3. SA-Token 模块
    SaTokenModule.forRoot({
      stpInterface: {
        useClass: StpInterfaceImpl,
      },
      config: {
        tokenName: 'Authorization',
        tokenPrefix: 'Bearer',
        timeout: 2592000,
        activeTimeout: -1,
        isConcurrent: true,
        isShare: false,
        maxLoginCount: 5,
        tokenStyle: 'jwt',
        jwtSecretKey: 'your_jwt_secret_key',
        isLog: false,
      },
      interceptors: {
        responseTransform: true, // 成功响应统一包装
        requestLog: true,        // 请求耗时日志
      },
      routerConfig: (router) => {
        // ✅ 匹配所有路径
        router.match('/**')
          .notMatch(
            '/',              // ✅ 排除根路径
            '/debug/**',      // 排除调试
            '/api/auth/test',
          )
          .checkLogin();
      },

      dao: {
        useFactory: (redis: Redis) => {
          console.log('📦 创建 SaTokenDaoRedis');
          return new SaTokenDaoRedis(redis);
        },
        inject: ['REDIS_CLIENT'],
      },

      global: true,
    }),
  ],
  controllers: [AppController],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(SaRouterMiddleware).forRoutes('*');
  }
}

实现权限接口

创建 StpInterface 的实现类,提供权限和角色数据源:

import { Injectable } from '@nestjs/common';
import { StpInterface } from 'nestjs-sa-token';

@Injectable()
export class MyStpInterface implements StpInterface {
  async getPermissionList(loginId: string | number, loginType: string): Promise<string[]> {
    // 从数据库查询用户权限列表
    return ['user:add', 'user:delete', 'user:update'];
  }

  async getRoleList(loginId: string | number, loginType: string): Promise<string[]> {
    // 从数据库查询用户角色列表
    return ['admin', 'super-admin'];
  }
}

然后在模块注册时注入:

SaTokenModule.forRoot({
  stpInterface: {
    useClass: MyStpInterface,
  },
}),

使用注解进行鉴权

import { Controller, Get, Post, Body } from '@nestjs/common';
import {
  SaCheckLogin,
  SaCheckPermission,
  SaCheckRole,
  SaCheckSafe,
  SaIgnore,
  LoginId,
  TokenValue,
} from 'nestjs-sa-token';

@Controller('user')
export class UserController {

  @Get('info')
  @SaCheckLogin()
  async getInfo(@LoginId() loginId: string) {
    return { loginId };
  }

  @Post('add')
  @SaCheckPermission('user:add')
  async addUser(@Body() body: any) {
    // 需要 user:add 权限
  }

  @Delete('delete')
  @SaCheckRole('admin')
  async deleteUser() {
    // 需要 admin 角色
  }

  @Post('password')
  @SaCheckSafe('update-password')   // 二级认证
  async updatePassword(@Body() body: any) {
    // 修改密码需要先通过二级认证
  }

  @Get('public')
  @SaIgnore()
  async publicData() {
    // 无需登录即可访问
  }

  @Get('or-permission')
  @SaCheckPermissionOr('user:add', 'user:update')
  async orPermission() {
    // 满足任一权限即可
  }
}

编程式调用

注入 StpUtil 进行编程式鉴权操作:

import { Controller, Get, Req, Res } from '@nestjs/common';
import { StpUtil, SaLoginModel } from 'nestjs-sa-token';

@Controller('auth')
export class AuthController {
  constructor(private readonly stpUtil: StpUtil) {}

  @Post('login')
  async login(@Req() req: any, @Res() res: any, @Body() body: any) {
    const { username, password } = body;

    // 校验账号密码...
    const userId = await this.verifyUser(username, password);

    // 执行登录
    const tokenValue = await this.stpUtil.login(userId, req, res, {
      device: 'PC',
      tag: 'online',
    });

    return { token: tokenValue };
  }

  @Post('logout')
  async logout(@Req() req: any, @Res() res: any) {
    await this.stpUtil.logout(req, res);
    return { msg: '注销成功' };
  }

  @Get('check')
  async check(@Req() req: any) {
    const isLogin = await this.stpUtil.isLogin(req);
    const loginId = await this.stpUtil.getLoginIdDefaultNull(req);
    return { isLogin, loginId };
  }
}

配置项说明

| 配置项 | 类型 | 默认值 | 说明 | | --------------- | --------- | ----------- | --------------------------------------- | | tokenName | string | 'satoken' | Token 名称(Header/Cookie/Body 字段名) | | timeout | number | 2592000 | Token 有效期(秒),-1 为永不过期 | | activeTimeout | number | -1 | 临时有效期(秒),-1 表示不启用 | | isConcurrent | boolean | true | 是否允许同一账号多地同时登录 | | maxLoginCount | number | 12 | 同一账号最大登录数量 | | isShare | boolean | true | 多设备登录时是否共用同一个 Token | | tokenStyle | string | 'uuid' | Token 风格 | | tokenPrefix | string | '' | Token 前缀(如 Bearer) | | autoRenew | boolean | true | 是否自动续签 | | jwtSecretKey | string | - | JWT 密钥(tokenStyle 为 jwt 时必填) | | isReadHeader | boolean | true | 是否从 Header 读取 Token | | isReadCookie | boolean | true | 是否从 Cookie 读取 Token | | isReadBody | boolean | true | 是否从 Body/Query 读取 Token | | isWriteHeader | boolean | true | 登录时是否写入 Header | | isLog | boolean | false | 是否打印操作日志 | | isColorLog | boolean | true | 是否打印彩色日志 |

Cookie 配置

config: {
  cookie: {
    domain: string;     // Cookie 域名
    path: '/';          // Cookie 路径
    secure: boolean;    // 仅 HTTPS
    httpOnly: true;     // HttpOnly
    sameSite: 'lax';    // SameSite 策略
    maxAge: number;     // 过期时间(毫秒)
  },
}

redis 配置

import { Module, Global } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Redis from 'ioredis';

const redisProvider = {
  provide: 'REDIS_CLIENT',
  useFactory: (configService: ConfigService) => {
    const redis = new Redis({
      host: configService.get('REDIS_HOST', '127.0.0.1'),
      port: configService.get('REDIS_PORT', 6379),
      password: configService.get('REDIS_PASSWORD'),
      db: configService.get('REDIS_DB', 0),
      retryStrategy: (times) => {
        const delay = Math.min(times * 50, 2000);
        return delay;
      },
    });

    redis.on('connect', () => {
      console.log('✅ Redis 连接成功');
    });

    redis.on('ready', () => {
      console.log('✅ Redis 已就绪');
    });

    redis.on('error', (err) => {
      console.error('❌ Redis 错误:', err.message);
    });

    redis.on('close', () => {
      console.log('⚠️  Redis 连接已关闭');
    });

    return redis;
  },
  inject: [ConfigService],
};

@Global() // 设为全局模块
@Module({
  providers: [redisProvider],
  exports: ['REDIS_CLIENT'], // 导出供其他模块使用
})
export class RedisModule {}

Token 风格

| 风格 | 说明 | 示例 | | ------------- | ------------------- | -------------------------------------- | | uuid | 标准 UUID v4 | f2b8c4e1-a3d7-4e9f-b5c6-8d2a1e0f3b7a | | simple-uuid | 简化 UUID(去横线) | f2b8c4e1a3d74e9fb5c68d2a1e0f3b7a | | random-32 | 32位随机字符串 | xK8mP2qRvLwN5tZjFhCdEgBiAuYsIoJk | | random-64 | 64位随机字符串 | - | | random-128 | 128位随机字符串 | - | | jwt | JSON Web Token | eyJhbGciOiJIUzI1NiIs... |

注解一览

鉴权注解

| 注解 | 说明 | 参数 | | -------------------------------- | --------------- | ------------------ | | @SaCheckLogin() | 登录校验 | type? — 登录类型 | | @SaCheckPermission(...perms) | 权限校验(AND) | 权限码列表 | | @SaCheckPermissionOr(...perms) | 权限校验(OR) | 权限码列表 | | @SaCheckRole(...roles) | 角色校验(AND) | 角色标识列表 | | @SaCheckRoleOr(...roles) | 角色校验(OR) | 角色标识列表 | | @SaCheckSafe(service?) | 二级认证 | 服务名称 | | @SaIgnore() | 忽略认证 | - |

参数装饰器

| 装饰器 | 说明 | | --------------- | ----------------- | | @LoginId() | 获取当前登录 ID | | @TokenValue() | 获取当前 Token 值 |

路由拦截

使用声明式路由规则配置全局鉴权:

import { SaRouter } from 'nestjs-sa-token';

SaTokenModule.forRoot({
  routerConfig: (router: SaRouter) => {
    router
      .match('/api/**')           // 匹配所有 /api/** 路径
      .notMatch('/api/public/**') // 排除公开接口
      .checkLogin();              // 要求登录

    router
      .match('/api/admin/**')
      .checkLogin()
      .checkRole('admin');        // 要求 admin 角色

    router
      .match('/api/user/**')
      .notMatch('/api/user/info')
      .check(async (req, res, stpUtil) => {
        // 自定义校验逻辑
        await stpUtil.checkPermission(req, 'user:read');
      });
  },
});

Session 操作

Account-Session(账号会话)

每个登录账号对应一个 Session,用于存储账号级别的数据:

// 获取 Session
const session = await this.stpUtil.getSessionByLoginId(10001);

// 存取数据
session.set('nickname', '张三');
session.set('avatar', '/avatar/10001.png');

const nickname = session.get<string>('nickname');

Token-Session(Token 会话)

每个 Token 对应一个独立 Session,用于存储 Token 级别的数据:

const tokenSession = await this.stpUtil.getTokenSession(req);

tokenSession.set('lastIp', req.ip);
tokenSession.set('loginTime', Date.now());

二级认证

适用于敏感操作的二次验证场景:

// 1. 开启二级认证(如输入密码/短信验证后)
await this.stpUtil.openSafe(req, 'transfer', 300); // 300秒有效

// 2. 在需要保护的方法上加注解
@SaCheckSafe('transfer')
async transferMoney(@Body() body: any) {
  // 已通过二级认证才能执行
}

// 3. 关闭二级认证
await this.stpUtil.closeSafe(req, 'transfer');

账号封禁

// 封禁账号
await this.stpUtil.disable(10001, 'comment', 1, 3600); // 封禁评论功能1小时

// 判断是否被封禁
const isDisable = await this.stpUtil.isDisable(10001, 'comment');

// 获取剩余封禁时间
const time = await this.stpUtil.getDisableTime(10001, 'comment');

// 解封
await this.stpUtil.untieDisable(10001, 'comment');

异常处理

框架内置了全局异常过滤器 SaTokenExceptionFilter,自动捕获并格式化异常响应:

| 异常 | HTTP 状态码 | 错误码 | | ------------------------- | ----------- | ------ | | NotLoginException | 401 | 11011 | | NotPermissionException | 403 | 11012 | | NotRoleException | 403 | 11013 | | DisableServiceException | 403 | 11014 | | NotSafeException | 403 | 11015 |

异常响应示例:

// 未登录
{
  "code": 11011,
  "message": "未能读取到有效Token",
  "data": null,
  "loginType": "login",
  "type": "-1"
}

// 缺少权限
{
  "code": 11012,
  "message": "缺少权限: user:delete",
  "data": null,
  "permission": "user:delete"
}

自定义 DAO

实现 SaTokenDao 接口即可对接任意存储:

import { Injectable } from '@nestjs/common';
import { SaTokenDao } from '@sa-token-nestjs';

@Injectable()
export class CustomDao implements SaTokenDao {
  async get(key: string): Promise<string | null> { /* ... */ }
  async set(key: string, value: string, timeout: number): Promise<void> { /* ... */ }
  async update(key: string, value: string): Promise<void> { /* ... */ }
  async delete(key: string): Promise<void> { /* ... */ }
  async getTimeout(key: string): Promise<number> { /* ... */ }
  async updateTimeout(key: string, timeout: number): Promise<void> { /* ... */ }
  async getObject(key: string): Promise<any> { /* ... */ }
  async setObject(key: string, object: any, timeout: number): Promise<void> { /* ... */ }
  async updateObject(key: string, object: any): Promise<void> { /* ... */ }
  async deleteObject(key: string): Promise<void> { /* ... */ }
  async getObjectTimeout(key: string): Promise<number> { /* ... */ }
  async updateObjectTimeout(key: string, timeout: number): Promise<void> { /* ... */ }
  async searchData(prefix: string, keyword: string, start: number, size: number, sortType: boolean): Promise<string[]> { /* ... */ }
}

项目结构

src/
├── auth/                    # 认证逻辑
│   ├── stp-logic.ts         # 核心鉴权引擎(StpLogic)
│   ├── stp-util.ts          # 便捷工具类(StpUtil)
│   └── sa-login-model.ts    # 登录参数模型
├── core/                    # 核心定义
│   ├── sa-token-config.ts   # 配置接口与默认值
│   └── constants.ts         # 常量与元数据键
├── dao/                     # 持久化层
│   ├── sa-token-dao.interface.ts  # DAO 接口定义
│   ├── memory-dao.ts               # 内存实现(默认)
│   └── redis-dao.ts                # Redis 实现
├── decorators/             # 装饰器
│   └── index.ts            # 鉴权注解 & 参数装饰器
├── exception/              # 异常体系
│   └── sa-token-exception.ts       # 异常类定义
├── filters/                # 过滤器
│   └── sa-token-exception.filter.ts # 全局异常过滤器
├── guards/                 # 守卫
│   └── sa-token.guard.ts   # 全局鉴权守卫
├── permission/             # 权限接口
│   └── stp-interface.ts    # 权限/角色数据源接口
├── router/                 # 路由拦截
│   ├── sa-router.ts        # 路由匹配引擎
│   └── sa-router.middleware.ts # 路由中间件
├── session/                # 会话管理
│   └── sa-session.ts       # Session 实现
├── token/                  # Token 策略
│   ├── token-strategy.interface.ts # 策略接口
│   ├── token-strategy-factory.ts   # 策略工厂
│   ├── uuid-strategy.ts            # UUID 策略
│   ├── simple-uuid-strategy.ts     # Simple UUID 策略
│   ├── random-strategy.ts          # Random 策略
│   └── jwt-strategy.ts             # JWT 策略
├── sa-token.module.ts      # 模块定义
└── index.ts                # 统一导出入口

开发

# 安装依赖
npm install

依赖要求

| 依赖 | 版本要求 | | -------------- | ------------------- | | Node.js | >= 16.0.0 | | @nestjs/common | ^9.0.0 || ^10.0.0 | | @nestjs/core | ^9.0.0 || ^10.0.0 | | uuid | ^9.0.0(内置依赖) | | ioredis | ^5.3.0(可选) | | jsonwebtoken | ^9.0.2(可选) |

联系本人

邮箱 [email protected]

未来规划

1 实现更多对齐java sa-token 2 完成api doc文档开发

License

MIT