如何在Next.js中添加基于角色的访问控制(RBAC)权限管理
授权就是决定用户能对哪些资源进行什么操作的过程,这是我们应用程序中的一个关键要求。采用基于角色的访问控制(RBAC)是管理并控制我们应用的授权的一种简单方法。
我们将讨论如何在Next.js应用程序中添加RBAC权限管理,并以安全且可扩展的方式实现。
我们首先将在Next.js中设置一个基本的Todo应用程序,并实现JWT进行用户身份验证。接下来,我们使用Permit.io配置RBAC策略,同步用户,并将某个用户的角色升级为具有全部权限的管理员角色。
到本教程结束时,您将清楚地了解如何使用 RBAC 来确保您的 Next.js 应用程序安全,根据他们的角色完全控制他们可以访问的内容。
开始吧!
搭建一个基本的 Next.js 项目要开始,让我们创建一个新的 Next.js 项目。为了节省时间(假设您已经了解 Next.js 的基础知识),我们已经为您准备好了一个启动项目,在这个项目中,您会找到我们将在本教程中使用的简单待办事项应用。
- 确保您的机器上已安装 Node.js 和 npm。这两个工具可以在 Node.js 官方网站 下载。
- 打开终端窗口并输入如下命令来创建一个新的 Next.js 应用程序:
git clone https://github.com/SagarSingh2003/rbac-permit-starter-next.js
这将复制一个 Next.js 项目,使用默认设置并包含启动代码。
创建项目之后,使用命令运行以下内容以切换到项目目录:
切换到rbac-permit-starter-next.js目录
cd rbac-permit-starter-next.js
首先,我们将运行以下指令来安装依赖项。
运行`npm install`以安装依赖包
现在我们已经成功地在本地设置了我们的 Next.js 项目,我们可以接着实现一个基本的 RBAC(角色基础访问控制)模型。
在 Next.js 中使用 JWT 认证用户在开始权限确认之前,我们需要先验证用户。
认证过程确认并分配给每个用户唯一的标识,这有助于区分用户。授权则是为了限制用户只能做他们在应用中被授权的事情,这样更自然。
为了使我们的应用程序保持简洁,我们使用了分别具有管理员和用户权限的两个用户的模拟JWT。同样的方法也适用于例如Clerk、Auth0或Kinde这样的提供商。
虚拟用户的详细信息如下:
创建以下两个用户及其登录信息:
用户A:
- 键 : 1
- 电子邮件 : admin@gmail.com
- 名字 : Project
- 姓氏 : Admin
- 租户名 : Todo-tenant(或您创建的租户)
- 角色 : 管理员角色
用户2 (一般用户):
- 编号 : 2,
- 电子邮件 : user@gmail.com
- 名字 : Project
- 姓氏 : User
- 租户名称 : Todo-tenant(或你创建的租户)
- 角色 : 用户角色
在你的代码编辑器里,找到 src 目录下的 data 文件夹里的 sampleData.js 文件。
const sampleData = [
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwibmFtZSI6IlByb2plY3QgQWRtaW4iLCJlbWFpbCI6ImFkbWluQGdtYWlsLmNvbSJ9.KtQpee_bZF_Sx0t87trx8-ljuE3SwJ7SZYeYzZO-694",
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwibmFtZSI6IlByb2plY3QgVXNlciIsImVtYWlsIjoidXNlckBnbWFpbC5jb20ifQ.FVG2dcFVsOy1cmzImHqtbeJ0mnT1h4aCSN7aSPq9Xew",
];
// sampleData 包含了两个 JSON 字符串,每个字符串代表一个具有唯一标识符、名称和电子邮件的用户数据。
export default sampleData;
这包含了上述凭证用户的JWT。我们将解码这些JWT来获取用户信息。
授权反模式(Antipattern)开发人员经常使用以下指令性条件语句来实施基于角色的访问控制(RBAC)规则。常用的RBAC规则包括:
在这个例子中,我们有一个用户对象,它有ID和角色。
deleteUser 函数会先确认当前用户是否是“admin”角色,然后再允许该用户删除。
updatePost 这个函数检查用户的角色是否已被更新。
直接写死**if**
语句中的授权规则硬编码会带来几个主要问题:
- 重复授权:在函数调用中添加授权常常导致在多个函数中重复进行授权,从而使代码库变得杂乱,从而增加在更新时出错的风险。
- 复杂性:随着角色和权限的增多,嵌套的
if
语句变得复杂并且难以维护,使代码容易出错且难读。 - 不灵活:硬编码的授权逻辑需要手动更新角色或权限的任何变化,这既耗时又容易出错的。
在本教程中,我们将使用Permit.io来实现RBAC——一个作为服务的授权提供商。Permit.io通过提供一个更集中化、灵活且可扩展的权限管理方案,解决了硬编码授权的不足。
配置基本的 RBAC 策略,允许在 Permit 中使用。
要开始设置权限,请登录app.permit.io。然后,就为这个项目新建一个权限设置。
按照以下步骤来。
- 在Permit中创建账户
- 输入工作区名称
- 输入工作区密钥
- 点按“启动账户”
接下来,我们需要创建一个资源。资源是我们应用程序中的任何需要保护或管理访问权限的元素。例如,这里的 Todo 代表了需要管理和保护的任务,是一个资源。
让我们创建一个叫做Todo的资源
- 进入策略页面并点击创建 > 资源
- 创建以下资源
- 分配以下五个与之相关的操作:(创建、读取、修改、更新、删除):
接下来,我们将会创建一个角色。角色是一种方便的工具,可以将权限组分组并分配给用户等实体。
- 前往策略页面,然后点击创建 > 角色
- 添加以下角色,并根据下方所示添加相关操作:
-
管理员(Admin)
- 用户(User)
保存这些改动。
接下来,我们接着会去“策略编辑区域”,并设定策略。在设定策略前,我们先来谈谈具体的操作。
- 创建:在待办事项列表中新增一个任务。
- 查看:查看任务详情。
- 更新:将任务标记为已完成或未完成。
- 编辑:更改任务的名称、截止日期或优先级。
- 删除:从待办事项列表中移除一个任务。
我们需要更新一下我们的政策,让管理员可以执行所有这些操作,而用户只能读和更新。
要创建此策略,请转到策略编辑器页面,选中如图所示的所有复选框,然后保存修改。
就这样。我们已经成功搭建好了RBAC(基于角色的访问控制)模型,现在我们就可以在应用程序中使用这个模型了。
将用户同步至权限现在我们创建了RBAC配置,让我们将这些虚拟用户账号添加到我们的Permit目录中。因为我们使用的是虚拟的JWT,所以我们将手动添加用户到Permit目录中。如果有身份验证提供器,你可以使用syncUser
API将所有现有用户同步到Permit中。
咱们开始吧!
- 前往许可目录页面
- 选择默认的租户
- 点击添加用户按钮
填写如下用户信息并保存:
会员
- 管理员
现在我们在Permit里创建了一些用户,可以开始写代码了。
首先,我们来设置一下环境变量吧
- 前往
**设置 > API 密钥 > 开发密钥**
页面 - 复制该密钥并将其粘贴到 .env 文件中:
如下所示:
PERMIT_TOKEN="复制的API密钥"
现在我们来将应用程序与Permit.io连接,以实施我们设定的政策。
为此,我们将创建这样一个网关,用于初始化Permit.io客户端并使用一个API令牌和一个PDP端点。
这种设置让应用可以按照预设的访问控制规则执行权限检查。
- 进入
**src > lib > permitProvider.js**
- 按以下方式启动 Permit.io 客户端如下:
import { Permit } from 'permitio';
const permit = new Permit({
// 用于与 Permit.io API 进行身份验证的令牌(token)
token: process.env.NEXT_PUBLIC_PERMIT_TOKEN,
// 策略决策点(PDP)URL,Permit.io 用于评估策略
pdp: "https://cloudpdp.api.permit.io",
});
export default permit;
我们已经创建了两个用户,是时候利用现有的策略来测试我们基于角色的访问控制(RBAC)模型是否按预期工作了。
在继续前进之前,让我们讨论一下如何根据用户注册的角色授予或限制访问权限的检查机制。
我们来看一下怎么给应用程序授权。
- 每当用户想在前端做某事时,前端会将
user_id
和操作名称发送到后端 API。 - 我们的后端会检查用户是否有权限做这件事。
- 根据用户是否被允许,后端会把回应发回前端。
- 前端接收到响应后,如果允许,就执行;否则,弹出个窗口告诉用户没权限。
现在我们已经把应用程序和Permit.io连起来了,看看Permit.io是怎么管理资源的了。
- 打开文件
**src > api > check-permission > route.js**
- 添加以下代码,
import permit from "@/lib/permitProvider";
export async function GET(req) {
const { searchParams } = new URL(req.url)
const id = searchParams.get('id');
const operation = searchParams.get('operation')
try {
const permitted = await permit.check(String(id), String(operation), {
type: 'TodoTasks',
tenant: 'todo-tenant',
});
if (permitted) {
return Response.json({
success: true,
message: "permitted"
}, { status: 200 })
}
} catch (err) {
console.error(`权限检查出错: ${err}`);
}
return Response.json({
success: false,
message: "未获许可"
}, { status: 403 })
}
在上述代码中,我们提取查询参数值,通过Permit.io检查权限并将结果返回。
以下是一个逐步解析:
获取查询参数:
GET
函数是由发送到此API路由的HTTP GET请求触发的,该请求会调用API。- 它会从请求的URL里提取
id
和operation
这两个参数。
使用 Permit.io 确认是否拥有权限:
- 调用
permit.check()
方法来确认具有id
的用户是否有权限在todo-tenant
租户中的TodoTasks
(待办事项)资源上执行如创建、更新或删除等操作。
让我们返回结果吧
- 成功 (状态码 200):如果用户有必要的权限,将成功回复状态码为 200。
- 未经授权访问 (状态码 403):如果用户没有所需权限,会返回状态码 403 的错误信息。
现在来看一下集中式的API调用功能
const 检查权限 = async (操作) => {
const response = await fetch(`/api/check-permission?id=${标识符}&操作=${操作}`);
const data = await response.json();
return data.成功;
};
它会根据我们设计的 API 路由,用所需的 ID 和操作参数进行反馈。
基于角色的资源访问限制:我们有一个待办事项(Todo)资源,根据其规则,具有“Admin”角色的人可以执行所有五项操作:查看、创建、编辑/修改、更新和删除。而拥有“User”角色的人只能查看和更新。
创建: 检查该个体是否有“编辑”权限,若有则用新值更新该待办事项。
const handleSaveEdit = async () => {
if (!await checkPermission("edit")) {
alert("您没有权限编辑此待办事项。");
return;
}
const updatedTodos = todos.map((todo, i) =>
i === editingIndex
? { ...todo, content: editingContent, 截止时间: editingDeadline, priority: editingPriority }
: todo
);
setTodos(updatedTodos);
setEditingIndex(null);
setEditingContent("");
setEditingDeadline("");
setEditingPriority("低优先级");
};
检查该用户是否具有“编辑”权限来更新指定待办事项的更新详情。
const handleSaveEdit = async () => {
if (!await checkPermission("edit")) {
alert("您没有权限编辑待办事项哦。");
return;
}
const updatedTodos = todos.map((todo, index) =>
index === editingIndex
? { ...todo, content: editingContent, deadline: editingDeadline, priority: "低" }
: todo
);
setTodos(updatedTodos);
setEditingIndex(null);
setEditingContent("");
setEditingDeadline("");
setEditingPriority("低");
};
更新:用于更改指定待办事项的完成状态,前提是有权限更新。
const handleToggleDone = async (index) => {
if (!await checkPermission("修改")) {
alert("您没有权限修改这条待办事项。");
return;
}
const updatedTodos = todos.map((todo, i) =>
i === index ? { ...todo, done: !todo.done } : todo
);
setTodos(updatedTodos);
};
删除: 检查该用户是否有“删除”权限,并从列表中删除指定的待办事项。
const handleDeleteTodo = async (index) => {
if (!(await checkPermission('删除'))){
alert("您没有权限这么做。");
return;
}
const updatedTodos = todos.filter((_, i) => i !== index);
setTodos(updatedTodos);
};
我们已经添加了所有必需的功能,通过定义功能来限制个人操作,以在我们的待办事项应用中创建、修改、更新和移除项目,这些功能是基于定义的角色。
就这样,我们用 Next.js 和 Permit 完成了这个简易待办事项应用 🥳。
你可以通过运行来启动应用程序,比如:
# 要启动应用,请在此处填入具体的命令
运行开发命令 `npm run dev`
这将在本地服务器(http://localhost:3000)运行我们的Next.js应用。
我们Todo应用的示例是时候看看我们待办清单应用的演示了,该演示将展示用户权限功能以及一个拥有完整权限的管理员角色。
‘用户’权限的个人示例
在接下来的演示中,我们将展示用户只能查看待办事项,但不能编辑或删除它们。
- 查看待办事项: 用户可以查看待办事项。
- 无法编辑待办事项: 用户不能修改待办事项。
- 无法删除待办: 用户不能删除待办。
将我们的‘User’更新为‘Admin’
要将用户的角色从“用户”改为“管理员”,请导航至Permit.io管理控制台,并如图所示更新用户的角色分配。
管理员“用户”权限设置演示示例
将角色更新为Admin后,用户现在具有应用程序中的全部权限。作为Admin,用户可以进行以下操作:管理用户、权限设置等。
- 编辑待办事项: 更改现有的待办事项。
- 删除待办事项: 移除待办事项。
- 阅读待办事项: 阅读现有的待办事项。
下面的视频展示了Admin(管理员)角色增强的功能:
使用Permit在任何应用中实施RBAC策略是如此简单。
我们在 Permit.io 控制台的审计日志部分可以看到请求的记录轨迹,如下图所示:
在本教程中,我们介绍了如何在 Next.js 应用程序中设置并配置 RBAC,以便根据用户角色控制访问权限。
现在你已经在你的Next.js应用程序中成功地实现了RBAC,可以通过将其应用到实际场景中来提升应用程序的安全性。
如果你想要比用户角色更细化的控制,但又需要更详细的用户身份信息,那么我建议你查看Permit的更多学习资料,比如RBAC和ABAC的区别,以及如何使用Permit.io为你的应用添加ABAC。
想要了解更多关于授权实现的信息?有问题想问吗?加入我们的Slack社区。
简单易懂 🚀感谢你加入“在平实英语”社区!离开之前,
- 记得给作者点赞👏
- 关注我们在以下平台:X | LinkedIn | YouTube | Discord | Newsletter
- 还可以看看我们的其他平台:CoFeed | Differ
- 还有更多精彩内容,可以去PlainEnglish.io查看
共同学习,写下你的评论
评论加载中...
作者其他优质文章