近期,收到许多用户关于如何配置和管理 MemFire Cloud 中的用户角色和权限的询问。为了解答大家的疑问,我们编写了一个简易教程,指导你完成如何在 MemFire Cloud 中轻松设置用户角色和权限,以及配置前端路由和按钮权限的各个步骤。本教程以“商品后台管理系统”中设置的不同角色(超级管理员:可以对商品管理系统的所有数据进行任何操作;普通管理员:可以查看和新增商品;普通用户:只能查看商品)和通过权限来管理商品为例进行讲解。
准备工作:创建Auth Hook
此Auth Hook介绍了一种为 MemFire Cloud 项目实现自定义声明的方法。该方法旨在将JSON 数据添加到经过身份验证的用户在登录应用程序时收到的访问令牌中。此令牌(及其包含的自定义声明)可供你的应用程序和 PostgreSQL 数据库服务器读取和使用,方便在后续给用户设置和读取用户的角色声明。这些自定义声明存储在 auth 架构中 users 表的 raw_app_meta_data 字段中。
文件 install.sql 包含在 MemFire Cloud项目中实现和管理自定义声明所需的所有 Auth Hook。
1.将 install.sql 中的 SQL 代码粘贴到 MemFire Cloud 项目的 SQL 查询编辑器中。
2.单击 RUN 来执行代码。
文件:https://github.com/supabase-community/supabase-custom-claims
创建表
"商品后台管理系统"需要创建权限表(role_permissions)、菜单表(sys_menu)、用户表(users)、和商品表(prod) ,用这四张表来完成对于用户角色和权限的配置及管理。
创建权限表
首先需要创建一个权限表来跟踪角色的权限。我们设置了三种角色,分别为超级管理员:superAdmin、普通管理员:admin、普通用户:authenticated。
将 SQL 代码粘贴到 MemFire Cloud 项目的 SQL 查询编辑器中,点击RUN来执行代码。
-- 角色权限
create table public.role_permissions (
id bigint generated by default as identity primary key,
role character varying(255) not null,
permission character varying(255) not null
);
comment on table public.role_permissions is '权限表';
insert into public.role_permissions (role, permission)
values
('superAdmin', 'all'),
('admin', 'prod:spec:info,prod:spec:save'),
('authenticated', 'prod:spec:info');
permission是代表能够访问和控制哪些页面,prod:spec:info表示可以查看商品管理列表的详情,prod:spec:save表示可以新增商品,prod:spec:update表示可以修改商品,prod:spec:delete表示可以删除商品。
创建菜单表
创建"商品后台管理系统"的菜单表,方便后续管理菜单。
将 SQL 代码粘贴到 MemFire Cloud 项目的 SQL 查询编辑器中,点击RUN来执行代码。
create table public.sys_menu (
menu_id bigint generated by default as identity primary key,
parent_id bigint null,
name character varying(50) null default null::character varying,
perms character varying(500) null default null::character varying
) tablespace pg_default;
insert into public.sys_menu (parent_id,type,menu_id,name,perms)
values
(1,1,34,'商品管理',null),
(34,2,57,'查看','prod:spec:info'),
(34,2,58,'添加','prod:spec:save'),
(34,2,59,'修改','prod:spec:update,prod:spec:info'),
(34,2,60,'删除','prod:spec:delete');
perms 是此菜单的权限标识符,允许在后续前端页面上根据标识匹配轻松控制显示和隐藏。
创建用户表
方便后续访问用户表,同步auth.users表里的数据,例如uid、角色声明等。
将 SQL 代码粘贴到 MemFire Cloud 项目的 SQL 查询编辑器中,点击RUN来执行代码。
create table
public.users (
instance_id uuid null,
id uuid not null,
aud character varying(255) null,
email character varying(255) null,
phone character varying(255) null,
user_name character varying(255) null,
raw_app_meta_data jsonb null
) tablespace pg_default;
创建数据库函数和触发器
当auth.users表创建一个用户时,也同样在public.users表插入一条用户
raw_app_meta_data 是用户的身份声明,例如其角色。我们主要需要更新 raw_app_meta_data,因为当我们修改 auth.users 中的角色时,实际上就是修改了 raw_app_meta_data 中的 role声明。因此,我们需要同步到 public.users 表中。
将 SQL 代码粘贴到 MemFire Cloud 项目的 SQL 查询编辑器中,点击RUN来执行代码。
CREATE OR REPLACE FUNCTION sync_users()
RETURNS TRIGGER AS $$
BEGIN
-- 在 public.users 表中插入新用户数据
INSERT INTO public.users (id,aud,raw_app_meta_data,email)
VALUES (NEW.id,NEW.aud ,NEW.raw_app_meta_data, NEW.email);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 创建触发器,当在 auth.users 表中插入新数据时触发 sync_users() 函数
CREATE TRIGGER sync_users_trigger
AFTER INSERT ON auth.users
FOR EACH ROW
EXECUTE FUNCTION sync_users();
如果auth.users字段进行了修改,也同样在public.users表里面相同的字段
将 SQL 代码粘贴到 MemFire Cloud 项目的 SQL 查询编辑器中,点击RUN来执行代码。
CREATE OR REPLACE FUNCTION sync_user_update()
RETURNS TRIGGER AS $$
BEGIN
-- 在 public.users 表中更新相应字段
UPDATE public.users
SET
role= NEW.raw_app_meta_data,
email = NEW.email
WHERE id = NEW.id; -- 根据用户 id 进行更新
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 创建触发器,当在 auth.users 表中更新数据时触发 sync_user_update() 函数
CREATE TRIGGER sync_user_update_trigger
AFTER UPDATE ON auth.users
FOR EACH ROW
EXECUTE FUNCTION sync_user_update();
安全考虑
如果你想加强安全性,以便只能从查询编辑器内部或 PostgreSQL 函数或触发器内部设置或删除自定义声明,因为默认情况下,是可以允许通过 API 使用,但设置或删除声明的能力仅限于将 claims_admin 自定义声明设置为 true 的用户。这允许你创建应用程序的管理部分,允许指定用户修改应用程序其他用户的自定义声明。请替换Auth Hook函数里的 is_claims_admin() 返回为false以禁止应用程序用户使用(不能通过 API / Postgrest 使用) 。指令如下:
将 SQL 代码粘贴到 MemFire Cloud 项目的 SQL 查询编辑器中,点击RUN来执行代码。
CREATE OR REPLACE FUNCTION is_claims_admin() RETURNS "bool"
LANGUAGE "plpgsql"
AS $$
BEGIN
IF session_user = 'authenticator' THEN
--------------------------------------------
-- To disallow any authenticated app users
-- from editing claims, delete the following
-- block of code and replace it with:
-- RETURN FALSE;
--------------------------------------------
IF extract(epoch from now()) > coalesce((current_setting('request.jwt.claims', true)::jsonb)->>'exp', '0')::numeric THEN
return false; -- jwt expired
END IF;
If current_setting('request.jwt.claims', true)::jsonb->>'role' = 'service_role' THEN
RETURN true; -- service role users have admin rights
END IF;
IF coalesce((current_setting('request.jwt.claims', true)::jsonb)->'app_metadata'->'claims_admin', 'true')::bool THEN
return true; -- user has claims_admin set to true
ELSE
return false; -- user does NOT have claims_admin set to true
END IF;
--------------------------------------------
-- End of block
--------------------------------------------
ELSE -- not a user session, probably being called from a trigger or something
return false;
END IF;
END;
$$;
给用户设置角色声明
首先在查询编辑器内部给一个认证过的用户设置claims_admin 为true 方便他能够管理角色声明
将 SQL 代码粘贴到 MemFire Cloud 项目的 SQL 查询编辑器中,点击RUN来执行代码。
select set_claim('03acaa13-7989-45c1-8dfb-6eeb7cf0b92e', 'claims_admin', 'true');
其中 03acaa13-7989-45c1-8dfb-6eeb7cf0b92e 是在 auth.users中找到的管理用户的 ID。
例如:我想给我auth.users表里的一个用户(uuid为:f695a1a5-8725-4c66-8399-57d842d2b1d1)设置角色为超级管理员
上面步骤提到我们在public.users表内同步了auth.users的用户数据,出于安全起见,设置对public.users的角色访问权限后(例如role为superAdmin才能操作public.users),那我们可以直接获取用户的uid。
这个api操作只有claims_admin 为true 的用户才有权限
const { data, error } = await supabase.rpc('set_claim', {uid:'f695a1a5-8725-4c66-8399-57d842d2b1d1',claim:'role', value:'superAdmin'});
我们可以在auth.users 和public.users的raw_app_meta_data看到同步结果
创建商品表
将 SQL 代码粘贴到 MemFire Cloud 项目的 SQL 查询编辑器中,点击RUN来执行代码。
create table
public.prod (
id bigint generated by default as identity primary key,
prod_name character varying(300) not null default ''::character varying,
price numeric(15, 2) null default null::numeric,
content text null,
pic character varying(255) null default null::character varying,
sold_num integer null,
create_time timestamp without time zone null,
update_time timestamp without time zone null
) tablespace pg_default;
insert into public.prod (prod_name , price ,content ,pic ,sold_num )
values
('石材', 1234,'很硬',null,23),
('地板', 354,'很滑',null,33),
('玻璃', 45,'很亮',null,54);```
设置数据访问策略
-
超级管理员:可以对商品管理系统的所有数据进行任何操作
-
普通管理员:可以查看和新增商品
-
普通用户:只能查看商品
根据权限表对数据表数据的访问权限,我们设置如下策略。
将 SQL 代码粘贴到 MemFire Cloud 项目的 SQL 查询编辑器中,点击RUN来执行代码。
CREATE POLICY "超级管理员可以访问商品表的全部操作" ON "public"."prod"
AS PERMISSIVE FOR ALL
TO public
USING ((get_my_claim('role'::text) = '"superAdmin"'::jsonb))
WITH CHECK (true);
CREATE POLICY "管理员可以查看商品表的数据" ON "public"."prod"
AS PERMISSIVE FOR SELECT
TO public
USING ((get_my_claim('role'::text) = '"admin"'::jsonb));
CREATE POLICY "管理员可以新增商品表" ON "public"."prod"
AS PERMISSIVE FOR INSERT
TO public
WITH CHECK ((get_my_claim('role'::text) = '"admin"'::jsonb));
CREATE POLICY "普通用户可以查看商品表的数据" ON "public"."prod"
AS PERMISSIVE FOR SELECT
TO public
USING ((get_my_claim('role'::text) = '"authenticated"'::jsonb));
这样我们就分别对角色进行了数据的访问和操作的权限设置
get_my_claim 是用来检查当前登录用户的特定声明。
结果如下:
我们就可以根据结果中的role来进行页面路由权限的管理和控制了。例如控制按钮的显示与隐藏:
<el-button v-if="isAuth('prod:spec:update')"
type="primary"
icon="el-icon-edit"
@click.stop="onAddOrUpdate(scope.row.roleId)"
> 编辑 </el-button>
/**
* 是否有权限
* @param {*} key
*/
export function isAuth(key) {
//从session用户登录时获得的对象中提取角色声明信息
supabase.auth.onAuthStateChange(async (_event, session) => {
if (session?.user?.app_metadata?.role) {
//查询权限表对应角色对应的权限
let { data: role_permissions, error } = await supabase
.from('role_permissions')
.select("*")
.eq('role', session?.user?.app_metadata?.role)
if (role_permissions) {
if (role_permissions.permission.length) {
if(role_permissions.permission === 'all'){
return true
}else{
for (const i in role_permissions.permission) {
const element = role_permissions.permission[i]
if (element === key) {
return true
}
}
}
}
return false
}
}
})
}```
小结
通过创建 Auth Hook,我们实现了在前端通过角色声明管理和控制路由菜单和按钮,并配置了不同角色的数据访问权限。这种方法使你能够轻松地在MemFire Cloud中管理用户角色和权限,并构建一个强大的系统。