一、VPD概述
在某些系统中,权限控制必须定义到数据行访问权限的控制,此需求一般出现在同一系统中,不同的相对独立机构使用的情况。
比如,集团下属多个子公司,所有子公司使用同一套数据表,但不同子公司的数据相对隔离,也就是每个子公司的人员只能查看、操作本公司的数据。面对这种情况,绝大多数人会选择在表或视图加上WHERE子句来进行数据隔离。但此方法的缺点是编码工作量大、系统适应用户管理体系的弹性空间较小,一旦权限逻辑发生变动,就可能需要修改权限体系,导致所有的表或视图都必须修改。
为了解决此类问题,可以使用达梦数据库的虚拟专用数据库(VPD)技术,该技术提供了角色和视图无法提供的行级访问控制。
对于互联网访问,虚拟专用数据库可以确保在线银行的客户只能看到他们自己的帐户。Web托管公司可以在同一达梦数据库中维护多个公司的数据,但只允许每个公司查看其自身数据。
在企业内部,虚拟数据库可在应用程序部署方面降低拥有成本。可以在数据库服务器一次实现安全性,而不用在访问数据的每个应用程序中分别实现安全性。因为是在数据库中实施安全性,所以不管用户访问数据的方式如何,安全性较以前更高。
二、VPD工作原理
虚拟专用数据库是通过安全策略来实现行级访问控制的,具体来说,就是先将一个或多个安全策略与表或视图关联,对带安全策略的表进行直接或间接访问时,数据库将调用一个实施该策略的函数。策略函数返回一个访问条件(WHERE子句),即谓词。应用程序将它附加到用户的 SQL 语句,从而动态修改用户的数据访问权限。
也就是说,当你使用SQL语句访问带安全策略的表或视图时,VPD技术自动在该SQL语句上附加了SQL谓词(WHERE子句),例如,当你输入SELECT * FROM Person语句时,可以使用VPD添加WHERE DEPT = 10子句,这样查询出来的结果永远都是受到限制的。
三、达梦VPD示例
达梦数据库管理系统提供了 DBMS_RLS 包, DBMS_RLS 包通过策略(POLICY)管理方法来实现数据行的隔离。
下面我以一个实例来说明虚拟专用数据VPD的使用。
现在有一个订单表Orders,里面记录了销售部所有业务员的订单信息,要求每个销售员查询该表时,只能看到自己的订单,只有系统管理员能看到所有人的订单。
实现步骤如下:
--以系统管理员SYSDBA登录
CONN SYSDBA/SYSDBA
--创建DBMS_RLS包
SP_CREATE_SYSTEM_PACKAGES(1);
--创建测试用户(即业务员)
CREATE USER USER1 IDENTIFIED BY 123456789;
CREATE USER USER2 IDENTIFIED BY 123456789;
--授权
GRANT DBA TO USER1;
GRANT DBA TO USER2;
--创建测试表
DROP TABLE IF EXISTS SYSDBA.Orders;
CREATE TABLE SYSDBA.Orders(orderid int, salesman varchar(20), num int, amount int);
--插入测试数据
INSERT INTO SYSDBA.Orders VALUES(1,'USER1',10,100);
INSERT INTO SYSDBA.Orders VALUES(2,'USER1',20,200);
INSERT INTO SYSDBA.Orders VALUES(3,'USER2',30,300);
INSERT INTO SYSDBA.Orders VALUES(4,'USER2',40,400);
INSERT INTO SYSDBA.Orders VALUES(5,'USER2',50,500);
COMMIT;
此时尚未创建策略,分别使用系统管理员SYSDBA和普通用户USER1查询Orders表
--SYSDBA
CONN SYSDBA/SYSDBA
SELECT COUNT(*) FROM SYSDBA.Orders;
–USER1
CONN USER1/123456789
SELECT COUNT(*) FROM SYSDBA.Orders;
可以看到,此时,不同用户查询出的结果是一样的。
下面开始创建策略函数,然后创建策略组,并将策略应用到Orders表上
--创建策略函数,根据当前用户的不同,在SQL语句后面添加不同的WHERE子句
CREATE OR REPLACE FUNCTION FN_Orders_Condition(p_schema VARCHAR2,p_object VARCHAR2)
RETURN VARCHAR2 IS
v_user VARCHAR2(100) := CURRENT_USER();
v_where_condition VARCHAR2(2000);
BEGIN
IF v_user = 'SYSDBA' THEN
v_where_condition := NULL;
ELSE
v_where_condition := 'SALESMAN = ''' || v_user || '''';
END IF;
RETURN v_where_condition;
END;
--创建策略组
DBMS_RLS.CREATE_POLICY_GROUP(
OBJECT_SCHEMA=>'SYSDBA', --模式名
OBJECT_NAME=>'ORDERS', --表名
POLICY_GROUP=>'PG_ORDERS' --策略组名
);
--将策略应用到表Orders
DBMS_RLS.ADD_GROUPED_POLICY(
OBJECT_SCHEMA=>'SYSDBA', --表模式名
OBJECT_NAME=>'ORDERS', --表名
POLICY_GROUP=>'PG_ORDERS', --策略组名
POLICY_NAME=>'ORDERS_POLICY', --策略名(任意取,管理策略时使用)
FUNCTION_SCHEMA=>'SYSDBA', --策略函数的模式名
POLICY_FUNCTION=>'FN_Orders_Condition', --策略函数名
STATEMENT_TYPES=>'select,update,delete,insert', --应用策略的DML语句类型
UPDATE_CHECK=>true,
ENABLE=>true --是否启用策略
);
策略已启用,此时再用SYSDBA和USER1分别访问Orders表
--SYSDBA
CONN SYSDBA/SYSDBA
SELECT COUNT(*) FROM SYSDBA.Orders;
--USER1
CONN USER1/123456789
SELECT COUNT(*) FROM SYSDBA.Orders;
可以看到,此时SYSDBA和USER1查询的结果已经不一样了,SYSDBA仍然能查询所有的五行数据,而USER1只能查询自己的两行数据了。
下面利用USER1分别尝试一下SELECT, UPDATE,DELETE和INSERT的使用效果:
--USER1
CONN USER1/123456789
SELECT * FROM SYSDBA.Orders;
UPDATE SYSDBA.Orders SET SALESMAN = 'USER2' WHERE orderid = 1;
DELETE FROM SYSDBA.Orders WHERE SALESMAN = 'USER2'
INSERT INTO SYSDBA.Orders VALUES(6,'USER2',60,600);
执行结果如下:
可以看到,USER1只能查询到自己的两行数据,试图UPDATE\DELETE\INSERT别的用户数据均不能成功。