@lark-apaas/nestjs-authzpaas 0.1.0-alpha.10 → 0.1.0-alpha.12
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 +8 -529
- package/dist/index.cjs +9 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -68
- package/dist/index.d.ts +7 -68
- package/dist/index.js +9 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,20 +18,14 @@
|
|
|
18
18
|
本模块采用以下权限模型:
|
|
19
19
|
|
|
20
20
|
- **角色 (Role)**: 用户所属的角色,如 `admin`、`editor`、`viewer`
|
|
21
|
-
- **权限 (Permission)**: 对特定资源的操作权限,由 `actions` + `subject` 组成
|
|
22
|
-
- `actions`: 操作类型,如 `create`、`read`、`update`、`delete`、`manage`
|
|
23
|
-
- `subject`: 资源类型,如 `User`、`Task`、`Article`
|
|
24
21
|
|
|
25
22
|
### 核心组件
|
|
26
23
|
|
|
27
24
|
| 组件 | 类型 | 说明 |
|
|
28
25
|
|------|------|------|
|
|
29
26
|
| `AuthZPaasGuard` | 全局守卫 | 自动拦截所有请求,执行鉴权检查 |
|
|
30
|
-
| `PermissionService` | 服务 | 提供权限获取、检查、缓存管理等功能 |
|
|
31
27
|
| `RolesMiddleware` | 中间件 | 自动获取并注入用户角色信息到请求上下文 |
|
|
32
28
|
| `CanRole` | 装饰器 | 声明式角色检查 |
|
|
33
|
-
| `CanPermission` | 装饰器 | 声明式权限检查 |
|
|
34
|
-
| `UserId` | 参数装饰器 | 获取当前用户ID |
|
|
35
29
|
|
|
36
30
|
## 快速开始
|
|
37
31
|
|
|
@@ -57,39 +51,13 @@ import { UserContextMiddleware } from '@lark-apaas/fullstack-nestjs-core';
|
|
|
57
51
|
// 配置 AuthZPaas 模块
|
|
58
52
|
AuthZPaasModule.forRoot({
|
|
59
53
|
permissionApi: {
|
|
60
|
-
// 权限 API
|
|
61
|
-
baseUrl: 'http://localhost:3000',
|
|
62
|
-
endpoint: '/mock-api/users/:userId/permissions',
|
|
63
|
-
timeout: 5000,
|
|
54
|
+
timeout: 5000, // 权限 API 超时时间(毫秒), 默认 5000
|
|
64
55
|
},
|
|
65
|
-
cache: {
|
|
66
|
-
ttl: 60, // 缓存时间(秒)
|
|
67
|
-
max: 100, // 最大缓存数量
|
|
68
|
-
enabled: true, // 启用缓存
|
|
69
|
-
},
|
|
70
|
-
// 高级配置选项
|
|
71
|
-
enableMockRole: false, // 是否启用角色模拟功能(默认 false)
|
|
72
|
-
isGlobal: true, // 全局模块
|
|
73
56
|
}),
|
|
74
57
|
],
|
|
75
58
|
controllers: [/* ... */],
|
|
76
59
|
providers: [/* ... */],
|
|
77
60
|
})
|
|
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
61
|
```
|
|
94
62
|
|
|
95
63
|
### 3. 异步配置(可选)
|
|
@@ -107,15 +75,8 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
|
107
75
|
inject: [ConfigService],
|
|
108
76
|
useFactory: async (configService: ConfigService) => ({
|
|
109
77
|
permissionApi: {
|
|
110
|
-
baseUrl: configService.get('PERMISSION_API_URL'),
|
|
111
|
-
endpoint: configService.get('PERMISSION_API_ENDPOINT'),
|
|
112
78
|
timeout: 5000,
|
|
113
79
|
},
|
|
114
|
-
cache: {
|
|
115
|
-
ttl: configService.get('CACHE_TTL', 300),
|
|
116
|
-
max: configService.get('CACHE_MAX', 1000),
|
|
117
|
-
enabled: true,
|
|
118
|
-
},
|
|
119
80
|
}),
|
|
120
81
|
}),
|
|
121
82
|
],
|
|
@@ -129,50 +90,7 @@ export class AppModule {}
|
|
|
129
90
|
|
|
130
91
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
131
92
|
|--------|------|--------|------|
|
|
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
|
-
```
|
|
93
|
+
| `permissionApi` | `PermissionApiConfig` | - | 权限 API 配置 |
|
|
176
94
|
|
|
177
95
|
## 装饰器使用
|
|
178
96
|
|
|
@@ -202,7 +120,7 @@ export class DemoController {
|
|
|
202
120
|
}
|
|
203
121
|
```
|
|
204
122
|
|
|
205
|
-
#### 示例 2: 多个角色(OR
|
|
123
|
+
#### 示例 2: 多个角色(OR 逻辑)
|
|
206
124
|
|
|
207
125
|
```typescript
|
|
208
126
|
@Controller('content')
|
|
@@ -216,20 +134,6 @@ export class ContentController {
|
|
|
216
134
|
}
|
|
217
135
|
```
|
|
218
136
|
|
|
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
137
|
## 核心组件
|
|
234
138
|
|
|
235
139
|
> 📚 **核心组件是装饰器的底层实现**,了解核心组件有助于深入理解权限系统的工作原理。
|
|
@@ -307,17 +211,6 @@ export class YourService {
|
|
|
307
211
|
}
|
|
308
212
|
```
|
|
309
213
|
|
|
310
|
-
#### 权限缓存机制
|
|
311
|
-
|
|
312
|
-
- **自动缓存**: 权限数据和 Ability 实例会自动缓存
|
|
313
|
-
- **防重复请求**: 同时发起的相同请求会共享同一个 Promise
|
|
314
|
-
- **TTL 控制**: 通过配置的 `ttl` 自动过期
|
|
315
|
-
- **手动清除**: 可以调用 `clearUserCache()` 或 `clearAllCache()`
|
|
316
|
-
|
|
317
|
-
### RolesMiddleware
|
|
318
|
-
|
|
319
|
-
`RolesMiddleware` 是一个中间件,用于在请求处理前自动获取用户角色信息。
|
|
320
|
-
|
|
321
214
|
#### 工作原理
|
|
322
215
|
|
|
323
216
|
1. 从 `req.userContext.userId` 中提取用户ID
|
|
@@ -325,38 +218,6 @@ export class YourService {
|
|
|
325
218
|
3. 将角色列表注入到 `req.userContext.userRoles`
|
|
326
219
|
4. 如果获取失败,记录警告但不阻塞请求(由 Guard 处理)
|
|
327
220
|
|
|
328
|
-
#### 配置示例
|
|
329
|
-
|
|
330
|
-
```typescript
|
|
331
|
-
export class AppModule implements NestModule {
|
|
332
|
-
configure(consumer: MiddlewareConsumer) {
|
|
333
|
-
consumer
|
|
334
|
-
.apply(RolesMiddleware)
|
|
335
|
-
.exclude('mock-api/(.*)') // 排除特定路由
|
|
336
|
-
.forRoutes('*');
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
#### 类型扩展
|
|
342
|
-
|
|
343
|
-
`RolesMiddleware` 会扩展 Express Request 类型:
|
|
344
|
-
|
|
345
|
-
```typescript
|
|
346
|
-
declare global {
|
|
347
|
-
namespace Express {
|
|
348
|
-
interface Request {
|
|
349
|
-
userContext: {
|
|
350
|
-
userId?: string;
|
|
351
|
-
tenantId?: number;
|
|
352
|
-
appId?: string;
|
|
353
|
-
userRoles?: string[]; // RolesMiddleware 注入
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
```
|
|
359
|
-
|
|
360
221
|
## 高级用法
|
|
361
222
|
|
|
362
223
|
### 1. 手动权限检查
|
|
@@ -426,60 +287,6 @@ export class ArticleService {
|
|
|
426
287
|
}
|
|
427
288
|
```
|
|
428
289
|
|
|
429
|
-
### 3. 权限缓存管理
|
|
430
|
-
|
|
431
|
-
当用户权限发生变更时,需要清除缓存:
|
|
432
|
-
|
|
433
|
-
```typescript
|
|
434
|
-
import { Injectable } from '@nestjs/common';
|
|
435
|
-
import { PermissionService } from '@lark-apaas/nestjs-authzpaas';
|
|
436
|
-
|
|
437
|
-
@Injectable()
|
|
438
|
-
export class UserManagementService {
|
|
439
|
-
constructor(private readonly permissionService: PermissionService) {}
|
|
440
|
-
|
|
441
|
-
async updateUserRole(userId: string, newRole: string) {
|
|
442
|
-
// 1. 更新用户角色(调用你的业务逻辑)
|
|
443
|
-
await this.updateRoleInDatabase(userId, newRole);
|
|
444
|
-
|
|
445
|
-
// 2. 清除该用户的权限缓存
|
|
446
|
-
this.permissionService.clearUserCache(userId);
|
|
447
|
-
|
|
448
|
-
return '角色更新成功';
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
async batchUpdatePermissions() {
|
|
452
|
-
// 批量更新权限后,清除所有缓存
|
|
453
|
-
this.permissionService.clearAllCache();
|
|
454
|
-
|
|
455
|
-
return '权限批量更新成功';
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
private async updateRoleInDatabase(userId: string, newRole: string) {
|
|
459
|
-
// 实现你的数据库更新逻辑
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
### 4. 自定义权限 API 响应处理
|
|
465
|
-
|
|
466
|
-
如果你的权限 API 返回的数据格式不同,可以通过自定义逻辑转换:
|
|
467
|
-
|
|
468
|
-
```typescript
|
|
469
|
-
// 你的权限 API 应该返回以下格式:
|
|
470
|
-
interface PermissionApiResponse {
|
|
471
|
-
userId: string;
|
|
472
|
-
roles: string[]; // 角色列表,如 ['admin', 'editor']
|
|
473
|
-
permissions: Array<{
|
|
474
|
-
id: string;
|
|
475
|
-
name: string;
|
|
476
|
-
sub: string; // 资源类型,如 'User', 'Task'
|
|
477
|
-
actions: string[]; // 操作列表,如 ['create', 'read', 'update']
|
|
478
|
-
}>;
|
|
479
|
-
fetchedAt?: Date;
|
|
480
|
-
}
|
|
481
|
-
```
|
|
482
|
-
|
|
483
290
|
## 权限 API 规范
|
|
484
291
|
|
|
485
292
|
### 请求格式
|
|
@@ -489,8 +296,6 @@ interface PermissionApiResponse {
|
|
|
489
296
|
```typescript
|
|
490
297
|
AuthZPaasModule.forRoot({
|
|
491
298
|
permissionApi: {
|
|
492
|
-
baseUrl: 'http://localhost:3000',
|
|
493
|
-
endpoint: '/mock-api/users/:userId/permissions', // :userId 会被替换
|
|
494
299
|
timeout: 5000,
|
|
495
300
|
},
|
|
496
301
|
})
|
|
@@ -502,28 +307,7 @@ AuthZPaasModule.forRoot({
|
|
|
502
307
|
|
|
503
308
|
```json
|
|
504
309
|
{
|
|
505
|
-
"
|
|
506
|
-
"roles": ["admin_role", "editor_role"],
|
|
507
|
-
"permissions": [
|
|
508
|
-
{
|
|
509
|
-
"id": "1",
|
|
510
|
-
"name": "user.create",
|
|
511
|
-
"sub": "User",
|
|
512
|
-
"actions": ["create"]
|
|
513
|
-
},
|
|
514
|
-
{
|
|
515
|
-
"id": "2",
|
|
516
|
-
"name": "user.read",
|
|
517
|
-
"sub": "User",
|
|
518
|
-
"actions": ["read"]
|
|
519
|
-
},
|
|
520
|
-
{
|
|
521
|
-
"id": "3",
|
|
522
|
-
"name": "task.manage",
|
|
523
|
-
"sub": "Task",
|
|
524
|
-
"actions": ["manage"]
|
|
525
|
-
}
|
|
526
|
-
],
|
|
310
|
+
"roles": ["role_admin", "role_editor"],
|
|
527
311
|
"fetchedAt": "2024-01-01T00:00:00.000Z"
|
|
528
312
|
}
|
|
529
313
|
```
|
|
@@ -532,110 +316,12 @@ AuthZPaasModule.forRoot({
|
|
|
532
316
|
|
|
533
317
|
- `userId`: 用户ID(必需)
|
|
534
318
|
- `roles`: 角色名称列表(必需)
|
|
535
|
-
-
|
|
536
|
-
- 或角色对象数组(会自动提取 `name` 字段)
|
|
537
|
-
- `permissions`: 权限列表(必需)
|
|
538
|
-
- `id`: 权限ID
|
|
539
|
-
- `name`: 权限名称
|
|
540
|
-
- `sub`: 资源类型(如 `User`、`Task`、`Article`)
|
|
541
|
-
- `actions`: 操作列表(如 `['create', 'read', 'update', 'delete']` 或 `['manage']`)
|
|
319
|
+
- 字符串数组 `['role_admin', 'role_editor']`
|
|
542
320
|
- `fetchedAt`: 获取时间(可选,SDK 会自动添加)
|
|
543
321
|
|
|
544
|
-
### Mock 权限 API 示例
|
|
545
|
-
|
|
546
|
-
参考 `node-demo` 中的实现:
|
|
547
|
-
|
|
548
|
-
```typescript
|
|
549
|
-
import { Controller, Get, Param } from '@nestjs/common';
|
|
550
|
-
import { Public, Permission, UserRole } from '@lark-apaas/nestjs-authzpaas';
|
|
551
|
-
|
|
552
|
-
@Controller('mock-api/users')
|
|
553
|
-
@Public() // 重要:跳过 AuthZPaasGuard,避免循环调用
|
|
554
|
-
export class MockPermissionController {
|
|
555
|
-
// 模拟用户权限数据库
|
|
556
|
-
private readonly mockPermissions: Record<string, {
|
|
557
|
-
roles: UserRole[],
|
|
558
|
-
permissions: Permission[]
|
|
559
|
-
}> = {
|
|
560
|
-
'user-admin': {
|
|
561
|
-
roles: [
|
|
562
|
-
{ id: '1', name: 'admin', description: '管理员' },
|
|
563
|
-
],
|
|
564
|
-
permissions: [
|
|
565
|
-
{ id: '1', name: 'user.create', sub: 'User', actions: ['create'] },
|
|
566
|
-
{ id: '2', name: 'user.read', sub: 'User', actions: ['read'] },
|
|
567
|
-
{ id: '3', name: 'task.manage', sub: 'Task', actions: ['manage'] },
|
|
568
|
-
],
|
|
569
|
-
},
|
|
570
|
-
'user-normal': {
|
|
571
|
-
roles: [
|
|
572
|
-
{ id: '2', name: 'reader', description: '只读用户' },
|
|
573
|
-
],
|
|
574
|
-
permissions: [
|
|
575
|
-
{ id: '4', name: 'user.read', sub: 'User', actions: ['read'] },
|
|
576
|
-
{ id: '5', name: 'task.read', sub: 'Task', actions: ['read'] },
|
|
577
|
-
],
|
|
578
|
-
},
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
@Get(':userId/permissions')
|
|
582
|
-
getUserPermissions(@Param('userId') userId: string) {
|
|
583
|
-
const permissions = this.mockPermissions[userId] || this.mockPermissions['user-guest'];
|
|
584
|
-
|
|
585
|
-
return {
|
|
586
|
-
userId,
|
|
587
|
-
...permissions,
|
|
588
|
-
fetchedAt: new Date(),
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
```
|
|
593
|
-
|
|
594
322
|
## 错误处理
|
|
595
323
|
|
|
596
|
-
### 1.
|
|
597
|
-
|
|
598
|
-
模块提供了 `AuthZPaasExceptionFilter` 用于统一处理权限异常:
|
|
599
|
-
|
|
600
|
-
```typescript
|
|
601
|
-
import { Module } from '@nestjs/common';
|
|
602
|
-
import { APP_FILTER } from '@nestjs/core';
|
|
603
|
-
import { AuthZPaasExceptionFilter } from '@lark-apaas/nestjs-authzpaas';
|
|
604
|
-
|
|
605
|
-
@Module({
|
|
606
|
-
providers: [
|
|
607
|
-
{
|
|
608
|
-
provide: APP_FILTER,
|
|
609
|
-
useClass: AuthZPaasExceptionFilter,
|
|
610
|
-
},
|
|
611
|
-
],
|
|
612
|
-
})
|
|
613
|
-
export class AppModule {}
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
### 2. 自定义异常处理
|
|
617
|
-
|
|
618
|
-
```typescript
|
|
619
|
-
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
|
|
620
|
-
import { PermissionDeniedException } from '@lark-apaas/nestjs-authzpaas';
|
|
621
|
-
|
|
622
|
-
@Catch(PermissionDeniedException)
|
|
623
|
-
export class CustomPermissionExceptionFilter implements ExceptionFilter {
|
|
624
|
-
catch(exception: PermissionDeniedException, host: ArgumentsHost) {
|
|
625
|
-
const ctx = host.switchToHttp();
|
|
626
|
-
const response = ctx.getResponse();
|
|
627
|
-
|
|
628
|
-
response.status(403).json({
|
|
629
|
-
statusCode: 403,
|
|
630
|
-
message: exception.message,
|
|
631
|
-
error: 'Forbidden',
|
|
632
|
-
timestamp: new Date().toISOString(),
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
### 3. 常见异常类型
|
|
324
|
+
### 1. 常见异常类型
|
|
639
325
|
|
|
640
326
|
| 异常 | 说明 | HTTP 状态码 |
|
|
641
327
|
|------|------|------------|
|
|
@@ -646,156 +332,7 @@ export class CustomPermissionExceptionFilter implements ExceptionFilter {
|
|
|
646
332
|
|
|
647
333
|
## 最佳实践
|
|
648
334
|
|
|
649
|
-
### 1.
|
|
650
|
-
|
|
651
|
-
```typescript
|
|
652
|
-
// ✅ 好的做法:清晰的资源和操作命名
|
|
653
|
-
@CanPermission([{ actions: ['create'], subject: 'Article' }])
|
|
654
|
-
@CanPermission([{ actions: ['update'], subject: 'User' }])
|
|
655
|
-
@CanPermission([{ actions: ['delete'], subject: 'Task' }])
|
|
656
|
-
|
|
657
|
-
// ❌ 避免使用:模糊的命名
|
|
658
|
-
@CanPermission([{ actions: ['action1'], subject: 'resource1' }])
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
### 2. 遵循 CRUD 标准操作
|
|
662
|
-
|
|
663
|
-
建议使用标准的 CRUD 操作名称:
|
|
664
|
-
|
|
665
|
-
- `create`: 创建资源
|
|
666
|
-
- `read`: 读取资源
|
|
667
|
-
- `update`: 更新资源
|
|
668
|
-
- `delete`: 删除资源
|
|
669
|
-
- `manage`: 完全管理权限(包含所有操作)
|
|
670
|
-
|
|
671
|
-
```typescript
|
|
672
|
-
// 推荐的权限设计
|
|
673
|
-
@CanPermission([{ actions: ['read'], subject: 'User' }])
|
|
674
|
-
@CanPermission([{ actions: ['manage'], subject: 'Task' }]) // manage 包含所有操作
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
### 3. 合理使用 requireAll
|
|
678
|
-
|
|
679
|
-
- **默认 `requireAll: true`**: 适用于需要同时满足多个权限的场景(AND 逻辑)
|
|
680
|
-
- **`requireAll: false`**: 适用于满足任一权限即可的场景(OR 逻辑)
|
|
681
|
-
|
|
682
|
-
```typescript
|
|
683
|
-
// AND 逻辑:需要同时拥有 read 和 update 权限
|
|
684
|
-
@CanPermission([{
|
|
685
|
-
actions: ['read', 'update'],
|
|
686
|
-
subject: 'Article',
|
|
687
|
-
requireAll: true // 默认值,可省略
|
|
688
|
-
}])
|
|
689
|
-
|
|
690
|
-
// OR 逻辑:拥有 read 或 preview 任一权限即可
|
|
691
|
-
@CanPermission([{
|
|
692
|
-
actions: ['read', 'preview'],
|
|
693
|
-
subject: 'Article',
|
|
694
|
-
requireAll: false
|
|
695
|
-
}])
|
|
696
|
-
```
|
|
697
|
-
|
|
698
|
-
### 4. 权限粒度设计
|
|
699
|
-
|
|
700
|
-
细粒度权限优于粗粒度权限:
|
|
701
|
-
|
|
702
|
-
```typescript
|
|
703
|
-
// ✅ 推荐:细粒度权限,更精确的控制
|
|
704
|
-
@CanPermission([{ actions: ['publish'], subject: 'Article' }])
|
|
705
|
-
@CanPermission([{ actions: ['archive'], subject: 'Article' }])
|
|
706
|
-
|
|
707
|
-
// ⚠️ 谨慎使用:粗粒度权限,权限范围过大
|
|
708
|
-
@CanPermission([{ actions: ['manage'], subject: 'Article' }])
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
### 5. 缓存策略
|
|
712
|
-
|
|
713
|
-
根据业务需求调整缓存配置:
|
|
714
|
-
|
|
715
|
-
```typescript
|
|
716
|
-
AuthZPaasModule.forRoot({
|
|
717
|
-
cache: {
|
|
718
|
-
ttl: 60, // 开发环境:较短的缓存时间,便于测试
|
|
719
|
-
// ttl: 300, // 生产环境:较长的缓存时间,提升性能
|
|
720
|
-
max: 1000, // 根据用户量调整
|
|
721
|
-
enabled: true, // 生产环境建议开启
|
|
722
|
-
},
|
|
723
|
-
})
|
|
724
|
-
```
|
|
725
|
-
|
|
726
|
-
**重要**:权限变更后务必清除缓存
|
|
727
|
-
|
|
728
|
-
```typescript
|
|
729
|
-
// 单用户权限变更
|
|
730
|
-
this.permissionService.clearUserCache(userId);
|
|
731
|
-
|
|
732
|
-
// 批量权限变更
|
|
733
|
-
this.permissionService.clearAllCache();
|
|
734
|
-
```
|
|
735
|
-
|
|
736
|
-
### 6. 中间件配置顺序
|
|
737
|
-
|
|
738
|
-
确保中间件按正确的顺序配置:
|
|
739
|
-
|
|
740
|
-
```typescript
|
|
741
|
-
export class AppModule implements NestModule {
|
|
742
|
-
configure(consumer: MiddlewareConsumer) {
|
|
743
|
-
// 1️⃣ 第一步:UserContextMiddleware 提取用户信息
|
|
744
|
-
consumer.apply(UserContextMiddleware).forRoutes('*');
|
|
745
|
-
|
|
746
|
-
// 2️⃣ 第二步:RolesMiddleware 获取用户角色
|
|
747
|
-
// 注意:必须排除权限 API 路由
|
|
748
|
-
consumer
|
|
749
|
-
.apply(RolesMiddleware)
|
|
750
|
-
.exclude('mock-api/(.*)')
|
|
751
|
-
.forRoutes('*');
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
```
|
|
755
|
-
|
|
756
|
-
### 7. 公开接口使用 @Public()
|
|
757
|
-
|
|
758
|
-
对于不需要鉴权的接口,务必使用 `@Public()` 装饰器:
|
|
759
|
-
|
|
760
|
-
```typescript
|
|
761
|
-
// ✅ 好的做法
|
|
762
|
-
@Controller('health')
|
|
763
|
-
export class HealthController {
|
|
764
|
-
@Get()
|
|
765
|
-
@Public()
|
|
766
|
-
check() {
|
|
767
|
-
return { status: 'ok' };
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// ❌ 避免:未标记 @Public(),会触发鉴权检查
|
|
772
|
-
@Controller('health')
|
|
773
|
-
export class HealthController {
|
|
774
|
-
@Get()
|
|
775
|
-
check() {
|
|
776
|
-
return { status: 'ok' };
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
```
|
|
780
|
-
|
|
781
|
-
### 8. 组合使用装饰器
|
|
782
|
-
|
|
783
|
-
对于复杂的权限需求,可以组合使用多个装饰器:
|
|
784
|
-
|
|
785
|
-
```typescript
|
|
786
|
-
@Controller('admin')
|
|
787
|
-
export class AdminController {
|
|
788
|
-
// 角色检查 + 权限检查
|
|
789
|
-
@CanRole(['admin'])
|
|
790
|
-
@CanPermission([{ actions: ['manage'], subject: 'System' }])
|
|
791
|
-
@Get('system')
|
|
792
|
-
manageSystem() {
|
|
793
|
-
return '系统管理';
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
```
|
|
797
|
-
|
|
798
|
-
### 9. 使用 TypeScript 类型提示
|
|
335
|
+
### 1. 使用 TypeScript 类型提示
|
|
799
336
|
|
|
800
337
|
充分利用 TypeScript 的类型系统:
|
|
801
338
|
|
|
@@ -810,33 +347,7 @@ import {
|
|
|
810
347
|
// TypeScript 会自动提供代码提示和类型检查
|
|
811
348
|
```
|
|
812
349
|
|
|
813
|
-
###
|
|
814
|
-
|
|
815
|
-
#### enableMockRole 使用场景
|
|
816
|
-
|
|
817
|
-
```typescript
|
|
818
|
-
// ✅ 开发环境:启用角色模拟
|
|
819
|
-
AuthZPaasModule.forRoot({
|
|
820
|
-
enableMockRole: process.env.NODE_ENV === 'development',
|
|
821
|
-
})
|
|
822
|
-
|
|
823
|
-
// ✅ 测试环境:便于自动化测试
|
|
824
|
-
AuthZPaasModule.forRoot({
|
|
825
|
-
enableMockRole: process.env.NODE_ENV === 'test',
|
|
826
|
-
})
|
|
827
|
-
|
|
828
|
-
// ❌ 生产环境:必须关闭
|
|
829
|
-
AuthZPaasModule.forRoot({
|
|
830
|
-
enableMockRole: false, // 生产环境安全考虑
|
|
831
|
-
})
|
|
832
|
-
```
|
|
833
|
-
|
|
834
|
-
**使用建议**:
|
|
835
|
-
- 开发环境:便于测试不同角色权限
|
|
836
|
-
- 测试环境:自动化测试时模拟不同用户
|
|
837
|
-
- 生产环境:必须关闭,避免安全风险
|
|
838
|
-
|
|
839
|
-
### 11. 测试建议
|
|
350
|
+
### 2. 测试建议
|
|
840
351
|
|
|
841
352
|
编写单元测试时,可以 mock `PermissionService`:
|
|
842
353
|
|
|
@@ -895,38 +406,6 @@ curl -H "x-user-id: user-admin" http://localhost:3000/demo/admin-only
|
|
|
895
406
|
curl -H "x-user-id: user-normal" http://localhost:3000/demo/reader-only
|
|
896
407
|
```
|
|
897
408
|
|
|
898
|
-
## 常见问题
|
|
899
|
-
|
|
900
|
-
### Q: 如何自定义提取 userId 的逻辑?
|
|
901
|
-
|
|
902
|
-
A: 可以自定义 `UserContextMiddleware` 或扩展 `AuthZPaasGuard`。通常在 `UserContextMiddleware` 中从 JWT、Session 或 Header 中提取用户信息并设置到 `req.userContext.userId`。
|
|
903
|
-
|
|
904
|
-
### Q: manage 权限和其他权限的关系?
|
|
905
|
-
|
|
906
|
-
A: `manage` 是特殊的操作类型,在 CASL 中表示"所有权限"。如果用户有某个资源的 `manage` 权限,则自动拥有该资源的所有操作权限(`create`、`read`、`update`、`delete` 等)。
|
|
907
|
-
|
|
908
|
-
### Q: 如何实现数据级权限控制(如只能访问自己创建的数据)?
|
|
909
|
-
|
|
910
|
-
A: 可以结合 CASL 的 conditions 功能和业务逻辑实现。在 `PermissionService` 中手动检查时,可以传入资源实例进行细粒度验证。
|
|
911
|
-
|
|
912
|
-
### Q: 性能优化建议?
|
|
913
|
-
|
|
914
|
-
A:
|
|
915
|
-
|
|
916
|
-
- 启用缓存(默认启用)
|
|
917
|
-
- 根据业务调整 TTL(生产环境可设置 5-15 分钟)
|
|
918
|
-
- 权限变更时及时清除缓存
|
|
919
|
-
- 对于高频接口,考虑将权限检查前置到网关层
|
|
920
|
-
|
|
921
|
-
### Q: enableMockRole 的安全风险?
|
|
922
|
-
|
|
923
|
-
A:
|
|
924
|
-
|
|
925
|
-
- 角色模拟功能仅在开发/测试环境使用
|
|
926
|
-
- 生产环境必须设置 `enableMockRole: false`
|
|
927
|
-
- 模拟角色通过 Cookie 传递,存在被恶意利用的风险
|
|
928
|
-
- 建议在环境变量中控制:`enableMockRole: process.env.NODE_ENV !== 'production'`
|
|
929
|
-
|
|
930
409
|
## License
|
|
931
410
|
|
|
932
411
|
MIT
|