数据访问策略
数据库策略是数据墙DBW 提供的行级过滤机制。策略表达式在引擎层被解析并转换为 SQL WHERE 条件,追加到每次数据库查询中。用户只能看到策略允许的数据行。
工作原理
请求 → 引擎确定角色 → 查找策略表达式
→ 解析 @claims 引用(从 JWT 令牌中提取实际值)
→ 生成 WHERE 条件
→ 追加到 SQL 查询 → 数据库执行 → 返回过滤后的结果数据库策略在数据墙DBW 引擎层工作。引擎不依赖数据库原生的行级安全功能——策略被转换为标准 SQL 查询谓词,因此对所有支持的数据库(SQL Server、PostgreSQL、MySQL)都有效。
策略语法
策略使用 OData 风格的表达式,支持字段引用、声明引用和逻辑组合:
| 语法元素 | 格式 | 示例 |
|---|---|---|
| 字段引用 | @item.<field> | @item.status |
| 声明引用 | @claims.<claimType> | @claims.userId |
| 相等 | eq | @item.ownerId eq @claims.userId |
| 不等 | ne | @item.status ne 'deleted' |
| 大于 | gt | @item.price gt 100 |
| 大于等于 | ge | @item.year ge 2000 |
| 小于 | lt | @item.inventory lt 10 |
| 小于等于 | le | @item.price le 50 |
| 逻辑与 | and | @item.active eq true and @item.status eq 'published' |
| 逻辑或 | or | @item.role eq 'admin' or @item.role eq 'editor' |
IMPORTANT
如果在策略中引用了某个 JWT 声明,但当前请求的令牌中不存在该声明,引擎会直接拒绝请求并返回 403 Forbidden。不是返回空结果——是直接拒绝。
策略适用的操作
| 操作 | 是否支持 | 原因 |
|---|---|---|
read | ✓ | SELECT 语句可追加 WHERE 条件 |
update | ✓ | UPDATE 语句可追加 WHERE 条件 |
delete | ✓ | DELETE 语句可追加 WHERE 条件 |
create | ✗ | INSERT 语句不支持 WHERE 条件 |
execute | ✗ | 存储过程不接受外部查询谓词 |
配置语法
策略按角色和操作定义在实体权限中:
{
"entities": {
"<entity-name>": {
"permissions": [
{
"role": "<role-name>",
"actions": [
{
"action": "read",
"policy": {
"database": "<predicate-expression>"
}
}
]
}
]
}
}
}配置示例
用户只能访问自己的数据
{
"Order": {
"permissions": [
{
"role": "customer",
"actions": [
{
"action": "read",
"policy": {
"database": "@item.customerId eq @claims.userId"
}
}
]
}
]
}
}当用户 user123 查询订单时,生成的 SQL 包含:
SELECT * FROM Orders WHERE customerId = 'user123'只返回已发布的内容
固定值过滤,不依赖用户声明:
{
"Article": {
"permissions": [
{
"role": "anonymous",
"actions": [
{
"action": "read",
"policy": {
"database": "@item.status eq 'published'"
}
}
]
}
]
}
}匿名用户只能看到已发布的文章,草稿自动被过滤。
同一策略应用于多个操作
{
"Document": {
"permissions": [
{
"role": "author",
"actions": [
{
"action": "read",
"policy": {
"database": "@item.authorId eq @claims.sub"
}
},
{
"action": "update",
"policy": {
"database": "@item.authorId eq @claims.sub"
}
},
{
"action": "delete",
"policy": {
"database": "@item.authorId eq @claims.sub"
}
}
]
}
]
}
}作者只能读取、更新和删除自己的文档。
复合条件
{
"report": {
"permissions": [
{
"role": "manager",
"actions": [
{
"action": "read",
"policy": {
"database": "@item.departmentId eq @claims.departmentId and @item.status ne 'archived'"
}
}
]
}
]
}
}部门经理只能看到本部门且非归档状态的报告。
不同角色不同策略
{
"Order": {
"permissions": [
{
"role": "customer",
"actions": [
{
"action": "read",
"policy": {
"database": "@item.customerId eq @claims.userId"
}
}
]
},
{
"role": "admin",
"actions": [
{
"action": "read",
"policy": {
"database": "@item.region eq @claims.region"
}
},
"create",
"update"
]
}
]
}
}customer 只能看到自己的订单,admin 能看到本区域的全部订单并可以创建和更新。
策略与字段控制的组合
一个动作可以同时配置字段过滤和行级策略:
{
"action": "read",
"fields": {
"include": ["*"],
"exclude": ["salary", "internalNotes"]
},
"policy": {
"database": "@item.departmentId eq @claims.departmentId"
}
}引擎先应用字段过滤(隐藏列),再应用行级策略(过滤行)。两者独立生效,互不干扰。
CLI 配置
dab update Order \
--permissions "customer:read" \
--policy-database "@item.customerId eq @claims.userId"带特殊字符时用引号包裹整个表达式,具体引号规则取决于所用 Shell。
claims 替换的实现
当请求到达时,引擎执行以下替换:
- 从 JWT 令牌中提取所有声明键值对。
- 扫描策略表达式中的
@claims.<type>模式。 - 将每个
@claims.<type>替换为令牌中对应声明的实际值。 - 替换后的纯值表达式被转换为 SQL WHERE 条件。
例如,策略 @item.ownerId eq @claims.userId,令牌中 userId: "user123":
@item.ownerId eq @claims.userId
↓ 替换
@item.ownerId eq 'user123'
↓ 转换
WHERE [ownerId] = 'user123'常见声明类型
| 声明 | 来源 | 示例值 |
|---|---|---|
sub | JWT 标准,用户主体标识 | ffffffff-eeee-dddd... |
userId | 自定义,用户 ID | user_12345 |
email | 用户邮箱 | user@example.com |
name | 用户显示名 | 张三 |
roles | 角色数组 | ["reader", "editor"] |
tenantId | 多租户标识 | tenant_abc |
departmentId | 部门标识 | dept_sales |
声明的可用性取决于你的身份提供程序配置。策略中引用的每个声明都必须在 JWT 中存在。
字段名引用
策略中的 @item.<field> 使用 API 层字段名。如果配置了字段别名(fields[].alias),应使用别名:
{
"fields": [
{ "name": "customer_id", "alias": "customerId" }
]
}策略中引用 @item.customerId,而不是 @item.customer_id。
数据库策略 vs 数据库原生 RLS
| 维度 | 数据墙DBW 数据库策略 | 数据库原生 RLS |
|---|---|---|
| 工作层级 | 数据墙DBW 引擎层 | 数据库引擎层 |
| 配置位置 | dab-config.json | SQL Server 安全策略 |
| 跨数据库支持 | SQL Server、PostgreSQL、MySQL | 各数据库自行实现 |
| 实现方式 | 追加 WHERE 条件 | 数据库安全策略谓词 |
| 灵活性 | 按角色和操作配置 | 按数据库用户配置 |
数据库策略的优势在于配置简单、跨数据库一致、无需修改数据库对象。数据库原生 RLS 的优势在于安全边界在数据库内部,即使绕过数据墙DBW 直接访问数据库,过滤仍然生效。两者可以配合使用——数据墙DBW 策略提供应用层过滤,数据库原生 RLS 提供最后一道防线。
故障排查
| 问题 | 可能原因 | 检查方法 |
|---|---|---|
| 策略未生效 | 角色不匹配 | 确认请求的最终角色名与策略中 role 一致 |
| 始终 403 | 引用的声明不存在 | 解码 JWT 检查策略中引用的声明是否存在 |
| 语法错误 | 表达式不符合 OData 规范 | 确认使用了 eq 而非 =,使用 and 而非 && |
| 字段名错误 | 使用了数据库列名而非 API 字段名 | 检查 fields.alias 配置,策略中使用别名 |
设置会话上下文
{
"data-source": {
"options": {
"set-session-context": true
}
}
}