# 权限管理文档 ## 一、权限架构概述 本系统采用 NestJS 框架,基于 **JWT 认证 + 角色权限** 的多层权限控制体系。 ### 权限层级 ``` 全局守卫(APP_GUARD) ├─ JwtAuthGuard(JWT 认证守卫) │ └─ 验证用户身份,检查 Token 有效性 │ └─ RolesGuard(角色守卫) └─ 验证用户角色权限 ``` --- ## 二、权限角色定义 ### 2.1 系统级角色(UserRole) 定义位置:[src/common/enums/index.ts](src/common/enums/index.ts) | 角色 | 枚举值 | 描述 | 权限范围 | |-----|-------|-----|---------| | **管理员** | `admin` | 系统管理员 | 全部系统功能 | | **普通用户** | `user` | 普通用户 | 基础功能 | 用户角色存储在 `users` 表的 `role` 字段(类型:enum)。 ### 2.2 小组级角色(GroupMemberRole) 定义位置:[src/common/enums/index.ts](src/common/enums/index.ts) | 角色 | 枚举值 | 描述 | 权限范围 | |-----|-------|-----|---------| | **组长** | `owner` | 小组创建者 | 小组所有管理权限 | | **管理员** | `admin` | 小组管理员 | 大部分管理权限 | | **普通成员** | `member` | 普通小组成员 | 基础功能 | 小组角色存储在 `group_members` 表的 `role` 字段(类型:enum)。 ### 2.3 会员权限(isMember) - **字段**:`users.isMember`(布尔值) - **到期时间**:`users.memberExpireAt`(日期时间) - **特权**: - 创建更多小组(非会员1个,会员10个) - 创建子组 - 审核黑名单 - 删除任意黑名单记录 --- ## 三、权限控制组件 ### 3.1 守卫(Guards) #### JwtAuthGuard - JWT 认证守卫 **文件位置**:[src/common/guards/jwt-auth.guard.ts](src/common/guards/jwt-auth.guard.ts) **功能**: - 默认所有接口都需要认证 - 验证 JWT Token 有效性 - 支持通过 `@Public()` 装饰器标记公开接口 **执行逻辑**: ```typescript 1. 检查接口是否标记为 @Public() 2. 如果是公开接口,直接放行 3. 否则,调用 passport 的 JWT 策略验证 Token 4. 验证失败,抛出 UNAUTHORIZED 异常(错误码:10006) 5. 验证成功,将用户信息注入到 request.user ``` **错误响应**: ```json { "code": 10006, "message": "未授权" } ``` #### RolesGuard - 角色守卫 **文件位置**:[src/common/guards/roles.guard.ts](src/common/guards/roles.guard.ts) **功能**: - 检查用户是否拥有所需的系统角色 - 配合 `@Roles()` 装饰器使用 **执行逻辑**: ```typescript 1. 获取接口要求的角色列表(通过 @Roles() 装饰器) 2. 如果未设置角色要求,直接放行 3. 检查 request.user 是否存在 4. 检查用户角色是否在要求的角色列表中 5. 不满足,抛出 NO_PERMISSION 异常(错误码:20003) ``` **错误响应**: ```json { "code": 20003, "message": "无权限操作" } ``` ### 3.2 装饰器(Decorators) #### @Public() - 公开接口装饰器 **文件位置**:[src/common/decorators/public.decorator.ts](src/common/decorators/public.decorator.ts) **用途**:标记不需要认证的公开接口 **使用示例**: ```typescript @Public() @Post('login') async login(@Body() loginDto: LoginDto) { return this.authService.login(loginDto); } ``` #### @Roles() - 角色装饰器 **文件位置**:[src/common/decorators/roles.decorator.ts](src/common/decorators/roles.decorator.ts) **用途**:限制接口只允许特定角色访问 **使用示例**: ```typescript @Roles(UserRole.ADMIN) @Delete(':id') async deleteUser(@Param('id') id: string) { return this.usersService.remove(id); } ``` #### @CurrentUser() - 当前用户装饰器 **文件位置**:[src/common/decorators/current-user.decorator.ts](src/common/decorators/current-user.decorator.ts) **用途**:获取当前登录用户信息 **使用示例**: ```typescript @Get('me') async getProfile(@CurrentUser() user: User) { return this.usersService.findOne(user.id); } ``` --- ## 四、权限检查接口分类 ### 4.1 公开接口(无需认证) #### 认证模块(Auth) | 方法 | 路径 | 功能 | 权限 | |------|------|------|------| | POST | `/auth/register` | 用户注册 | 公开 | | POST | `/auth/login` | 用户登录 | 公开 | | POST | `/auth/refresh` | 刷新令牌 | 公开 | #### 游戏模块(Games) | 方法 | 路径 | 功能 | 权限 | |------|------|------|------| | GET | `/games` | 获取游戏列表 | 公开 | | GET | `/games/popular` | 获取热门游戏 | 公开 | | GET | `/games/tags` | 获取游戏标签 | 公开 | | GET | `/games/platforms` | 获取游戏平台 | 公开 | | GET | `/games/:id` | 获取游戏详情 | 公开 | ### 4.2 需要认证的接口(JWT) #### 用户模块(Users) | 方法 | 路径 | 功能 | 权限要求 | |------|------|------|---------| | GET | `/users/me` | 获取当前用户信息 | 登录用户 | | GET | `/users/:id` | 获取用户信息 | 登录用户 | | PUT | `/users/me` | 更新用户信息 | 本人 | | PUT | `/users/me/password` | 修改密码 | 本人 | #### 小组模块(Groups) | 方法 | 路径 | 功能 | 权限要求 | |------|------|------|---------| | POST | `/groups` | 创建小组 | 登录用户(有创建限制) | | POST | `/groups/join` | 加入小组 | 登录用户 | | GET | `/groups/my` | 获取我的小组 | 登录用户 | | GET | `/groups/:id` | 获取小组详情 | 登录用户 | | PUT | `/groups/:id` | 更新小组信息 | Owner/Admin | | PUT | `/groups/:id/members/role` | 设置成员角色 | Owner/Admin | | DELETE | `/groups/:id/members` | 踢出成员 | Owner/Admin | | DELETE | `/groups/:id/leave` | 退出小组 | 本人 | | DELETE | `/groups/:id` | 解散小组 | Owner | #### 预约模块(Appointments) | 方法 | 路径 | 功能 | 权限要求 | |------|------|------|---------| | POST | `/appointments` | 创建预约 | 登录用户 | | GET | `/appointments` | 获取预约列表 | 登录用户 | | GET | `/appointments/my` | 获取我的预约 | 登录用户 | | GET | `/appointments/:id` | 获取预约详情 | 登录用户 | | POST | `/appointments/join` | 加入预约 | 登录用户 | | PUT | `/appointments/:id` | 更新预约 | 发起人 | | PUT | `/appointments/:id/confirm` | 确认预约 | 发起人 | | PUT | `/appointments/:id/complete` | 完成预约 | 发起人 | | DELETE | `/appointments/:id/leave` | 退出预约 | 参与者 | | DELETE | `/appointments/:id` | 取消预约 | 发起人 | #### 黑名单模块(Blacklist) | 方法 | 路径 | 功能 | 权限要求 | |------|------|------|---------| | POST | `/blacklist` | 提交举报 | 登录用户 | | GET | `/blacklist` | 查询黑名单列表 | 登录用户 | | GET | `/blacklist/check/:targetGameId` | 检查游戏ID | 登录用户 | | GET | `/blacklist/:id` | 查询记录详情 | 登录用户 | | PATCH | `/blacklist/:id/review` | 审核黑名单 | 会员 | | DELETE | `/blacklist/:id` | 删除记录 | 举报人或会员 | #### 资产模块(Assets) | 方法 | 路径 | 功能 | 权限要求 | |------|------|------|---------| | POST | `/assets` | 创建资产 | 小组Owner/Admin | | GET | `/assets/group/:groupId` | 查询小组资产 | 登录用户 | | GET | `/assets/:id` | 查询资产详情 | 登录用户 | | PATCH | `/assets/:id` | 更新资产 | 小组Owner/Admin | | POST | `/assets/:id/borrow` | 借用资产 | 小组成员 | | POST | `/assets/:id/return` | 归还资产 | 借用人 | | GET | `/assets/:id/logs` | 查询借还记录 | 登录用户 | | DELETE | `/assets/:id` | 删除资产 | 小组Owner/Admin | #### 游戏模块(Games - 管理功能) | 方法 | 路径 | 功能 | 权限要求 | |------|------|------|---------| | POST | `/games` | 创建游戏 | 登录用户 | | PUT | `/games/:id` | 更新游戏 | 登录用户 | | DELETE | `/games/:id` | 删除游戏 | 登录用户 | --- ## 五、业务层权限检查 ### 5.1 小组权限检查 **实现位置**:[src/modules/groups/groups.service.ts](src/modules/groups/groups.service.ts) #### 创建小组限制 ```typescript // 非会员:最多1个小组 // 会员:最多10个小组 if (!user.isMember && ownedGroupsCount >= 1) { throw new BadRequestException('非会员最多只能创建1个小组'); } if (user.isMember && ownedGroupsCount >= 10) { throw new BadRequestException('会员最多只能创建10个小组'); } ``` #### 创建子组权限 ```typescript // 只有会员才能创建子组 if (createGroupDto.parentId && !user.isMember) { throw new ForbiddenException('非会员不能创建子组'); } ``` #### 小组管理权限 ```typescript // 检查用户是否为组长或管理员 const member = await this.groupMemberRepository.findOne({ where: { groupId, userId }, }); if (!member || (member.role !== GroupMemberRole.OWNER && member.role !== GroupMemberRole.ADMIN)) { throw new ForbiddenException('需要管理员权限'); } ``` #### 解散小组权限 ```typescript // 只有组长可以解散小组 if (member.role !== GroupMemberRole.OWNER) { throw new ForbiddenException('只有组长可以解散小组'); } ``` ### 5.2 黑名单权限检查 **实现位置**:[src/modules/blacklist/blacklist.service.ts](src/modules/blacklist/blacklist.service.ts) #### 审核权限 ```typescript // 只有会员才能审核黑名单 if (!user || !user.isMember) { throw new ForbiddenException('需要会员权限'); } ``` #### 删除权限 ```typescript // 举报人或会员可以删除记录 if (blacklist.reporterId !== userId && !user.isMember) { throw new ForbiddenException('无权限操作'); } ``` ### 5.3 资产管理权限检查 **实现位置**:[src/modules/assets/assets.service.ts](src/modules/assets/assets.service.ts) #### 创建/更新/删除资产 ```typescript // 需要是小组的 Owner 或 Admin const membership = await this.groupMemberRepository.findOne({ where: { groupId, userId }, }); if (!membership || (membership.role !== GroupMemberRole.ADMIN && membership.role !== GroupMemberRole.OWNER)) { throw new ForbiddenException('需要管理员权限'); } ``` #### 借用资产 ```typescript // 必须是小组成员才能借用 const membership = await this.groupMemberRepository.findOne({ where: { groupId: asset.groupId, userId }, }); if (!membership) { throw new ForbiddenException('您不是该小组成员'); } ``` --- ## 六、权限相关错误码 定义位置:[src/common/interfaces/response.interface.ts](src/common/interfaces/response.interface.ts) | 错误码 | 枚举值 | 消息 | 说明 | |--------|--------|------|------| | 10004 | `TOKEN_INVALID` | Token无效 | JWT验证失败 | | 10005 | `TOKEN_EXPIRED` | Token已过期 | JWT已过期 | | 10006 | `UNAUTHORIZED` | 未授权 | 未登录或认证失败 | | 20003 | `NO_PERMISSION` | 无权限操作 | 角色权限不足 | | 60002 | `INVALID_OPERATION` | 无效操作 | 业务逻辑不允许 | --- ## 七、权限配置 ### 7.1 全局守卫注册 **文件位置**:[src/app.module.ts](src/app.module.ts) ```typescript providers: [ // 全局 JWT 认证守卫 { provide: APP_GUARD, useClass: JwtAuthGuard, }, // 全局角色守卫 { provide: APP_GUARD, useClass: RolesGuard, }, ] ``` **执行顺序**: 1. JwtAuthGuard(先执行)- 验证身份 2. RolesGuard(后执行)- 验证角色 ### 7.2 JWT 策略配置 **文件位置**:[src/modules/auth/jwt.strategy.ts](src/modules/auth/jwt.strategy.ts) 从 JWT Payload 中提取用户信息并注入到 `request.user`。 --- ## 八、权限使用最佳实践 ### 8.1 Controller 层 ```typescript // 1. 使用 @Public() 标记公开接口 @Public() @Post('login') async login(@Body() loginDto: LoginDto) { return this.authService.login(loginDto); } // 2. 使用 @CurrentUser() 获取当前用户 @Get('me') async getProfile(@CurrentUser() user: User) { return this.usersService.findOne(user.id); } // 3. 使用 @Roles() 限制角色(目前项目中未使用) @Roles(UserRole.ADMIN) @Delete(':id') async deleteUser(@Param('id') id: string) { return this.usersService.remove(id); } // 4. 使用 @UseGuards() 应用特定守卫 @UseGuards(JwtAuthGuard) @Controller('users') export class UsersController {} ``` ### 8.2 Service 层 ```typescript // 1. 在业务逻辑中检查权限 async update(userId: string, groupId: string, updateDto: UpdateGroupDto) { // 检查用户是否有权限 const member = await this.groupMemberRepository.findOne({ where: { groupId, userId }, }); if (!member || member.role === GroupMemberRole.MEMBER) { throw new ForbiddenException({ code: ErrorCode.NO_PERMISSION, message: ErrorMessage[ErrorCode.NO_PERMISSION], }); } // 执行业务逻辑 // ... } // 2. 根据用户角色返回不同数据 async findOne(assetId: string, userId: string) { const asset = await this.assetRepository.findOne({ where: { id: assetId } }); // 检查是否为小组管理员,决定是否显示加密信息 const membership = await this.groupMemberRepository.findOne({ where: { groupId: asset.groupId, userId }, }); if (membership?.role === GroupMemberRole.OWNER || membership?.role === GroupMemberRole.ADMIN) { // 解密敏感信息 asset.accountCredentials = this.decrypt(asset.accountCredentials); } else { // 隐藏敏感信息 delete asset.accountCredentials; } return asset; } ``` --- ## 九、权限扩展建议 ### 9.1 当前缺失的权限功能 1. **系统管理员角色未充分利用** - `UserRole.ADMIN` 已定义但未在Controller层使用 `@Roles()` 装饰器 - 建议:对管理功能使用 `@Roles(UserRole.ADMIN)` 限制 2. **小组级权限检查不够细化** - 当前只区分 Owner/Admin/Member - 建议:可以增加更细粒度的权限点(如:财务管理、成员管理等) 3. **缺少权限审计日志** - 建议:记录敏感操作的权限检查日志 ### 9.2 推荐改进 #### 1. 使用 @Roles() 装饰器 ```typescript // 在控制器中明确标注需要管理员权限的接口 @Roles(UserRole.ADMIN) @Patch(':id/review') async review(@CurrentUser() user, @Param('id') id: string) { return this.blacklistService.review(user.id, id, reviewDto); } ``` #### 2. 创建自定义权限守卫 ```typescript // group-permission.guard.ts @Injectable() export class GroupPermissionGuard implements CanActivate { constructor(private reflector: Reflector) {} async canActivate(context: ExecutionContext): Promise { const requiredPermissions = this.reflector.get( 'permissions', context.getHandler(), ); if (!requiredPermissions) { return true; } const request = context.switchToHttp().getRequest(); const user = request.user; const groupId = request.params.groupId; // 检查用户在小组中的权限 // ... return hasPermission; } } ``` #### 3. 添加权限装饰器 ```typescript // group-permissions.decorator.ts export const GroupPermissions = (...permissions: string[]) => SetMetadata('permissions', permissions); // 使用 @GroupPermissions('manage_members', 'manage_assets') @Put(':groupId/settings') async updateSettings() {} ``` --- ## 十、权限测试建议 ### 10.1 单元测试 - 测试 JwtAuthGuard 的认证逻辑 - 测试 RolesGuard 的角色验证 - 测试各 Service 的权限检查方法 ### 10.2 集成测试 - 测试未登录访问受保护接口 - 测试普通用户访问管理员接口 - 测试跨小组权限隔离 - 测试会员与非会员权限差异 ### 10.3 E2E 测试 - 完整的用户注册-登录-操作流程 - 权限升级后的功能可用性 - Token 过期后的行为 --- ## 十一、安全建议 1. **Token 管理** - JWT Secret 使用环境变量配置 - 设置合理的 Token 过期时间 - 实现 Refresh Token 机制(已实现) 2. **密码安全** - 使用强加密算法存储密码 - 实施密码强度策略 - 防止暴力破解 3. **敏感信息保护** - 资产凭据加密存储(已实现) - 根据权限动态脱敏 - API 响应中排除敏感字段 4. **防护措施** - 实施请求限流 - 记录异常访问 - 添加 CORS 配置 - 使用 Helmet 中间件 --- ## 十二、权限流程图 ### JWT 认证流程 ``` 用户请求 → JwtAuthGuard ↓ 检查 @Public() ↓ 是 → 直接放行 否 → 验证 JWT Token ↓ 有效 → 注入 user 到 request 无效 → 返回 401 错误 ↓ RolesGuard ↓ 检查 @Roles() ↓ 未设置 → 放行 已设置 → 验证用户角色 ↓ 匹配 → 放行到 Controller 不匹配 → 返回 403 错误 ``` ### 小组权限检查流程 ``` 用户操作小组资源 ↓ 获取小组成员信息 ↓ 检查成员角色 ↓ Owner → 全部权限 Admin → 管理权限(不含解散) Member → 基础权限 非成员 → 拒绝访问 ``` --- ## 附录:相关文件清单 ### 核心文件 - [src/common/guards/jwt-auth.guard.ts](src/common/guards/jwt-auth.guard.ts) - JWT认证守卫 - [src/common/guards/roles.guard.ts](src/common/guards/roles.guard.ts) - 角色守卫 - [src/common/decorators/public.decorator.ts](src/common/decorators/public.decorator.ts) - 公开接口装饰器 - [src/common/decorators/roles.decorator.ts](src/common/decorators/roles.decorator.ts) - 角色装饰器 - [src/common/decorators/current-user.decorator.ts](src/common/decorators/current-user.decorator.ts) - 当前用户装饰器 - [src/common/enums/index.ts](src/common/enums/index.ts) - 角色枚举定义 - [src/common/interfaces/response.interface.ts](src/common/interfaces/response.interface.ts) - 错误码定义 ### 配置文件 - [src/app.module.ts](src/app.module.ts) - 全局守卫注册 - [src/modules/auth/jwt.strategy.ts](src/modules/auth/jwt.strategy.ts) - JWT策略 ### 业务模块 - [src/modules/auth/auth.controller.ts](src/modules/auth/auth.controller.ts) - 认证接口 - [src/modules/users/users.controller.ts](src/modules/users/users.controller.ts) - 用户接口 - [src/modules/groups/groups.controller.ts](src/modules/groups/groups.controller.ts) - 小组接口 - [src/modules/groups/groups.service.ts](src/modules/groups/groups.service.ts) - 小组权限检查 - [src/modules/blacklist/blacklist.controller.ts](src/modules/blacklist/blacklist.controller.ts) - 黑名单接口 - [src/modules/blacklist/blacklist.service.ts](src/modules/blacklist/blacklist.service.ts) - 黑名单权限检查 - [src/modules/assets/assets.controller.ts](src/modules/assets/assets.controller.ts) - 资产接口 - [src/modules/assets/assets.service.ts](src/modules/assets/assets.service.ts) - 资产权限检查 --- **文档版本**:v1.0 **更新日期**:2025-12-20 **维护者**:开发团队