445 lines
11 KiB
Markdown
445 lines
11 KiB
Markdown
|
|
# RBAC 权限控制使用示例
|
|||
|
|
|
|||
|
|
## 📋 目录
|
|||
|
|
1. [基础使用](#基础使用)
|
|||
|
|
2. [角色控制示例](#角色控制示例)
|
|||
|
|
3. [权限控制示例](#权限控制示例)
|
|||
|
|
4. [完整示例](#完整示例)
|
|||
|
|
|
|||
|
|
## 🔧 基础使用
|
|||
|
|
|
|||
|
|
### 1. 创建权限
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 在数据库中创建权限
|
|||
|
|
const permissions = [
|
|||
|
|
{ code: 'user:create', resource: 'user', action: 'create', name: '创建用户' },
|
|||
|
|
{ code: 'user:read', resource: 'user', action: 'read', name: '查看用户' },
|
|||
|
|
{ code: 'user:update', resource: 'user', action: 'update', name: '更新用户' },
|
|||
|
|
{ code: 'user:delete', resource: 'user', action: 'delete', name: '删除用户' },
|
|||
|
|
{ code: 'role:create', resource: 'role', action: 'create', name: '创建角色' },
|
|||
|
|
{ code: 'role:read', resource: 'role', action: 'read', name: '查看角色' },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
for (const perm of permissions) {
|
|||
|
|
await prisma.permission.create({ data: perm });
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 创建角色并分配权限
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 创建管理员角色
|
|||
|
|
const adminRole = await prisma.role.create({
|
|||
|
|
data: {
|
|||
|
|
name: '管理员',
|
|||
|
|
code: 'admin',
|
|||
|
|
permissions: {
|
|||
|
|
create: [
|
|||
|
|
{ permission: { connect: { code: 'user:create' } } },
|
|||
|
|
{ permission: { connect: { code: 'user:read' } } },
|
|||
|
|
{ permission: { connect: { code: 'user:update' } } },
|
|||
|
|
{ permission: { connect: { code: 'user:delete' } } },
|
|||
|
|
{ permission: { connect: { code: 'role:create' } } },
|
|||
|
|
{ permission: { connect: { code: 'role:read' } } },
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 创建编辑角色(只有查看和更新权限)
|
|||
|
|
const editorRole = await prisma.role.create({
|
|||
|
|
data: {
|
|||
|
|
name: '编辑',
|
|||
|
|
code: 'editor',
|
|||
|
|
permissions: {
|
|||
|
|
create: [
|
|||
|
|
{ permission: { connect: { code: 'user:read' } } },
|
|||
|
|
{ permission: { connect: { code: 'user:update' } } },
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 给用户分配角色
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 给用户分配管理员角色
|
|||
|
|
await prisma.userRole.create({
|
|||
|
|
data: {
|
|||
|
|
user: { connect: { id: 1 } },
|
|||
|
|
role: { connect: { code: 'admin' } }
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 用户可以有多个角色
|
|||
|
|
await prisma.userRole.create({
|
|||
|
|
data: {
|
|||
|
|
user: { connect: { id: 1 } },
|
|||
|
|
role: { connect: { code: 'editor' } }
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🎯 角色控制示例
|
|||
|
|
|
|||
|
|
### 在控制器中使用角色装饰器
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import { Controller, Get, Post, Delete, UseGuards } from '@nestjs/common';
|
|||
|
|
import { Roles } from '../auth/decorators/roles.decorator';
|
|||
|
|
import { RolesGuard } from '../auth/guards/roles.guard';
|
|||
|
|
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
|||
|
|
|
|||
|
|
@Controller('users')
|
|||
|
|
@UseGuards(JwtAuthGuard, RolesGuard) // 先验证 JWT,再验证角色
|
|||
|
|
export class UsersController {
|
|||
|
|
|
|||
|
|
// 所有已登录用户都可以查看
|
|||
|
|
@Get()
|
|||
|
|
findAll() {
|
|||
|
|
return this.usersService.findAll();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 只有管理员和编辑可以创建用户
|
|||
|
|
@Post()
|
|||
|
|
@Roles('admin', 'editor')
|
|||
|
|
create(@Body() createUserDto: CreateUserDto) {
|
|||
|
|
return this.usersService.create(createUserDto);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 只有管理员可以删除用户
|
|||
|
|
@Delete(':id')
|
|||
|
|
@Roles('admin')
|
|||
|
|
remove(@Param('id') id: string) {
|
|||
|
|
return this.usersService.remove(+id);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔐 权限控制示例
|
|||
|
|
|
|||
|
|
### 创建权限守卫(可选扩展)
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// src/auth/guards/permissions.guard.ts
|
|||
|
|
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
|||
|
|
import { Reflector } from '@nestjs/core';
|
|||
|
|
|
|||
|
|
@Injectable()
|
|||
|
|
export class PermissionsGuard implements CanActivate {
|
|||
|
|
constructor(private reflector: Reflector) {}
|
|||
|
|
|
|||
|
|
canActivate(context: ExecutionContext): boolean {
|
|||
|
|
const requiredPermissions = this.reflector.getAllAndOverride<string[]>(
|
|||
|
|
'permissions',
|
|||
|
|
[context.getHandler(), context.getClass()],
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (!requiredPermissions) {
|
|||
|
|
return true; // 没有权限要求,允许访问
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const { user } = context.switchToHttp().getRequest();
|
|||
|
|
const userPermissions = user.permissions || [];
|
|||
|
|
|
|||
|
|
// 检查用户是否拥有任一所需权限
|
|||
|
|
return requiredPermissions.some((permission) =>
|
|||
|
|
userPermissions.includes(permission),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 创建权限装饰器
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// src/auth/decorators/permissions.decorator.ts
|
|||
|
|
import { SetMetadata } from '@nestjs/common';
|
|||
|
|
|
|||
|
|
export const PERMISSIONS_KEY = 'permissions';
|
|||
|
|
export const Permissions = (...permissions: string[]) =>
|
|||
|
|
SetMetadata(PERMISSIONS_KEY, permissions);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 使用权限控制
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import { Controller, Get, Post, Delete, UseGuards } from '@nestjs/common';
|
|||
|
|
import { Permissions } from '../auth/decorators/permissions.decorator';
|
|||
|
|
import { PermissionsGuard } from '../auth/guards/permissions.guard';
|
|||
|
|
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
|||
|
|
|
|||
|
|
@Controller('users')
|
|||
|
|
@UseGuards(JwtAuthGuard, PermissionsGuard)
|
|||
|
|
export class UsersController {
|
|||
|
|
|
|||
|
|
@Get()
|
|||
|
|
@Permissions('user:read') // 需要 user:read 权限
|
|||
|
|
findAll() {
|
|||
|
|
return this.usersService.findAll();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Post()
|
|||
|
|
@Permissions('user:create') // 需要 user:create 权限
|
|||
|
|
create(@Body() createUserDto: CreateUserDto) {
|
|||
|
|
return this.usersService.create(createUserDto);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Delete(':id')
|
|||
|
|
@Permissions('user:delete') // 需要 user:delete 权限
|
|||
|
|
remove(@Param('id') id: string) {
|
|||
|
|
return this.usersService.remove(+id);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 📚 完整示例
|
|||
|
|
|
|||
|
|
### 完整的用户管理控制器
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import {
|
|||
|
|
Controller,
|
|||
|
|
Get,
|
|||
|
|
Post,
|
|||
|
|
Body,
|
|||
|
|
Patch,
|
|||
|
|
Param,
|
|||
|
|
Delete,
|
|||
|
|
Query,
|
|||
|
|
UseGuards,
|
|||
|
|
Request,
|
|||
|
|
} from '@nestjs/common';
|
|||
|
|
import { UsersService } from './users.service';
|
|||
|
|
import { CreateUserDto } from './dto/create-user.dto';
|
|||
|
|
import { UpdateUserDto } from './dto/update-user.dto';
|
|||
|
|
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
|||
|
|
import { RolesGuard } from '../auth/guards/roles.guard';
|
|||
|
|
import { Roles } from '../auth/decorators/roles.decorator';
|
|||
|
|
|
|||
|
|
@Controller('users')
|
|||
|
|
@UseGuards(JwtAuthGuard) // 所有接口都需要登录
|
|||
|
|
export class UsersController {
|
|||
|
|
constructor(private readonly usersService: UsersService) {}
|
|||
|
|
|
|||
|
|
// 查看用户列表 - 所有已登录用户都可以访问
|
|||
|
|
@Get()
|
|||
|
|
findAll(@Query('page') page?: string, @Query('pageSize') pageSize?: string) {
|
|||
|
|
return this.usersService.findAll(
|
|||
|
|
page ? parseInt(page) : 1,
|
|||
|
|
pageSize ? parseInt(pageSize) : 10,
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查看用户详情 - 所有已登录用户都可以访问
|
|||
|
|
@Get(':id')
|
|||
|
|
findOne(@Param('id') id: string) {
|
|||
|
|
return this.usersService.findOne(+id);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建用户 - 需要 admin 或 editor 角色
|
|||
|
|
@Post()
|
|||
|
|
@UseGuards(RolesGuard)
|
|||
|
|
@Roles('admin', 'editor')
|
|||
|
|
create(@Body() createUserDto: CreateUserDto, @Request() req) {
|
|||
|
|
// req.user 包含当前用户信息(从 JWT 中提取)
|
|||
|
|
return this.usersService.create(createUserDto);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新用户 - 需要 admin 角色,或者用户自己更新自己
|
|||
|
|
@Patch(':id')
|
|||
|
|
@UseGuards(RolesGuard)
|
|||
|
|
async update(
|
|||
|
|
@Param('id') id: string,
|
|||
|
|
@Body() updateUserDto: UpdateUserDto,
|
|||
|
|
@Request() req,
|
|||
|
|
) {
|
|||
|
|
const userId = parseInt(id);
|
|||
|
|
const currentUserId = req.user.userId;
|
|||
|
|
|
|||
|
|
// 管理员可以更新任何人,普通用户只能更新自己
|
|||
|
|
if (req.user.roles?.includes('admin') || userId === currentUserId) {
|
|||
|
|
return this.usersService.update(userId, updateUserDto);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
throw new ForbiddenException('无权更新此用户');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 删除用户 - 只有管理员可以删除
|
|||
|
|
@Delete(':id')
|
|||
|
|
@UseGuards(RolesGuard)
|
|||
|
|
@Roles('admin')
|
|||
|
|
remove(@Param('id') id: string) {
|
|||
|
|
return this.usersService.remove(+id);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔍 权限检查流程
|
|||
|
|
|
|||
|
|
### 1. 用户登录
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// POST /api/auth/login
|
|||
|
|
{
|
|||
|
|
"username": "admin",
|
|||
|
|
"password": "password123"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 返回
|
|||
|
|
{
|
|||
|
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|||
|
|
"user": {
|
|||
|
|
"id": 1,
|
|||
|
|
"username": "admin",
|
|||
|
|
"nickname": "管理员",
|
|||
|
|
"roles": ["admin"], // 用户的角色列表
|
|||
|
|
"permissions": [ // 用户的所有权限(从角色中聚合)
|
|||
|
|
"user:create",
|
|||
|
|
"user:read",
|
|||
|
|
"user:update",
|
|||
|
|
"user:delete",
|
|||
|
|
"role:create",
|
|||
|
|
"role:read"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 访问受保护的接口
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 请求头
|
|||
|
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|||
|
|
|
|||
|
|
// 流程
|
|||
|
|
1. JwtAuthGuard 验证 Token
|
|||
|
|
└─> 提取用户信息,添加到 req.user
|
|||
|
|
|
|||
|
|
2. RolesGuard 检查角色
|
|||
|
|
└─> 从 req.user.roles 中检查是否包含所需角色
|
|||
|
|
└─> 如果包含,允许访问;否则返回 403 Forbidden
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🎨 前端权限控制示例
|
|||
|
|
|
|||
|
|
### Vue 3 中使用权限
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// stores/auth.ts
|
|||
|
|
export const useAuthStore = defineStore('auth', () => {
|
|||
|
|
const user = ref<User | null>(null);
|
|||
|
|
|
|||
|
|
// 检查是否有指定角色
|
|||
|
|
const hasRole = (role: string) => {
|
|||
|
|
return user.value?.roles?.includes(role) ?? false;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 检查是否有指定权限
|
|||
|
|
const hasPermission = (permission: string) => {
|
|||
|
|
return user.value?.permissions?.includes(permission) ?? false;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 检查是否有任一角色
|
|||
|
|
const hasAnyRole = (roles: string[]) => {
|
|||
|
|
return roles.some(role => hasRole(role));
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 检查是否有任一权限
|
|||
|
|
const hasAnyPermission = (permissions: string[]) => {
|
|||
|
|
return permissions.some(perm => hasPermission(perm));
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
user,
|
|||
|
|
hasRole,
|
|||
|
|
hasPermission,
|
|||
|
|
hasAnyRole,
|
|||
|
|
hasAnyPermission,
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 在组件中使用
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<div>
|
|||
|
|
<!-- 根据角色显示按钮 -->
|
|||
|
|
<a-button v-if="authStore.hasRole('admin')" @click="deleteUser">
|
|||
|
|
删除用户
|
|||
|
|
</a-button>
|
|||
|
|
|
|||
|
|
<!-- 根据权限显示按钮 -->
|
|||
|
|
<a-button v-if="authStore.hasPermission('user:create')" @click="createUser">
|
|||
|
|
创建用户
|
|||
|
|
</a-button>
|
|||
|
|
|
|||
|
|
<!-- 根据角色或权限显示 -->
|
|||
|
|
<a-button
|
|||
|
|
v-if="authStore.hasAnyRole(['admin', 'editor']) || authStore.hasPermission('user:update')"
|
|||
|
|
@click="editUser"
|
|||
|
|
>
|
|||
|
|
编辑用户
|
|||
|
|
</a-button>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { useAuthStore } from '@/stores/auth';
|
|||
|
|
|
|||
|
|
const authStore = useAuthStore();
|
|||
|
|
</script>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 路由守卫
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// router/index.ts
|
|||
|
|
router.beforeEach((to, from, next) => {
|
|||
|
|
const authStore = useAuthStore();
|
|||
|
|
|
|||
|
|
// 检查是否需要认证
|
|||
|
|
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
|
|||
|
|
next({ name: 'Login' });
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查角色
|
|||
|
|
if (to.meta.roles && !authStore.hasAnyRole(to.meta.roles)) {
|
|||
|
|
next({ name: 'Forbidden' });
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查权限
|
|||
|
|
if (to.meta.permissions && !authStore.hasAnyPermission(to.meta.permissions)) {
|
|||
|
|
next({ name: 'Forbidden' });
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
next();
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 📊 权限矩阵示例
|
|||
|
|
|
|||
|
|
| 角色 | user:create | user:read | user:update | user:delete | role:create | role:read |
|
|||
|
|
|------|-------------|-----------|-------------|------------|-------------|-----------|
|
|||
|
|
| admin | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|||
|
|
| editor | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
|
|||
|
|
| viewer | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|||
|
|
|
|||
|
|
## 🎯 总结
|
|||
|
|
|
|||
|
|
RBAC 权限控制的核心是:
|
|||
|
|
|
|||
|
|
1. **用户** ←→ **角色** ←→ **权限**
|
|||
|
|
2. 通过 `@Roles()` 装饰器控制接口访问
|
|||
|
|
3. 前端根据返回的 `roles` 和 `permissions` 控制 UI 显示
|
|||
|
|
4. 权限由 `resource:action` 组成,如 `user:create`
|
|||
|
|
|
|||
|
|
这样的设计既保证了安全性,又提供了良好的灵活性和可维护性!
|
|||
|
|
|