如何在 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/Arindam200/Permit-RBAC
这将克隆一个包含启动代码的默认设置的 Next.js 项目。
- 创建项目后,通过运行以下命令导航到项目目录:
cd rbac-permit-starter-next.js
- 接下来,我们将通过运行以下命令安装所需的依赖项:
npm install
现在我们已经在本地成功设置了 Next.js 项目,我们可以继续实现基本的 RBAC 模型。
在 Next.js 中使用 JWT 验证用户身份
在开始授权之前,我们需要对用户进行身份验证。
身份验证阶段验证每个用户并提供唯一的身份,这有助于区分不同的用户。授权阶段限制用户只能在应用程序中执行他们被授权执行的操作。
为了简化应用程序,我们使用两个用户的虚拟 JWT 来演示管理员和用户权限。同样的方法也适用于任何提供 JWT 的身份验证提供商,例如 Clerk、Auth0 或 Kinde。
虚拟用户详细信息如下:
- 使用以下凭据创建两个用户:
- 用户 1(管理员):
- 密钥:1
- 电子邮件: admin@gmail.com
- 名字:项目
- 姓氏: 管理员
- 租户:Todo 租户(或您创建的租户)
- 角色:管理员
- 用户 2(普通用户):
- 密钥:2
- 电子邮件: user@gmail.com
- 名字:项目
- 姓氏:用户
- 租户:Todo 租户(或您创建的租户)
- 角色:用户
- 用户 1(管理员):
在代码编辑器中,导航到文件 src > data > sampleData.js 内的代码
const sampleData = [
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwibmFtZSI6IlByb2plY3QgQWRtaW4iLCJlbWFpbCI6ImFkbWluQGdtYWlsLmNvbSJ9.KtQpee_bZF_Sx0t87trx8-ljuE3SwJ7SZYeYzZO-694",
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwibmFtZSI6IlByb2plY3QgVXNlciIsImVtYWlsIjoidXNlckBnbWFpbC5jb20ifQ.FVG2dcFVsOy1cmzImHqtbeJ0mnT1h4aCSN7aSPq9Xew",
];
export default sampleData;
这包含具有上述凭据的两个用户的 JWT。我们将解码这些 JWT 以获取用户信息。
授权反模式
开发人员经常使用以下命令式条件语句来强制执行 RBAC 规则。
在这个例子中,我们有一个具有 ID 和角色属性的用户对象。
deleteUser 函数在允许当前用户删除用户之前会检查当前用户的角色是否为“管理员”。
updatePost 函数检查用户角色是否已更新。
使用命令式语句对授权规则进行硬编码if
会产生几个问题:
- 重复:在函数调用中添加授权通常会导致跨多个函数重复检查,从而使代码库变得混乱并增加更新期间出错的风险。
- 复杂性:随着角色和权限的增加,嵌套
if
语句变得更加复杂和难以维护,使得代码容易出现错误并且难以阅读。 - 缺乏灵活性:硬编码授权逻辑需要手动更新角色或权限的任何变化,这既耗时又容易出错。
在本教程中,我们将使用Permit.io (一个授权即服务提供商)实现 RBAC。Permit.io通过提供更集中、更灵活、更可扩展的权限管理方法,解决了硬编码授权的弊端。
在“允许”中配置基本 RBAC 策略
要开始配置权限,请登录app.permit.io。然后,我们将为该项目创建一个新的工作区。
请按照以下步骤操作:
- 在 Permit 中创建帐户
- 输入工作区名称
- 提供工作区密钥
- 点击“启动您的帐户”
接下来,我们需要创建一个资源。资源是指应用程序中需要保护或管理访问权限的任何元素。例如,在此应用程序中,Todo 是一个资源,它代表我们需要管理和保护的任务。
让我们创建一个名为Todo的资源
- 进入策略页面,点击创建 > 资源
- 创建以下资源
- 分配与其相关的五个操作(创建、读取、更新、编辑、删除):
接下来,我们将创建一个角色。角色是一种将权限分组并分配给用户或其他实体的简便方法。
- 进入策略页面,单击创建 > 角色
- 添加以下角色,其操作如下所示:
- 行政
- 用户
- 保存这些更改。
接下来,我们将进入“策略编辑器”部分并设置策略。在设置策略之前,让我们先讨论一下操作:
- 创建:向待办事项列表添加新任务。
- 阅读:查看任务的详细信息。
- 更新:将任务标记为已完成或未完成。
- 编辑:更改任务的名称、截止日期或优先级。
- 删除:从待办事项列表中删除任务。
我们需要更新我们的策略,以便管理员有权执行所有操作,而用户只能执行读取和更新操作。
要创建此策略,请转到策略编辑器,选中如下所示的所有复选框,然后保存更改。
就这样。我们已经成功设置了基于角色的访问控制 (RBAC) 模型,可以在我们的应用程序中使用它。
同步用户以允许
现在我们已经创建了 RBAC 配置,让我们将虚拟用户添加到 Permit.io 目录中。由于我们使用的是虚拟 JWT,因此需要手动将用户添加到 Permit 目录中。如果您有身份验证提供程序,则可以使用syncUser
API 将所有现有用户导入 Permit。
开始:
- 前往许可证目录
- 选择默认租户
- 点击添加用户
- 填写用户详细信息,如下所示并保存:
- 普通用户
- 管理员用户
现在我们已经在 Permit 中创建了一些用户,我们可以开始编码了。
让我们首先添加环境变量:
- 前往
Settings > API Keys > Developmental secret key
- 复制密钥并将其粘贴到 .env 中,如下所示:
PERMIT_TOKEN=”Copied API KEY”
现在让我们将我们的应用程序与Permit.io连接起来以实现我们设置的策略。
为此,我们将创建一个网关,该网关使用 API 令牌和策略决策点 (PDP) 端点初始化Permit.io
客户端。 此设置使应用程序能够根据预定义的访问控制规则执行授权检查。
- 导航至
src > lib > permitProvider.js
- 初始化Permit.io客户端如下:
import { Permit } from 'permitio';
const permit = new Permit({
// The token used for authenticating with the Permit.io API
token: process.env.NEXT_PUBLIC_PERMIT_TOKEN,
// The Policy Decision Point (PDP) URL that Permit.io uses to evaluate policies
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("Error checking permissions:", err);
}
return Response.json({
success: false,
message: "not permitted"
}, { status: 403 })
}
在上面的代码中,我们提取查询参数,使用Permit.io检查权限并返回结果。
以下是分步说明:
- 提取查询参数:
- 该
GET
功能由对此 API 路由的 HTTP GET 请求触发。 - 它从请求的 URL 中检索
id
和参数。operation
- 该
- 使用 Permit.io 检查权限:
permit.check()
调用该方法来验证所标识的用户是否有权对内的资源id
执行指定的操作(例如,创建、更新、删除)。TodoTasks
todo-tenant
- 返回结果:
- 成功(200):如果用户具有必要的权限,则返回状态代码为 200 的成功响应。
- 未授权(403):如果用户没有所需的权限,则返回状态代码为 403 的失败响应。
现在看一下进行 API 调用的集中函数
const checkPermission = async (operation) => {
const response = await fetch(`/api/check-permission?id=${identifier}&operation=${operation}`);
const data = await response.json();
return data.success;
};
它使用所需的参数 ID 和操作来响应我们设计的 API 路由。
根据角色限制对资源的访问
我们有 Todo 资源,根据其策略,具有“管理员”角色的个人可以执行所有五项操作:读取、创建、编辑、更新和删除。具有“用户”角色的个人只能执行读取和更新操作。
创建:用于检查个人是否具有“编辑”权限并使用新值更新指定的待办事项。
const handleSaveEdit = async () => {
if (!await checkPermission("edit")) {
alert("You do not have permission to edit a to-do.");
return;
}
const updatedTodos = todos.map((todo, i) =>
i === editingIndex
? { ...todo, content: editingContent, deadline: editingDeadline, priority: editingPriority }
: todo
);
setTodos(updatedTodos);
setEditingIndex(null);
setEditingContent("");
setEditingDeadline("");
setEditingPriority("low");
};
编辑:用于检查个人是否具有“编辑”权限,以便使用新的详细信息更新指定的待办事项。
const handleSaveEdit = async () => {
if (!await checkPermission("edit")) {
alert("You do not have permission to edit a to-do.");
return;
}
const updatedTodos = todos.map((todo, index) =>
index === editingIndex
? { ...todo, content: editingContent, deadline: editingDeadline, priority: editingPriority }
: todo
);
setTodos(updatedTodos);
setEditingIndex(null);
setEditingContent("");
setEditingDeadline("");
setEditingPriority("low");
};
更新:用于检查个人是否具有“更新”权限,切换指定待办事项的“完成”状态。
const handleToggleDone = async (index) => {
if (!await checkPermission("update")) {
alert("You do not have permission to update a to-do.");
return;
}
const updatedTodos = todos.map((todo, i) =>
i === index ? { ...todo, done: !todo.done } : todo
);
setTodos(updatedTodos);
};
删除:用于检查个人是否具有“删除”权限并从列表中删除指定的待办事项。
const handleDeleteTodo = async (index) => {
if (!await checkPermission("delete")) {
alert("You do not have permission to delete a to-do.");
return;
}
const updatedTodos = todos.filter((_, i) => i !== index);
setTodos(updatedTodos);
};
我们根据定义的角色,通过定义从我们的 Todo 应用程序创建、编辑、更新和删除项目的功能,添加了限制个人所需的所有操作。
这样,我们的带有 Next.js 和 Permit 的简单 Todo 应用程序就完成了🥳。
您可以通过运行来启动应用程序
npm run dev
这将在http://localhost:3000上启动我们的 Next.js 应用程序 。
我们的 Todo 应用程序演示
具有用户权限的个人演示
在这个演示中,我们将展示我们的用户如何只能阅读待办事项但不能编辑或删除它们:
- 阅读待办事项: 具有用户角色的个人可以阅读待办事项。
- 限制编辑博客: 用户不能修改现有的待办事项。
- 限制删除待办事项: 用户没有删除待办事项的权限。
将我们的用户更新为管理员
要将用户的角色从用户更改为管理员,请导航到Permit.io仪表板并更新用户的角色分配,如下所示:
具有所有权限的管理员用户的演示
将角色更新为管理员后,用户现在拥有应用程序内的全部权限。作为管理员,用户可以:
- 更新待办事项:修改现有待办事项。
- 删除待办事项:从列表中删除待办事项。
- 阅读待办事项:阅读现有的待办事项。
以下视频演示了管理员角色的增强功能:
这就是使用 permit.io 在任何应用程序中实现 RBAC 策略变得如此简单的原因。
我们还可以从 Permit.io 仪表板的审计日志部分看到请求的踪迹,如下所示:
结论
在本教程中,我们探讨了如何使用 Permit.io在 Next.js 应用程序中设置和配置 RBAC ,以根据用户角色控制用户访问。
现在您已在应用程序中实现了 RBAC,您可以通过将其应用于应用程序中的实际用例来提高应用程序的安全性。
如果您想要比用户角色更细粒度的控制级别但需要用户详细身份怎么办?
为此,我们建议您继续阅读我们的学习材料,例如 RBAC 和 ABAC 之间的区别,以及 如何使用 Permit.io 将 ABAC 添加到您的应用程序中
想了解更多关于授权实现的信息吗?还有疑问吗?欢迎在我们的Slack 社区中联系我们 。
鏂囩珷鏉ユ簮锛�https://dev.to/arindam_1729/how-to-add-rbac-authorization-in-nextjs-16m3