- 基于 NestJS + TypeScript + MySQL + Redis 架构 - 完整的模块化设计(认证、用户、小组、游戏、预约等) - JWT 认证和 RBAC 权限控制系统 - Docker 容器化部署支持 - 添加 CLAUDE.md 项目开发指南 - 配置 .gitignore 忽略文件 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
19 KiB
权限管理文档
一、权限架构概述
本系统采用 NestJS 框架,基于 JWT 认证 + 角色权限 的多层权限控制体系。
权限层级
全局守卫(APP_GUARD)
├─ JwtAuthGuard(JWT 认证守卫)
│ └─ 验证用户身份,检查 Token 有效性
│
└─ RolesGuard(角色守卫)
└─ 验证用户角色权限
二、权限角色定义
2.1 系统级角色(UserRole)
定义位置:src/common/enums/index.ts
| 角色 | 枚举值 | 描述 | 权限范围 |
|---|---|---|---|
| 管理员 | admin |
系统管理员 | 全部系统功能 |
| 普通用户 | user |
普通用户 | 基础功能 |
用户角色存储在 users 表的 role 字段(类型:enum)。
2.2 小组级角色(GroupMemberRole)
定义位置: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
功能:
- 默认所有接口都需要认证
- 验证 JWT Token 有效性
- 支持通过
@Public()装饰器标记公开接口
执行逻辑:
1. 检查接口是否标记为 @Public()
2. 如果是公开接口,直接放行
3. 否则,调用 passport 的 JWT 策略验证 Token
4. 验证失败,抛出 UNAUTHORIZED 异常(错误码:10006)
5. 验证成功,将用户信息注入到 request.user
错误响应:
{
"code": 10006,
"message": "未授权"
}
RolesGuard - 角色守卫
文件位置:src/common/guards/roles.guard.ts
功能:
- 检查用户是否拥有所需的系统角色
- 配合
@Roles()装饰器使用
执行逻辑:
1. 获取接口要求的角色列表(通过 @Roles() 装饰器)
2. 如果未设置角色要求,直接放行
3. 检查 request.user 是否存在
4. 检查用户角色是否在要求的角色列表中
5. 不满足,抛出 NO_PERMISSION 异常(错误码:20003)
错误响应:
{
"code": 20003,
"message": "无权限操作"
}
3.2 装饰器(Decorators)
@Public() - 公开接口装饰器
文件位置:src/common/decorators/public.decorator.ts
用途:标记不需要认证的公开接口
使用示例:
@Public()
@Post('login')
async login(@Body() loginDto: LoginDto) {
return this.authService.login(loginDto);
}
@Roles() - 角色装饰器
文件位置:src/common/decorators/roles.decorator.ts
用途:限制接口只允许特定角色访问
使用示例:
@Roles(UserRole.ADMIN)
@Delete(':id')
async deleteUser(@Param('id') id: string) {
return this.usersService.remove(id);
}
@CurrentUser() - 当前用户装饰器
文件位置:src/common/decorators/current-user.decorator.ts
用途:获取当前登录用户信息
使用示例:
@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
创建小组限制
// 非会员:最多1个小组
// 会员:最多10个小组
if (!user.isMember && ownedGroupsCount >= 1) {
throw new BadRequestException('非会员最多只能创建1个小组');
}
if (user.isMember && ownedGroupsCount >= 10) {
throw new BadRequestException('会员最多只能创建10个小组');
}
创建子组权限
// 只有会员才能创建子组
if (createGroupDto.parentId && !user.isMember) {
throw new ForbiddenException('非会员不能创建子组');
}
小组管理权限
// 检查用户是否为组长或管理员
const member = await this.groupMemberRepository.findOne({
where: { groupId, userId },
});
if (!member || (member.role !== GroupMemberRole.OWNER &&
member.role !== GroupMemberRole.ADMIN)) {
throw new ForbiddenException('需要管理员权限');
}
解散小组权限
// 只有组长可以解散小组
if (member.role !== GroupMemberRole.OWNER) {
throw new ForbiddenException('只有组长可以解散小组');
}
5.2 黑名单权限检查
实现位置:src/modules/blacklist/blacklist.service.ts
审核权限
// 只有会员才能审核黑名单
if (!user || !user.isMember) {
throw new ForbiddenException('需要会员权限');
}
删除权限
// 举报人或会员可以删除记录
if (blacklist.reporterId !== userId && !user.isMember) {
throw new ForbiddenException('无权限操作');
}
5.3 资产管理权限检查
实现位置:src/modules/assets/assets.service.ts
创建/更新/删除资产
// 需要是小组的 Owner 或 Admin
const membership = await this.groupMemberRepository.findOne({
where: { groupId, userId },
});
if (!membership ||
(membership.role !== GroupMemberRole.ADMIN &&
membership.role !== GroupMemberRole.OWNER)) {
throw new ForbiddenException('需要管理员权限');
}
借用资产
// 必须是小组成员才能借用
const membership = await this.groupMemberRepository.findOne({
where: { groupId: asset.groupId, userId },
});
if (!membership) {
throw new ForbiddenException('您不是该小组成员');
}
六、权限相关错误码
定义位置: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
providers: [
// 全局 JWT 认证守卫
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
// 全局角色守卫
{
provide: APP_GUARD,
useClass: RolesGuard,
},
]
执行顺序:
- JwtAuthGuard(先执行)- 验证身份
- RolesGuard(后执行)- 验证角色
7.2 JWT 策略配置
文件位置:src/modules/auth/jwt.strategy.ts
从 JWT Payload 中提取用户信息并注入到 request.user。
八、权限使用最佳实践
8.1 Controller 层
// 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 层
// 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 当前缺失的权限功能
-
系统管理员角色未充分利用
UserRole.ADMIN已定义但未在Controller层使用@Roles()装饰器- 建议:对管理功能使用
@Roles(UserRole.ADMIN)限制
-
小组级权限检查不够细化
- 当前只区分 Owner/Admin/Member
- 建议:可以增加更细粒度的权限点(如:财务管理、成员管理等)
-
缺少权限审计日志
- 建议:记录敏感操作的权限检查日志
9.2 推荐改进
1. 使用 @Roles() 装饰器
// 在控制器中明确标注需要管理员权限的接口
@Roles(UserRole.ADMIN)
@Patch(':id/review')
async review(@CurrentUser() user, @Param('id') id: string) {
return this.blacklistService.review(user.id, id, reviewDto);
}
2. 创建自定义权限守卫
// 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. 添加权限装饰器
// 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 过期后的行为
十一、安全建议
-
Token 管理
- JWT Secret 使用环境变量配置
- 设置合理的 Token 过期时间
- 实现 Refresh Token 机制(已实现)
-
密码安全
- 使用强加密算法存储密码
- 实施密码强度策略
- 防止暴力破解
-
敏感信息保护
- 资产凭据加密存储(已实现)
- 根据权限动态脱敏
- API 响应中排除敏感字段
-
防护措施
- 实施请求限流
- 记录异常访问
- 添加 CORS 配置
- 使用 Helmet 中间件
十二、权限流程图
JWT 认证流程
用户请求 → JwtAuthGuard
↓
检查 @Public()
↓
是 → 直接放行
否 → 验证 JWT Token
↓
有效 → 注入 user 到 request
无效 → 返回 401 错误
↓
RolesGuard
↓
检查 @Roles()
↓
未设置 → 放行
已设置 → 验证用户角色
↓
匹配 → 放行到 Controller
不匹配 → 返回 403 错误
小组权限检查流程
用户操作小组资源
↓
获取小组成员信息
↓
检查成员角色
↓
Owner → 全部权限
Admin → 管理权限(不含解散)
Member → 基础权限
非成员 → 拒绝访问
附录:相关文件清单
核心文件
- src/common/guards/jwt-auth.guard.ts - JWT认证守卫
- src/common/guards/roles.guard.ts - 角色守卫
- src/common/decorators/public.decorator.ts - 公开接口装饰器
- src/common/decorators/roles.decorator.ts - 角色装饰器
- src/common/decorators/current-user.decorator.ts - 当前用户装饰器
- src/common/enums/index.ts - 角色枚举定义
- src/common/interfaces/response.interface.ts - 错误码定义
配置文件
- src/app.module.ts - 全局守卫注册
- src/modules/auth/jwt.strategy.ts - JWT策略
业务模块
- src/modules/auth/auth.controller.ts - 认证接口
- src/modules/users/users.controller.ts - 用户接口
- src/modules/groups/groups.controller.ts - 小组接口
- src/modules/groups/groups.service.ts - 小组权限检查
- src/modules/blacklist/blacklist.controller.ts - 黑名单接口
- src/modules/blacklist/blacklist.service.ts - 黑名单权限检查
- src/modules/assets/assets.controller.ts - 资产接口
- src/modules/assets/assets.service.ts - 资产权限检查
文档版本:v1.0
更新日期:2025-12-20
维护者:开发团队