3、数据源层权限实现(SQL层面)

数据源层,说白了就是权限管控的第一道防线。很多团队喜欢在应用层做过滤,但我个人习惯——能下推到数据库层,就别往上提。为什么?因为数据库层过滤是物理级的,你连数据都拿不到,后面再怎么绕也绕不过去。

这一章我们聊三种主流实现方式:WHERE子句动态过滤、视图与存储过程、以及数据库原生的行级安全策略(RLS)。每种方式各有适用场景,我按实战经验给你拆开讲。

3.1 基于WHERE子句的动态过滤

这是最直接、最灵活的方式。你在每条查询后面拼一个条件,比如:

SELECT * FROM sales_order
WHERE region = '华东';

但问题是——用户A只能看华东,用户B只能看华南,你不能写死啊。所以得动态拼。

我在项目中常用的做法是:在报表工具或中间层拿到当前用户的权限上下文,然后注入到SQL里。比如在报表数据集里写:

SELECT * FROM sales_order
WHERE region IN (
    SELECT region FROM user_region_permission
    WHERE user_id = :current_user_id
);

嗯,这里要注意:千万别把用户ID直接拼字符串。我曾经见过一个项目,开发图省事用字符串拼接,结果被SQL注入搞崩了权限体系。一定要用参数化查询或绑定变量。

核心原则:动态过滤的WHERE条件必须来自权限表,不能来自前端传参。前端传的「用户ID」你信不过。

这种方式的好处是:

  • 零侵入——不需要改数据库结构
  • 灵活——权限逻辑可以随时调整
  • 兼容性好——所有数据库都支持

缺点也很明显:每条SQL都得写权限条件,容易漏。我见过一个报表,20个数据集里有3个忘了加WHERE,结果数据全透出去了……

3.2 视图(View)与存储过程实现行级安全

如果你不想每条SQL都手写权限条件,视图是个好选择。把权限逻辑封装在视图里,用户只能查视图,不能碰基表。

举个例子:

CREATE VIEW v_sales_order AS
SELECT * FROM sales_order
WHERE region IN (
    SELECT region FROM user_region_permission
    WHERE user_id = SESSION_CONTEXT(N'user_id')
);

这里用了SESSION_CONTEXT——SQL Server里可以在连接时设置会话变量。PostgreSQL里可以用current_setting。用户连上来时,应用层先设置好用户ID,视图自动过滤。

存储过程的方式类似,但更可控:

CREATE PROCEDURE usp_get_sales_order
    @user_id INT
AS
BEGIN
    SELECT * FROM sales_order
    WHERE region IN (
        SELECT region FROM user_region_permission
        WHERE user_id = @user_id
    );
END;

我个人更推荐视图方式。为什么?因为存储过程需要显式调用,而视图对报表工具来说就是一张「表」,拖拽字段时完全无感。我在做Power BI项目时,直接把视图暴露给数据集,权限逻辑完全透明。

避坑指南:视图里如果用了SESSION_CONTEXT,一定要在应用层设置默认值。我曾经遇到一个情况——DBA直接连数据库查视图,没设会话变量,结果视图返回空数据,DBA以为数据丢了……后来我加了个默认值,没设变量时返回空行而不是报错。

3.3 数据库行级安全策略(RLS)介绍

RLS是数据库原生的行级权限方案。PostgreSQL和SQL Server都支持,但实现方式略有不同。

PostgreSQL RLS

PostgreSQL的RLS非常优雅。先开启行级安全,再定义策略:

-- 开启RLS
ALTER TABLE sales_order ENABLE ROW LEVEL SECURITY;

-- 创建策略
CREATE POLICY sales_order_policy ON sales_order
FOR ALL
USING (region IN (
    SELECT region FROM user_region_permission
    WHERE user_id = current_setting('app.user_id')::INT
));

开启之后,所有对该表的查询都会自动应用策略。用户根本感知不到权限的存在——查到的数据就是他能看的。

我记得有一次帮客户做数据平台,他们之前用视图方案,但视图太多维护成本高。换成RLS后,基表直接暴露给报表工具,权限由数据库自动管控,运维量骤降。

SQL Server RLS

SQL Server的实现叫「行级安全性」,通过内联表值函数实现:

-- 创建过滤函数
CREATE FUNCTION fn_security_predicate(@region NVARCHAR(50))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS result
WHERE @region IN (
    SELECT region FROM user_region_permission
    WHERE user_id = CAST(SESSION_CONTEXT(N'user_id') AS INT)
);

-- 绑定安全策略
CREATE SECURITY POLICY sales_order_filter
ADD FILTER PREDICATE dbo.fn_security_predicate(region)
ON dbo.sales_order
WITH (STATE = ON);

两种数据库的RLS核心思路一致:在数据库引擎层面拦截每一行数据,不符合条件的直接过滤掉。应用层、报表层完全不需要改代码。

重要提醒:RLS不是银弹。它会影响查询性能,因为每一行都要过一遍过滤函数。我在一个千万级表上开RLS,查询从200ms涨到800ms。如果数据量极大,建议结合分区或索引优化。

三种方式对比

方式 维护成本 性能影响 透明性 适用场景
WHERE动态过滤 高(每条SQL都要写) 低 低(需应用层配合) 小团队、快速迭代
视图/存储过程 中(需维护视图层) 中 中(对报表工具透明) 中大型项目、多报表源
数据库RLS 低(一次配置) 高(每行过滤) 高(完全透明) 数据量大、权限规则复杂

你想想看,如果团队里报表开发人员多、SQL水平参差不齐,RLS或视图能帮你兜底。如果团队精干、权限规则简单,WHERE动态过滤反而更轻量。

本章知识体系

下面这张图帮你理清三种实现方式的关系和选择路径:

数据源层行级权限实现路径 行级权限实现 WHERE动态过滤 视图 / 存储过程 数据库RLS 特性 • 灵活,零侵入 • 每条SQL都要写条件 • 容易遗漏 特性 • 封装权限逻辑 • 对报表工具透明 • 需维护视图层 特性 • 原生行级管控 • 完全透明 • 性能开销较大 推荐:小团队快速迭代 推荐:中大型多报表项目 推荐:复杂权限、高安全

三种方式没有的好坏。我个人的选择逻辑是:先看团队规模,再看数据量,最后看安全等级。小团队用WHERE动态过滤起步,等维护成本高了再切视图或RLS。别一开始就上RLS——性能问题会让你头疼。

我的建议:如果数据库支持RLS(PostgreSQL 9.5+、SQL Server 2016+),优先考虑。虽然性能有损耗,但权限逻辑集中管控,后期审计和排查问题会省很多事。我曾经在一个金融项目里用RLS,审计问「某个用户能看到哪些数据」——直接查策略定义就行,不用翻代码。