Files
medical-mall/docs/sql/11_roles_and_permissions_strategy.md

185 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 11 角色、权限与路由整合策略
本节提供一套完整的“角色定义 → RLS 策略 → 前端路由/跳转 → 业务流程”整合方案,旨在将数据库安全模型与项目实际开发无缝结合。
---
## 1. 角色定义(权威口径)
为避免权限判断分裂,项目应统一使用 `public.ak_users.role` 作为唯一权威的角色字段。
### 1.1 推荐的角色枚举
- `customer`:消费者
- `merchant`:商家
- `delivery`:配送员
- `service`:客服
- `admin`:平台管理员
- `analytics`:数据分析/运营角色
> **决策点**
> - `analytics` 角色是可选的。如果运营/分析师与 `admin` 权限边界不清,可以先统一为 `admin`。
> - 但长远看,为“数据查看者”设定独立角色有利于最小权限原则。
### 1.2 如何在数据库中获取当前用户角色
通常通过一个函数实现,该函数内部使用 `auth.uid()`
```sql
CREATE OR REPLACE FUNCTION public.get_current_user_role()
RETURNS TEXT
LANGUAGE sql
SECURITY DEFINER
AS $$
SELECT role FROM public.ak_users WHERE auth_id = auth.uid() LIMIT 1;
$$;
```
---
## 2. RLS 策略与权限设计
### 2.1 权限分层(推荐)
- **A. Row Owner行归属者**
- 用户只能访问自己的数据,如地址、购物车、收藏、个人订单。
- RLS 策略核心:`auth.uid() = (SELECT auth_id FROM ak_users WHERE id = <row.user_id>)`
- **B. Business Owner业务归属者**
- 商家只能访问自己店铺的数据,如商品、店铺订单。
- RLS 策略核心:`auth.uid() = (SELECT auth_id FROM ak_users WHERE id = <row.merchant_id>)`
- **C. Privileged特权角色**
- `admin/analytics` 角色需要访问全局数据,尤其是聚合统计。
- **强烈建议**:不要为这些角色直接开放表的全局 `SELECT` 权限。
### 2.2 如何让 `admin/analytics` 安全地看全局数据?
**推荐方案RPC + `SECURITY DEFINER`**
1. **维持表的严格 RLS**:确保 `customer/merchant` 无法越权。
2. **Analytics 页面只调用 RPC**:例如 `rpc_analytics_*` 系列函数。
3. **RPC 函数必须 `SECURITY DEFINER`**:使其以“函数所有者”(通常是 `postgres` 超级用户)的权限执行,从而绕过调用者的 RLS 限制。
4. **RPC 函数内部必须做显式鉴权**:这是安全闭环的关键。
**RPC 鉴权模板**
```sql
CREATE OR REPLACE FUNCTION public.rpc_analytics_sales_kpis(
p_start_date DATE,
p_end_date DATE
)
RETURNS TABLE (...)
LANGUAGE plpgsql
SECURITY DEFINER -- 以函数所有者权限执行
SET search_path = public -- 显式设置 search_path避免 search_path 攻击
AS $$
BEGIN
-- 1. 在函数入口处做权限检查
IF get_current_user_role() NOT IN ('admin', 'analytics') THEN
RAISE EXCEPTION 'Permission denied: required role admin or analytics';
END IF;
-- 2. 执行统计(因为是 SECURITY DEFINER这里可以查到所有数据
RETURN QUERY
WITH ...
-- ... 统计逻辑 ...
END;
$$;
```
> **现状风险**:当前 `rpc_analytics_*` 脚本未包含 `SECURITY DEFINER` 与内部鉴权。若直接部署,当 RLS 开启时,`admin/analytics` 调用会因权限不足而查不到数据。
---
## 3. 前端项目整合:路由守卫与业务流程
### 3.1 路由分组(按角色)
项目页面按角色划分,便于集中管理路由与权限。
- `/pages/mall/consumer/**`
- `/pages/mall/merchant/**`
- `/pages/mall/delivery/**`
- `/pages/mall/admin/**`
- `/pages/mall/analytics/**`
### 3.2 路由守卫(客户端鉴权)
`services/analytics/authGuard.uts`(或类似文件)中,应提供更精细的守卫函数。
**守卫函数建议**
```typescript
// services/auth/guard.uts (示例)
import { getCurrentUser } from './user.uts' // 假设此函数能获取当前登录用户及其角色
// 1. 确保已登录
export function ensureLoggedIn(options: { redirect?: string } = {}): boolean {
const user = getCurrentUser();
if (!user) {
uni.navigateTo({ url: options.redirect ?? '/pages/user/login' });
return false;
}
return true;
}
// 2. 确保具备指定角色之一
export function ensureRole(allowedRoles: Array<string>, options: { toastTitle?: string } = {}): boolean {
if (!ensureLoggedIn()) return false;
const user = getCurrentUser();
if (!user || !allowedRoles.includes(user.role)) {
uni.showToast({ title: options.toastTitle ?? '无权访问', icon: 'none' });
// 可选择返回上一页或跳转首页
setTimeout(() => uni.navigateBack(), 1500);
return false;
}
return true;
}
```
**在 Analytics 页面中使用**
```typescript
// pages/mall/analytics/index.uvue
onLoad(() => {
if (!ensureRole(['admin', 'analytics'], { toastTitle: '仅管理员可访问数据分析' })) {
return;
}
initDashboard();
});
```
### 3.3 业务流程闭环(以 Analytics 首页为例)
1. **用户访问** `/pages/mall/analytics/index`
2. **前端守卫**`onLoad``ensureRole(['admin', 'analytics'])` 执行:
- 未登录 → 跳转登录页
- 已登录但角色不符 → toast 提示 + 返回
3. **调用 Service**`dashboardService.uts``fetch...` 函数被调用。
4. **执行 RPC**`rpcOrNull('rpc_analytics_sales_kpis', ...)` 发起请求。
5. **数据库鉴权**`rpc_analytics_sales_kpis` 函数内部首先检查 `get_current_user_role()` 是否为 `admin/analytics`
- 权限不足 → `RAISE EXCEPTION`,前端收到错误。
- 权限通过 → 执行统计。
6. **数据返回**:前端拿到聚合数据并渲染。
这个流程实现了“前端快速失败 + 后端强制校验”的安全闭环。
---
## 4. 权限矩阵(总结)
| 角色 | `customer` | `merchant` | `admin/analytics` |
| -------- | -------------------------------------------- | ---------------------------- | -------------------------------------- |
| **可读** | 上架商品、自己的(订单/地址/购物车/收藏/券) | 自己的(商品/订单/店铺数据) | 全局聚合数据(通过 RPC |
| **可写** | 自己的(地址/购物车/收藏/订单创建) | 自己的(商品/发货/售后) | 通常不直接写业务表(通过后台管理功能) |
---
## 5. 待办与实现建议
1. **统一角色字段**:在项目中明确 `ak_users.role` 为唯一权威,并提供获取当前用户角色的函数。
2. **增强 RPC 安全性**:为所有 `rpc_analytics_*` 函数增加 `SECURITY DEFINER` 与内部权限检查。
3. **实现前端路由守卫**:创建 `ensureRole` 函数,并在所有 `analytics` 子包页面中统一调用。