Files
gamegroup/权限管理文档.md

686 lines
19 KiB
Markdown
Raw Normal View History

# 权限管理文档
## 一、权限架构概述
本系统采用 NestJS 框架,基于 **JWT 认证 + 角色权限** 的多层权限控制体系。
### 权限层级
```
全局守卫APP_GUARD
├─ JwtAuthGuardJWT 认证守卫)
│ └─ 验证用户身份,检查 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<boolean> {
const requiredPermissions = this.reflector.get<string[]>(
'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
**维护者**:开发团队