@lark-apaas/nestjs-authzpaas 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,1191 @@
1
+ # nestjs-authzpaas
2
+
3
+ `nestjs-authzpaas` 是一个基于 NestJS 的权限管理模块,提供了完整的 RBAC(基于角色的访问控制)和 ABAC(基于属性的访问控制)解决方案。它基于 [CASL](https://casl.js.org/) 实现,支持角色检查、权限检查、环境检查等多种鉴权方式。
4
+
5
+ ## 目录
6
+
7
+ - [核心概念](#核心概念)
8
+ - [快速开始](#快速开始)
9
+ - [装饰器使用](#装饰器使用)
10
+ - [核心组件](#核心组件)
11
+ - [高级用法](#高级用法)
12
+ - [最佳实践](#最佳实践)
13
+
14
+ ## 核心概念
15
+
16
+ ### 权限模型
17
+
18
+ 本模块采用以下权限模型:
19
+
20
+ - **角色 (Role)**: 用户所属的角色,如 `admin`、`editor`、`viewer`
21
+ - **权限 (Permission)**: 对特定资源的操作权限,由 `actions` + `subject` 组成
22
+ - `actions`: 操作类型,如 `create`、`read`、`update`、`delete`、`manage`
23
+ - `subject`: 资源类型,如 `User`、`Task`、`Article`
24
+
25
+ ### 核心组件
26
+
27
+ | 组件 | 类型 | 说明 |
28
+ |------|------|------|
29
+ | `AuthZPaasGuard` | 全局守卫 | 自动拦截所有请求,执行鉴权检查 |
30
+ | `PermissionService` | 服务 | 提供权限获取、检查、缓存管理等功能 |
31
+ | `RolesMiddleware` | 中间件 | 自动获取并注入用户角色信息到请求上下文 |
32
+ | `CanRole` | 装饰器 | 声明式角色检查 |
33
+ | `CanPermission` | 装饰器 | 声明式权限检查 |
34
+ | `UserId` | 参数装饰器 | 获取当前用户ID |
35
+
36
+ ## 快速开始
37
+
38
+ ### 1. 安装依赖
39
+
40
+ ```bash
41
+ npm install @lark-apaas/nestjs-authzpaas
42
+ # 或
43
+ yarn add @lark-apaas/nestjs-authzpaas
44
+ ```
45
+
46
+ ### 2. 配置模块
47
+
48
+ 在 `app.module.ts` 中导入并配置 `AuthZPaasModule`:
49
+
50
+ ```typescript
51
+ import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
52
+ import { AuthZPaasModule, RolesMiddleware } from '@lark-apaas/nestjs-authzpaas';
53
+ import { UserContextMiddleware } from '@lark-apaas/fullstack-nestjs-core';
54
+
55
+ @Module({
56
+ imports: [
57
+ // 配置 AuthZPaas 模块
58
+ AuthZPaasModule.forRoot({
59
+ permissionApi: {
60
+ // 权限 API 配置
61
+ baseUrl: 'http://localhost:3000',
62
+ endpoint: '/mock-api/users/:userId/permissions',
63
+ timeout: 5000,
64
+ },
65
+ cache: {
66
+ ttl: 60, // 缓存时间(秒)
67
+ max: 100, // 最大缓存数量
68
+ enabled: true, // 启用缓存
69
+ },
70
+ // 高级配置选项
71
+ enableMockRole: false, // 是否启用角色模拟功能(默认 false)
72
+ isGlobal: true, // 全局模块
73
+ }),
74
+ ],
75
+ controllers: [/* ... */],
76
+ providers: [/* ... */],
77
+ })
78
+ export class AppModule implements NestModule {
79
+ configure(consumer: MiddlewareConsumer) {
80
+ // 1. UserContextMiddleware 必须在最前面,用于解析用户信息
81
+ consumer
82
+ .apply(UserContextMiddleware)
83
+ .forRoutes('*');
84
+
85
+ // 2. RolesMiddleware 用于自动获取用户角色
86
+ // 注意:对于不需要鉴权的接口可以配置排除
87
+ consumer
88
+ .apply(RolesMiddleware)
89
+ .exclude('mock-api/(.*)') // 排除权限权限 MOCK API
90
+ .forRoutes('*');
91
+ }
92
+ }
93
+ ```
94
+
95
+ ### 3. 异步配置(可选)
96
+
97
+ 如果需要从 `ConfigService` 获取配置:
98
+
99
+ ```typescript
100
+ import { ConfigModule, ConfigService } from '@nestjs/config';
101
+
102
+ @Module({
103
+ imports: [
104
+ ConfigModule.forRoot(),
105
+ AuthZPaasModule.forRootAsync({
106
+ imports: [ConfigModule],
107
+ inject: [ConfigService],
108
+ useFactory: async (configService: ConfigService) => ({
109
+ permissionApi: {
110
+ baseUrl: configService.get('PERMISSION_API_URL'),
111
+ endpoint: configService.get('PERMISSION_API_ENDPOINT'),
112
+ timeout: 5000,
113
+ },
114
+ cache: {
115
+ ttl: configService.get('CACHE_TTL', 300),
116
+ max: configService.get('CACHE_MAX', 1000),
117
+ enabled: true,
118
+ },
119
+ }),
120
+ }),
121
+ ],
122
+ })
123
+ export class AppModule {}
124
+ ```
125
+
126
+ ## 配置选项详解
127
+
128
+ ### 核心配置
129
+
130
+ | 配置项 | 类型 | 默认值 | 说明 |
131
+ |--------|------|--------|------|
132
+ | `permissionApi` | `PermissionApiConfig` | - | 权限 API 配置(必需) |
133
+ | `cache` | `CacheConfig` | `{ ttl: 300, max: 1000, enabled: true }` | 缓存配置 |
134
+ | `isGlobal` | `boolean` | `true` | 是否注册为全局模块 |
135
+
136
+ ### 高级配置
137
+
138
+ | 配置项 | 类型 | 默认值 | 说明 |
139
+ |--------|------|--------|------|
140
+ | `enableMockRole` | `boolean` | `false` | 是否启用角色模拟功能 |
141
+
142
+ #### enableMockRole
143
+
144
+ 启用角色模拟功能,允许通过 Cookie 模拟用户角色,主要用于开发和测试环境。
145
+
146
+ ```typescript
147
+ AuthZPaasModule.forRoot({
148
+ enableMockRole: true, // 启用角色模拟
149
+ })
150
+ ```
151
+
152
+ **功能特性**
153
+
154
+ - 通过 Cookie `mockRoles` 设置模拟角色
155
+ - 支持多个角色:`admin,editor,viewer`
156
+ - 仅在开发/测试环境使用,生产环境应关闭
157
+
158
+ **使用示例**
159
+
160
+ ```typescript
161
+ // 1. 开启角色模拟
162
+ POST /api/permissions/mock/enable
163
+ {
164
+ "roles": ["admin", "editor"]
165
+ }
166
+
167
+ // 2. 使用 @MockRoles 装饰器获取模拟角色
168
+ @Get('test')
169
+ testMockRoles(@MockRoles() mockRoles: string[]) {
170
+ return { mockRoles };
171
+ }
172
+
173
+ // 3. 关闭角色模拟
174
+ POST /api/permissions/mock/disable
175
+ ```
176
+
177
+ ## 装饰器使用
178
+
179
+ > 💡 **装饰器是最常用的权限控制方式**,推荐优先使用装饰器来进行权限检查,简单、直观、声明式。
180
+
181
+ ### 1. CanRole - 角色检查
182
+
183
+ 使用 `@CanRole()` 装饰器检查用户角色。
184
+
185
+ #### 示例 1: 单个角色
186
+
187
+ ```typescript
188
+ import { Controller, Get } from '@nestjs/common';
189
+ import { CanRole } from '@lark-apaas/nestjs-authzpaas';
190
+
191
+ @Controller('demo')
192
+ export class DemoController {
193
+ // 需要 admin 角色
194
+ @CanRole(['admin'])
195
+ @Get('admin-only')
196
+ adminOnly() {
197
+ return {
198
+ message: '✅ 这是管理员专属接口',
199
+ timestamp: new Date(),
200
+ };
201
+ }
202
+ }
203
+ ```
204
+
205
+ #### 示例 2: 多个角色(OR 逻辑,默认)
206
+
207
+ ```typescript
208
+ @Controller('content')
209
+ export class ContentController {
210
+ // 拥有 admin 或 editor 任一角色即可访问
211
+ @CanRole(['admin', 'editor'])
212
+ @Get('manage')
213
+ manage() {
214
+ return '管理内容';
215
+ }
216
+ }
217
+ ```
218
+
219
+ #### 示例 3: 多个角色(AND 逻辑)
220
+
221
+ ```typescript
222
+ @Controller('sensitive')
223
+ export class SensitiveController {
224
+ // 需要同时拥有 admin 和 superuser 角色
225
+ @CanRole(['admin', 'superuser'], true)
226
+ @Get('dangerous')
227
+ dangerousOperation() {
228
+ return '危险操作';
229
+ }
230
+ }
231
+ ```
232
+
233
+ ### 2. CanPermission - 权限检查
234
+
235
+ 使用 `@CanPermission()` 装饰器检查用户对特定资源的操作权限。
236
+
237
+ #### 示例 1: 单个操作检查
238
+
239
+ ```typescript
240
+ import { Controller, Get } from '@nestjs/common';
241
+ import { CanPermission } from '@lark-apaas/nestjs-authzpaas';
242
+
243
+ @Controller('demo')
244
+ export class DemoController {
245
+ // 需要对 User 资源有 read 权限
246
+ @CanPermission([{ actions: ['read'], subject: 'User' }])
247
+ @Get('read-user')
248
+ readUser() {
249
+ return {
250
+ message: '✅ 用户读取成功(需要 read 权限)',
251
+ };
252
+ }
253
+
254
+ // 需要对 User 资源有 create 权限
255
+ @CanPermission([{ actions: ['create'], subject: 'User' }])
256
+ @Get('create-user')
257
+ createUser() {
258
+ return {
259
+ message: '✅ 用户创建成功(需要 create 权限)',
260
+ };
261
+ }
262
+ }
263
+ ```
264
+
265
+ #### 示例 2: 多个操作检查(AND 逻辑,默认)
266
+
267
+ ```typescript
268
+ @Controller('articles')
269
+ export class ArticleController {
270
+ // 需要同时拥有 read 和 update 权限
271
+ @CanPermission([{
272
+ actions: ['read', 'update'],
273
+ subject: 'Article',
274
+ requireAll: true // 默认为 true,可省略
275
+ }])
276
+ @Get('publish')
277
+ publish() {
278
+ return '发布文章';
279
+ }
280
+ }
281
+ ```
282
+
283
+ #### 示例 3: 多个操作检查(OR 逻辑)
284
+
285
+ ```typescript
286
+ @Controller('articles')
287
+ export class ArticleController {
288
+ // 拥有 read 或 preview 任一权限即可
289
+ @CanPermission([{
290
+ actions: ['read', 'preview'],
291
+ subject: 'Article',
292
+ requireAll: false // OR 逻辑
293
+ }])
294
+ @Get('preview')
295
+ preview() {
296
+ return '预览文章';
297
+ }
298
+ }
299
+ ```
300
+
301
+ #### 示例 4: 多个资源权限检查
302
+
303
+ ```typescript
304
+ @Controller('articles')
305
+ export class ArticleController {
306
+ // 需要同时满足:能读文章 AND 能创建评论
307
+ @CanPermission([
308
+ { actions: ['read'], subject: 'Article' },
309
+ { actions: ['create'], subject: 'Comment' }
310
+ ])
311
+ @Get('comment')
312
+ addComment() {
313
+ return '添加评论';
314
+ }
315
+
316
+ // 需要同时满足:能读文章 AND (能更新或删除评论)
317
+ @CanPermission([
318
+ { actions: ['read'], subject: 'Article' },
319
+ { actions: ['update', 'delete'], subject: 'Comment', requireAll: false }
320
+ ])
321
+ @Get('manage-comment')
322
+ manageComment() {
323
+ return '管理评论';
324
+ }
325
+ }
326
+ ```
327
+
328
+ ### 3. UserId - 获取用户ID
329
+
330
+ 使用 `@UserId()` 参数装饰器获取当前用户ID。
331
+
332
+ #### 示例 1: 基本用法
333
+
334
+ ```typescript
335
+ import { Controller, Get } from '@nestjs/common';
336
+ import { UserId } from '@lark-apaas/nestjs-authzpaas';
337
+
338
+ @Controller('demo')
339
+ export class DemoController {
340
+ @Get('profile')
341
+ getProfile(@UserId() userId: string) {
342
+ return {
343
+ message: '获取用户资料',
344
+ userId,
345
+ };
346
+ }
347
+ }
348
+ ```
349
+
350
+ #### 示例 2: 结合 PermissionService 使用
351
+
352
+ ```typescript
353
+ import { Controller, Get } from '@nestjs/common';
354
+ import { UserId, PermissionService } from '@lark-apaas/nestjs-authzpaas';
355
+
356
+ @Controller('demo')
357
+ export class DemoController {
358
+ constructor(private readonly permissionService: PermissionService) {}
359
+
360
+ @Get('check-permission')
361
+ async checkPermission(@UserId() userId: string) {
362
+ // 手动检查权限
363
+ const hasPermission = await this.permissionService.checkPermissions(
364
+ [{ actions: ['read'], subject: 'User' }],
365
+ userId
366
+ );
367
+
368
+ return {
369
+ message: '权限检查成功',
370
+ userId,
371
+ hasPermission,
372
+ };
373
+ }
374
+ }
375
+ ```
376
+
377
+ #### 示例 3: 使用 CASL Ability
378
+
379
+ ```typescript
380
+ import { Controller, Get } from '@nestjs/common';
381
+ import { UserId, PermissionService, ROLE_SUBJECT } from '@lark-apaas/nestjs-authzpaas';
382
+
383
+ @Controller('demo')
384
+ export class DemoController {
385
+ constructor(private readonly permissionService: PermissionService) {}
386
+
387
+ @Get('ability')
388
+ async getAbility(@UserId() userId: string) {
389
+ const ability = await this.permissionService.getAbility(userId);
390
+
391
+ // 使用 CASL Ability 检查权限
392
+ const canReadUser = ability.can('read', 'User');
393
+ const canWriteTask = ability.can('write', 'Task');
394
+ const isAdmin = ability.can('admin', ROLE_SUBJECT);
395
+
396
+ return {
397
+ userId,
398
+ permissions: {
399
+ canReadUser,
400
+ canWriteTask,
401
+ isAdmin,
402
+ },
403
+ };
404
+ }
405
+ }
406
+ ```
407
+
408
+ ### 4. MockRoles - 角色模拟
409
+
410
+ 使用 `@MockRoles()` 参数装饰器获取模拟角色,仅在 `enableMockRole: true` 时有效。
411
+
412
+ #### 示例 1: 基本用法
413
+
414
+ ```typescript
415
+ import { Controller, Get } from '@nestjs/common';
416
+ import { MockRoles } from '@lark-apaas/nestjs-authzpaas';
417
+
418
+ @Controller('demo')
419
+ export class DemoController {
420
+ @Get('mock-roles')
421
+ getMockRoles(@MockRoles() mockRoles: string[]) {
422
+ return {
423
+ message: '获取模拟角色',
424
+ mockRoles: mockRoles || [],
425
+ hasMockRoles: !!mockRoles,
426
+ };
427
+ }
428
+ }
429
+ ```
430
+
431
+ #### 示例 2: 结合权限检查
432
+
433
+ ```typescript
434
+ @Controller('admin')
435
+ export class AdminController {
436
+ @Get('test')
437
+ @CanRole(['admin']) // 会优先使用模拟角色进行验证
438
+ testWithMockRoles(@MockRoles() mockRoles: string[]) {
439
+ return {
440
+ message: '管理员测试接口',
441
+ mockRoles,
442
+ };
443
+ }
444
+ }
445
+ ```
446
+
447
+ ### 5. 装饰器组合使用
448
+
449
+ 可以组合使用多个装饰器实现复杂的鉴权需求。
450
+
451
+ #### 示例 1: 角色 + 权限
452
+
453
+ ```typescript
454
+ import { Controller, Get } from '@nestjs/common';
455
+ import { CanRole, CanPermission } from '@lark-apaas/nestjs-authzpaas';
456
+
457
+ @Controller('posts')
458
+ export class PostController {
459
+ // 需要 editor 角色 + 对 Post 有 delete 权限
460
+ @CanRole(['editor'])
461
+ @CanPermission([{ actions: ['delete'], subject: 'Post' }])
462
+ @Get('delete')
463
+ delete() {
464
+ return '删除帖子';
465
+ }
466
+ }
467
+ ```
468
+
469
+ #### 示例 2: 多层权限检查
470
+
471
+ ```typescript
472
+ @Controller('tasks')
473
+ export class TasksController {
474
+ // 需要 admin 或 manager 角色 + Task 的 manage 权限
475
+ @CanRole(['admin', 'manager'])
476
+ @CanPermission([{ actions: ['manage'], subject: 'Task' }])
477
+ @Get('manage')
478
+ manageTasks() {
479
+ return '管理任务';
480
+ }
481
+ }
482
+ ```
483
+
484
+ ## 核心组件
485
+
486
+ > 📚 **核心组件是装饰器的底层实现**,了解核心组件有助于深入理解权限系统的工作原理。
487
+
488
+ ### AuthZPaasGuard
489
+
490
+ `AuthZPaasGuard` 是一个全局守卫,会自动拦截所有请求并执行以下操作:
491
+
492
+ 1. **提取用户ID**: 从 `req.userContext.userId` 中提取用户ID
493
+ 2. **角色检查**: 如果使用了 `@CanRole()`,检查用户角色
494
+ 3. **权限检查**: 如果使用了 `@CanPermission()`,检查用户权限
495
+ 4. **环境检查**: 如果使用了 `@RequireEnvironment()`,检查环境条件
496
+ 5. **注入权限数据**: 将权限数据附加到 `req.userPermissions`
497
+
498
+ **工作流程**:
499
+
500
+ ```yaml
501
+ 请求
502
+
503
+ AuthZPaasGuard
504
+
505
+ 提取 userId
506
+
507
+ 检查角色/权限
508
+
509
+ 注入权限数据
510
+
511
+ Controller
512
+ ```
513
+
514
+ **配置**: `AuthZPaasGuard` 会在模块导入时自动注册为全局守卫,无需手动配置。
515
+
516
+ ### PermissionService
517
+
518
+ `PermissionService` 提供权限管理的核心功能。
519
+
520
+ #### 主要方法
521
+
522
+ ```typescript
523
+ import { Injectable } from '@nestjs/common';
524
+ import { PermissionService } from '@lark-apaas/nestjs-authzpaas';
525
+
526
+ @Injectable()
527
+ export class YourService {
528
+ constructor(private readonly permissionService: PermissionService) {}
529
+
530
+ async checkUserPermission(userId: string) {
531
+ // 1. 获取用户权限数据(带缓存)
532
+ const permissionData = await this.permissionService.getUserPermissions(userId);
533
+ console.log('用户角色:', permissionData.roles);
534
+ console.log('用户权限:', permissionData.permissions);
535
+
536
+ // 2. 获取用户的 CASL Ability 实例
537
+ const ability = await this.permissionService.getAbility(userId);
538
+
539
+ // 使用 Ability 检查权限
540
+ const canReadUser = ability.can('read', 'User');
541
+ const canCreateTask = ability.can('create', 'Task');
542
+ const canManageAll = ability.can('manage', 'all');
543
+
544
+ // 3. 检查权限要求
545
+ const hasPermission = await this.permissionService.checkPermissions(
546
+ [{ actions: ['read'], subject: 'User' }],
547
+ userId
548
+ );
549
+
550
+ // 4. 清除用户缓存(权限变更时)
551
+ this.permissionService.clearUserCache(userId);
552
+
553
+ // 5. 清除所有缓存
554
+ this.permissionService.clearAllCache();
555
+
556
+ // 6. 获取缓存统计
557
+ const stats = this.permissionService.getCacheStats();
558
+ console.log('缓存统计:', stats);
559
+ }
560
+ }
561
+ ```
562
+
563
+ #### 权限缓存机制
564
+
565
+ - **自动缓存**: 权限数据和 Ability 实例会自动缓存
566
+ - **防重复请求**: 同时发起的相同请求会共享同一个 Promise
567
+ - **TTL 控制**: 通过配置的 `ttl` 自动过期
568
+ - **手动清除**: 可以调用 `clearUserCache()` 或 `clearAllCache()`
569
+
570
+ ### RolesMiddleware
571
+
572
+ `RolesMiddleware` 是一个中间件,用于在请求处理前自动获取用户角色信息。
573
+
574
+ #### 工作原理
575
+
576
+ 1. 从 `req.userContext.userId` 中提取用户ID
577
+ 2. 调用 `PermissionService.getUserPermissions()` 获取权限数据
578
+ 3. 将角色列表注入到 `req.userContext.userRoles`
579
+ 4. 如果获取失败,记录警告但不阻塞请求(由 Guard 处理)
580
+
581
+ #### 配置示例
582
+
583
+ ```typescript
584
+ export class AppModule implements NestModule {
585
+ configure(consumer: MiddlewareConsumer) {
586
+ consumer
587
+ .apply(RolesMiddleware)
588
+ .exclude('mock-api/(.*)') // 排除特定路由
589
+ .forRoutes('*');
590
+ }
591
+ }
592
+ ```
593
+
594
+ #### 类型扩展
595
+
596
+ `RolesMiddleware` 会扩展 Express Request 类型:
597
+
598
+ ```typescript
599
+ declare global {
600
+ namespace Express {
601
+ interface Request {
602
+ userContext: {
603
+ userId?: string;
604
+ tenantId?: number;
605
+ appId?: string;
606
+ userRoles?: string[]; // RolesMiddleware 注入
607
+ }
608
+ }
609
+ }
610
+ }
611
+ ```
612
+
613
+ ## 高级用法
614
+
615
+ ### 1. 手动权限检查
616
+
617
+ 在某些场景下,你可能需要在业务逻辑中手动检查权限,而不是使用装饰器:
618
+
619
+ ```typescript
620
+ import { Injectable } from '@nestjs/common';
621
+ import { PermissionService } from '@lark-apaas/nestjs-authzpaas';
622
+
623
+ @Injectable()
624
+ export class TasksService {
625
+ constructor(private readonly permissionService: PermissionService) {}
626
+
627
+ async processTask(userId: string, taskId: string) {
628
+ // 获取用户权限数据
629
+ const permissionData = await this.permissionService.getUserPermissions(userId);
630
+
631
+ // 检查权限
632
+ const canUpdate = await this.permissionService.checkPermissions(
633
+ [{ actions: ['update'], subject: 'Task' }],
634
+ userId
635
+ );
636
+
637
+ if (!canUpdate) {
638
+ throw new Error('没有权限');
639
+ }
640
+
641
+ // 执行业务逻辑
642
+ return '任务处理成功';
643
+ }
644
+ }
645
+ ```
646
+
647
+ ### 2. 使用 CASL Ability 进行细粒度控制
648
+
649
+ CASL Ability 提供了更灵活的权限检查方式:
650
+
651
+ ```typescript
652
+ import { Injectable } from '@nestjs/common';
653
+ import { PermissionService, ROLE_SUBJECT } from '@lark-apaas/nestjs-authzpaas';
654
+
655
+ @Injectable()
656
+ export class ArticleService {
657
+ constructor(private readonly permissionService: PermissionService) {}
658
+
659
+ async canUserEditArticle(userId: string, articleId: string) {
660
+ const ability = await this.permissionService.getAbility(userId);
661
+
662
+ // 检查是否可以更新文章
663
+ if (ability.can('update', 'Article')) {
664
+ return true;
665
+ }
666
+
667
+ // 或者检查是否有 manage all 权限
668
+ if (ability.can('manage', 'all')) {
669
+ return true;
670
+ }
671
+
672
+ // 检查角色
673
+ if (ability.can('admin', ROLE_SUBJECT)) {
674
+ return true;
675
+ }
676
+
677
+ return false;
678
+ }
679
+ }
680
+ ```
681
+
682
+ ### 3. 权限缓存管理
683
+
684
+ 当用户权限发生变更时,需要清除缓存:
685
+
686
+ ```typescript
687
+ import { Injectable } from '@nestjs/common';
688
+ import { PermissionService } from '@lark-apaas/nestjs-authzpaas';
689
+
690
+ @Injectable()
691
+ export class UserManagementService {
692
+ constructor(private readonly permissionService: PermissionService) {}
693
+
694
+ async updateUserRole(userId: string, newRole: string) {
695
+ // 1. 更新用户角色(调用你的业务逻辑)
696
+ await this.updateRoleInDatabase(userId, newRole);
697
+
698
+ // 2. 清除该用户的权限缓存
699
+ this.permissionService.clearUserCache(userId);
700
+
701
+ return '角色更新成功';
702
+ }
703
+
704
+ async batchUpdatePermissions() {
705
+ // 批量更新权限后,清除所有缓存
706
+ this.permissionService.clearAllCache();
707
+
708
+ return '权限批量更新成功';
709
+ }
710
+
711
+ private async updateRoleInDatabase(userId: string, newRole: string) {
712
+ // 实现你的数据库更新逻辑
713
+ }
714
+ }
715
+ ```
716
+
717
+ ### 4. 自定义权限 API 响应处理
718
+
719
+ 如果你的权限 API 返回的数据格式不同,可以通过自定义逻辑转换:
720
+
721
+ ```typescript
722
+ // 你的权限 API 应该返回以下格式:
723
+ interface PermissionApiResponse {
724
+ userId: string;
725
+ roles: string[]; // 角色列表,如 ['admin', 'editor']
726
+ permissions: Array<{
727
+ id: string;
728
+ name: string;
729
+ sub: string; // 资源类型,如 'User', 'Task'
730
+ actions: string[]; // 操作列表,如 ['create', 'read', 'update']
731
+ }>;
732
+ fetchedAt?: Date;
733
+ }
734
+ ```
735
+
736
+ ## 权限 API 规范
737
+
738
+ ### 请求格式
739
+
740
+ 权限 API 的 endpoint 配置支持动态参数替换:
741
+
742
+ ```typescript
743
+ AuthZPaasModule.forRoot({
744
+ permissionApi: {
745
+ baseUrl: 'http://localhost:3000',
746
+ endpoint: '/mock-api/users/:userId/permissions', // :userId 会被替换
747
+ timeout: 5000,
748
+ },
749
+ })
750
+ ```
751
+
752
+ ### 响应格式
753
+
754
+ 您的权限 API 必须返回以下格式的 JSON 数据:
755
+
756
+ ```json
757
+ {
758
+ "userId": "user-admin",
759
+ "roles": ["admin_role", "editor_role"],
760
+ "permissions": [
761
+ {
762
+ "id": "1",
763
+ "name": "user.create",
764
+ "sub": "User",
765
+ "actions": ["create"]
766
+ },
767
+ {
768
+ "id": "2",
769
+ "name": "user.read",
770
+ "sub": "User",
771
+ "actions": ["read"]
772
+ },
773
+ {
774
+ "id": "3",
775
+ "name": "task.manage",
776
+ "sub": "Task",
777
+ "actions": ["manage"]
778
+ }
779
+ ],
780
+ "fetchedAt": "2024-01-01T00:00:00.000Z"
781
+ }
782
+ ```
783
+
784
+ ### 字段说明
785
+
786
+ - `userId`: 用户ID(必需)
787
+ - `roles`: 角色名称列表(必需)
788
+ - 可以是字符串数组 `['admin', 'editor']`
789
+ - 或角色对象数组(会自动提取 `name` 字段)
790
+ - `permissions`: 权限列表(必需)
791
+ - `id`: 权限ID
792
+ - `name`: 权限名称
793
+ - `sub`: 资源类型(如 `User`、`Task`、`Article`)
794
+ - `actions`: 操作列表(如 `['create', 'read', 'update', 'delete']` 或 `['manage']`)
795
+ - `fetchedAt`: 获取时间(可选,SDK 会自动添加)
796
+
797
+ ### Mock 权限 API 示例
798
+
799
+ 参考 `node-demo` 中的实现:
800
+
801
+ ```typescript
802
+ import { Controller, Get, Param } from '@nestjs/common';
803
+ import { Public, Permission, UserRole } from '@lark-apaas/nestjs-authzpaas';
804
+
805
+ @Controller('mock-api/users')
806
+ @Public() // 重要:跳过 AuthZPaasGuard,避免循环调用
807
+ export class MockPermissionController {
808
+ // 模拟用户权限数据库
809
+ private readonly mockPermissions: Record<string, {
810
+ roles: UserRole[],
811
+ permissions: Permission[]
812
+ }> = {
813
+ 'user-admin': {
814
+ roles: [
815
+ { id: '1', name: 'admin', description: '管理员' },
816
+ ],
817
+ permissions: [
818
+ { id: '1', name: 'user.create', sub: 'User', actions: ['create'] },
819
+ { id: '2', name: 'user.read', sub: 'User', actions: ['read'] },
820
+ { id: '3', name: 'task.manage', sub: 'Task', actions: ['manage'] },
821
+ ],
822
+ },
823
+ 'user-normal': {
824
+ roles: [
825
+ { id: '2', name: 'reader', description: '只读用户' },
826
+ ],
827
+ permissions: [
828
+ { id: '4', name: 'user.read', sub: 'User', actions: ['read'] },
829
+ { id: '5', name: 'task.read', sub: 'Task', actions: ['read'] },
830
+ ],
831
+ },
832
+ };
833
+
834
+ @Get(':userId/permissions')
835
+ getUserPermissions(@Param('userId') userId: string) {
836
+ const permissions = this.mockPermissions[userId] || this.mockPermissions['user-guest'];
837
+
838
+ return {
839
+ userId,
840
+ ...permissions,
841
+ fetchedAt: new Date(),
842
+ };
843
+ }
844
+ }
845
+ ```
846
+
847
+ ## 错误处理
848
+
849
+ ### 1. 使用内置异常过滤器
850
+
851
+ 模块提供了 `AuthZPaasExceptionFilter` 用于统一处理权限异常:
852
+
853
+ ```typescript
854
+ import { Module } from '@nestjs/common';
855
+ import { APP_FILTER } from '@nestjs/core';
856
+ import { AuthZPaasExceptionFilter } from '@lark-apaas/nestjs-authzpaas';
857
+
858
+ @Module({
859
+ providers: [
860
+ {
861
+ provide: APP_FILTER,
862
+ useClass: AuthZPaasExceptionFilter,
863
+ },
864
+ ],
865
+ })
866
+ export class AppModule {}
867
+ ```
868
+
869
+ ### 2. 自定义异常处理
870
+
871
+ ```typescript
872
+ import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
873
+ import { PermissionDeniedException } from '@lark-apaas/nestjs-authzpaas';
874
+
875
+ @Catch(PermissionDeniedException)
876
+ export class CustomPermissionExceptionFilter implements ExceptionFilter {
877
+ catch(exception: PermissionDeniedException, host: ArgumentsHost) {
878
+ const ctx = host.switchToHttp();
879
+ const response = ctx.getResponse();
880
+
881
+ response.status(403).json({
882
+ statusCode: 403,
883
+ message: exception.message,
884
+ error: 'Forbidden',
885
+ timestamp: new Date().toISOString(),
886
+ });
887
+ }
888
+ }
889
+ ```
890
+
891
+ ### 3. 常见异常类型
892
+
893
+ | 异常 | 说明 | HTTP 状态码 |
894
+ |------|------|------------|
895
+ | `PermissionDeniedException.unauthenticated()` | 未认证(无 userId) | 403 |
896
+ | `PermissionDeniedException.roleRequired()` | 缺少必需的角色 | 403 |
897
+ | `PermissionDeniedException.permissionDenied()` | 缺少必需的权限 | 403 |
898
+ | `PermissionDeniedException.environmentRestricted()` | 环境限制(如 IP 白名单) | 403 |
899
+
900
+ ## 最佳实践
901
+
902
+ ### 1. 使用有意义的资源和操作名称
903
+
904
+ ```typescript
905
+ // ✅ 好的做法:清晰的资源和操作命名
906
+ @CanPermission([{ actions: ['create'], subject: 'Article' }])
907
+ @CanPermission([{ actions: ['update'], subject: 'User' }])
908
+ @CanPermission([{ actions: ['delete'], subject: 'Task' }])
909
+
910
+ // ❌ 避免使用:模糊的命名
911
+ @CanPermission([{ actions: ['action1'], subject: 'resource1' }])
912
+ ```
913
+
914
+ ### 2. 遵循 CRUD 标准操作
915
+
916
+ 建议使用标准的 CRUD 操作名称:
917
+
918
+ - `create`: 创建资源
919
+ - `read`: 读取资源
920
+ - `update`: 更新资源
921
+ - `delete`: 删除资源
922
+ - `manage`: 完全管理权限(包含所有操作)
923
+
924
+ ```typescript
925
+ // 推荐的权限设计
926
+ @CanPermission([{ actions: ['read'], subject: 'User' }])
927
+ @CanPermission([{ actions: ['manage'], subject: 'Task' }]) // manage 包含所有操作
928
+ ```
929
+
930
+ ### 3. 合理使用 requireAll
931
+
932
+ - **默认 `requireAll: true`**: 适用于需要同时满足多个权限的场景(AND 逻辑)
933
+ - **`requireAll: false`**: 适用于满足任一权限即可的场景(OR 逻辑)
934
+
935
+ ```typescript
936
+ // AND 逻辑:需要同时拥有 read 和 update 权限
937
+ @CanPermission([{
938
+ actions: ['read', 'update'],
939
+ subject: 'Article',
940
+ requireAll: true // 默认值,可省略
941
+ }])
942
+
943
+ // OR 逻辑:拥有 read 或 preview 任一权限即可
944
+ @CanPermission([{
945
+ actions: ['read', 'preview'],
946
+ subject: 'Article',
947
+ requireAll: false
948
+ }])
949
+ ```
950
+
951
+ ### 4. 权限粒度设计
952
+
953
+ 细粒度权限优于粗粒度权限:
954
+
955
+ ```typescript
956
+ // ✅ 推荐:细粒度权限,更精确的控制
957
+ @CanPermission([{ actions: ['publish'], subject: 'Article' }])
958
+ @CanPermission([{ actions: ['archive'], subject: 'Article' }])
959
+
960
+ // ⚠️ 谨慎使用:粗粒度权限,权限范围过大
961
+ @CanPermission([{ actions: ['manage'], subject: 'Article' }])
962
+ ```
963
+
964
+ ### 5. 缓存策略
965
+
966
+ 根据业务需求调整缓存配置:
967
+
968
+ ```typescript
969
+ AuthZPaasModule.forRoot({
970
+ cache: {
971
+ ttl: 60, // 开发环境:较短的缓存时间,便于测试
972
+ // ttl: 300, // 生产环境:较长的缓存时间,提升性能
973
+ max: 1000, // 根据用户量调整
974
+ enabled: true, // 生产环境建议开启
975
+ },
976
+ })
977
+ ```
978
+
979
+ **重要**:权限变更后务必清除缓存
980
+
981
+ ```typescript
982
+ // 单用户权限变更
983
+ this.permissionService.clearUserCache(userId);
984
+
985
+ // 批量权限变更
986
+ this.permissionService.clearAllCache();
987
+ ```
988
+
989
+ ### 6. 中间件配置顺序
990
+
991
+ 确保中间件按正确的顺序配置:
992
+
993
+ ```typescript
994
+ export class AppModule implements NestModule {
995
+ configure(consumer: MiddlewareConsumer) {
996
+ // 1️⃣ 第一步:UserContextMiddleware 提取用户信息
997
+ consumer.apply(UserContextMiddleware).forRoutes('*');
998
+
999
+ // 2️⃣ 第二步:RolesMiddleware 获取用户角色
1000
+ // 注意:必须排除权限 API 路由
1001
+ consumer
1002
+ .apply(RolesMiddleware)
1003
+ .exclude('mock-api/(.*)')
1004
+ .forRoutes('*');
1005
+ }
1006
+ }
1007
+ ```
1008
+
1009
+ ### 7. 公开接口使用 @Public()
1010
+
1011
+ 对于不需要鉴权的接口,务必使用 `@Public()` 装饰器:
1012
+
1013
+ ```typescript
1014
+ // ✅ 好的做法
1015
+ @Controller('health')
1016
+ export class HealthController {
1017
+ @Get()
1018
+ @Public()
1019
+ check() {
1020
+ return { status: 'ok' };
1021
+ }
1022
+ }
1023
+
1024
+ // ❌ 避免:未标记 @Public(),会触发鉴权检查
1025
+ @Controller('health')
1026
+ export class HealthController {
1027
+ @Get()
1028
+ check() {
1029
+ return { status: 'ok' };
1030
+ }
1031
+ }
1032
+ ```
1033
+
1034
+ ### 8. 组合使用装饰器
1035
+
1036
+ 对于复杂的权限需求,可以组合使用多个装饰器:
1037
+
1038
+ ```typescript
1039
+ @Controller('admin')
1040
+ export class AdminController {
1041
+ // 角色检查 + 权限检查
1042
+ @CanRole(['admin'])
1043
+ @CanPermission([{ actions: ['manage'], subject: 'System' }])
1044
+ @Get('system')
1045
+ manageSystem() {
1046
+ return '系统管理';
1047
+ }
1048
+ }
1049
+ ```
1050
+
1051
+ ### 9. 使用 TypeScript 类型提示
1052
+
1053
+ 充分利用 TypeScript 的类型系统:
1054
+
1055
+ ```typescript
1056
+ import {
1057
+ CanPermission,
1058
+ CanRole,
1059
+ PermissionService,
1060
+ ROLE_SUBJECT
1061
+ } from '@lark-apaas/nestjs-authzpaas';
1062
+
1063
+ // TypeScript 会自动提供代码提示和类型检查
1064
+ ```
1065
+
1066
+ ### 10. 高级配置使用建议
1067
+
1068
+ #### enableMockRole 使用场景
1069
+
1070
+ ```typescript
1071
+ // ✅ 开发环境:启用角色模拟
1072
+ AuthZPaasModule.forRoot({
1073
+ enableMockRole: process.env.NODE_ENV === 'development',
1074
+ })
1075
+
1076
+ // ✅ 测试环境:便于自动化测试
1077
+ AuthZPaasModule.forRoot({
1078
+ enableMockRole: process.env.NODE_ENV === 'test',
1079
+ })
1080
+
1081
+ // ❌ 生产环境:必须关闭
1082
+ AuthZPaasModule.forRoot({
1083
+ enableMockRole: false, // 生产环境安全考虑
1084
+ })
1085
+ ```
1086
+
1087
+ **使用建议**:
1088
+ - 开发环境:便于测试不同角色权限
1089
+ - 测试环境:自动化测试时模拟不同用户
1090
+ - 生产环境:必须关闭,避免安全风险
1091
+
1092
+ ### 11. 测试建议
1093
+
1094
+ 编写单元测试时,可以 mock `PermissionService`:
1095
+
1096
+ ```typescript
1097
+ describe('TasksController', () => {
1098
+ let controller: TasksController;
1099
+ let permissionService: PermissionService;
1100
+
1101
+ beforeEach(async () => {
1102
+ const module = await Test.createTestingModule({
1103
+ controllers: [TasksController],
1104
+ providers: [
1105
+ {
1106
+ provide: PermissionService,
1107
+ useValue: {
1108
+ getUserPermissions: jest.fn(),
1109
+ getAbility: jest.fn(),
1110
+ checkPermissions: jest.fn(),
1111
+ },
1112
+ },
1113
+ ],
1114
+ }).compile();
1115
+
1116
+ controller = module.get<TasksController>(TasksController);
1117
+ permissionService = module.get<PermissionService>(PermissionService);
1118
+ });
1119
+
1120
+ it('should check permissions', async () => {
1121
+ jest.spyOn(permissionService, 'checkPermissions').mockResolvedValue(true);
1122
+
1123
+ // 测试逻辑...
1124
+ });
1125
+ });
1126
+ ```
1127
+
1128
+ ## 完整示例
1129
+
1130
+ 查看 `examples/node-demo` 目录获取完整的工作示例,包括:
1131
+
1132
+ - ✅ 模块配置和中间件设置
1133
+ - ✅ Mock 权限 API 实现
1134
+ - ✅ 角色和权限装饰器使用
1135
+ - ✅ 手动权限检查
1136
+ - ✅ CASL Ability 使用
1137
+ - ✅ 测试脚本
1138
+
1139
+ 运行示例:
1140
+
1141
+ ```bash
1142
+ cd examples/node-demo
1143
+ yarn install
1144
+ yarn start
1145
+
1146
+ # 测试不同用户的权限
1147
+ curl -H "x-user-id: user-admin" http://localhost:3000/demo/admin-only
1148
+ curl -H "x-user-id: user-normal" http://localhost:3000/demo/reader-only
1149
+ ```
1150
+
1151
+ ## 常见问题
1152
+
1153
+ ### Q: 如何自定义提取 userId 的逻辑?
1154
+
1155
+ A: 可以自定义 `UserContextMiddleware` 或扩展 `AuthZPaasGuard`。通常在 `UserContextMiddleware` 中从 JWT、Session 或 Header 中提取用户信息并设置到 `req.userContext.userId`。
1156
+
1157
+ ### Q: manage 权限和其他权限的关系?
1158
+
1159
+ A: `manage` 是特殊的操作类型,在 CASL 中表示"所有权限"。如果用户有某个资源的 `manage` 权限,则自动拥有该资源的所有操作权限(`create`、`read`、`update`、`delete` 等)。
1160
+
1161
+ ### Q: 如何实现数据级权限控制(如只能访问自己创建的数据)?
1162
+
1163
+ A: 可以结合 CASL 的 conditions 功能和业务逻辑实现。在 `PermissionService` 中手动检查时,可以传入资源实例进行细粒度验证。
1164
+
1165
+ ### Q: 性能优化建议?
1166
+
1167
+ A:
1168
+
1169
+ - 启用缓存(默认启用)
1170
+ - 根据业务调整 TTL(生产环境可设置 5-15 分钟)
1171
+ - 权限变更时及时清除缓存
1172
+ - 对于高频接口,考虑将权限检查前置到网关层
1173
+
1174
+ ### Q: enableMockRole 的安全风险?
1175
+
1176
+ A:
1177
+
1178
+ - 角色模拟功能仅在开发/测试环境使用
1179
+ - 生产环境必须设置 `enableMockRole: false`
1180
+ - 模拟角色通过 Cookie 传递,存在被恶意利用的风险
1181
+ - 建议在环境变量中控制:`enableMockRole: process.env.NODE_ENV !== 'production'`
1182
+
1183
+ ## License
1184
+
1185
+ MIT
1186
+
1187
+ ## 相关链接
1188
+
1189
+ - [CASL 文档](https://casl.js.org/)
1190
+ - [NestJS 文档](https://docs.nestjs.com/)
1191
+ - [示例项目](../../examples/node-demo)