使用NestJS和Prisma构建安全的RESTful API ... - ZenStack
使用 NestJS 和 Prisma 构建安全的 RESTful API
NestJS 是一个功能强大且文档完善的框架,适用于构建各种后端应用程序,包括 RESTful、GraphQL、WebSocket 和微服务等。在后端开发中,基于数据库构建 API 是开发者的核心任务之一。而随着 Prisma ORM 的流行,越来越多的开发者选择将其与 NestJS 结合使用,以实现更高效的开发体验。
本文将介绍三种构建安全 RESTful API 的方法,逐步展示如何减少代码量并提升开发效率。
初始项目配置
在我们的示例项目中,已经配置了一个用于数据库访问的 PrismaService,并实现了一些基本的 CRUD 端点。这些端点可以列出用户和帖子,并管理帖子的发布状态。然而,这些 API 缺乏访问控制,任何人都可以随意操作数据。
为了让 API 更加安全和实用,我们需要满足以下业务需求:
用户相关规则
- 所有人可以创建账户。
- 除了“电子邮件”字段,所有用户都可以查看其他用户的配置文件。
- 用户只能更新自己的个人资料。
- 用户分为两种角色:“用户(USER)”和“编辑(EDITOR)”。
帖子相关规则
- “用户”角色的用户可以完全操作自己的帖子,但不能更新
published字段;对其他用户的帖子没有写权限。 - “编辑”角色的用户可以完全操作所有帖子,包括更新
published字段。 - 已发布的帖子对所有人可读。
为了简化示例,我们将使用 x-user-id 和 x-user-role HTTP 标头来模拟用户身份和角色,而非实现完整的身份验证模块。
方法一:传统实现方式
最直接的实现方式是通过代码手动检查权限。例如,我们可以通过 AsyncLocalStorage 提取用户身份,并在控制器和服务中注入身份信息。以下是一些示例代码:
// 示例代码:检查用户权限
if (user.role === 'USER' && user.id !== post.authorId) {
throw new Error('Unauthorized');
}
尽管这种方式可以满足需求,但手动编写复杂的条件逻辑容易出错,且难以维护。
方法二:使用 ZModel 声明式定义规则
为了简化权限管理,我们可以使用 ZenStack 的 ZModel 以声明式方式定义访问规则。以下是示例规则:
model User {
email String @auth((user) => user.id === this.id) // 仅用户自己可见
profile String @auth((user) => user.id === this.id || user.role === 'EDITOR')}model Post {
published Boolean @auth((user) => user.role === 'EDITOR') authorId Int @auth((user) => user.id === this.authorId || user.role === 'EDITOR')}
规则说明
- 用户只能查看自己的电子邮件地址。
- 用户可以完全访问自己的个人资料。
- 其他用户可以查看所有配置文件,但不能查看电子邮件字段。
- 只有“编辑”角色的用户可以更新
published字段。 - 作者和“编辑”角色用户可以完全访问帖子。
- 已发布的帖子对所有人可见。
通过运行 npx zenstack-generate,我们可以生成 Prisma 模式和相关支持代码。
方法三:使用 Enhanced Prisma Service
ZenStack 提供了一个增强的 Prisma API,可以自动注入访问控制逻辑。我们可以创建一个 EnhancedPrismaService,并将其作为现有 PrismaService 的包装器:
import { EnhancedPrismaClient } from '@zenstackhq/runtime';
@Injectable()
export class EnhancedPrismaService extends PrismaService {
constructor() {
super();
this.client = new EnhancedPrismaClient(this.client);
}
}
工作原理
EnhancedPrismaClient会拦截 CRUD 调用,并根据 ZModel 中的规则自动执行权限检查。- 例如,当调用
enhancedPrisma.post.findMany()时,返回的结果会自动过滤为当前用户可访问的帖子。
通过这种方式,我们可以从控制器中移除冗余的权限检查逻辑,代码更加清晰简洁。
方法四:自动生成 RESTful API
ZenStack 提供了内置的 ExpressJS 中间件,可以基于 ZModel 自动生成完整的 RESTful CRUD API。由于 NestJS 默认使用 Express 处理 HTTP 请求,我们可以直接集成该中间件。
实现步骤
-
创建一个封装 ZenStack 中间件的 NestJS 中间件:
import { ZenStackMiddleware } from '@zenstackhq/express'; @Injectable() export class ZenStackNestMiddleware { use(req: Request, res: Response, next: NextFunction) { ZenStackMiddleware(req, res, next); } } -
在路由中安装中间件:
app.use('/api', zenStackNestMiddleware); -
启用 OpenAPI 支持:
在 ZModel 文件中启用@zenstackhq/[openapi](https://www.explinks.com/blog/wx-safe-and-user-friendly-openapi)插件,并运行npx zenstack-generate生成 OpenAPI 文档。
总结
通过本文介绍的三种方法,我们可以逐步优化 NestJS 和 Prisma 的 API 开发流程:
最终,通过将访问控制逻辑集中到模式中,我们不仅提升了代码的可维护性,还确保了 API 的安全性。
原文链接:GitHub 示例项目
编码愉快!
原文链接: https://zenstack.dev/blog/nest-api