虛擬專用數(shù)據(jù)庫 (VPD) 提供了角色和視圖無法提供的行級訪問控制。對于互聯(lián)網(wǎng)訪問,虛擬專用數(shù)據(jù)庫可以確保在線銀行的客戶只能看到他們自己的帳戶。Web 托管公司可以在同一 Oracle 數(shù)據(jù)庫中維護多個公司的數(shù)據(jù),但只允許每個公司查看其自身數(shù)據(jù)。
在企業(yè)內部,虛擬數(shù)據(jù)庫可在應用程序部署方面降低擁有成本。可以在數(shù)據(jù)庫服務器一次實現(xiàn)安全性,而不用在訪問數(shù)據(jù)的每個應用程序中分別實現(xiàn)安全性。因為是在數(shù)據(jù)庫中實施安全性,所以不管用戶訪問數(shù)據(jù)的方式如何,安全性較以前更高。訪問即席查詢工具或新報表生成程序的用戶不再能繞過安全環(huán)節(jié)。虛擬專用數(shù)據(jù)庫是一項重要技術,使企業(yè)能夠構建托管的、基于 Web 的應用程序。實際上,許多 Oracle 應用程序本身使用 VPD 實施數(shù)據(jù)分隔,包括 Oracle SalesOnline.com 和 Oracle Portal 等程序。
虛擬專用數(shù)據(jù)庫如何工作
將一個或多個安全策略與表或視圖關聯(lián)后,就可以實現(xiàn)虛擬專用數(shù)據(jù)庫。對帶安全策略的表進行直接或間接訪問時,數(shù)據(jù)庫將調用一個實施該策略的函數(shù)。策略函數(shù)返回一個訪問條件(WHERE 子句),即謂詞。應用程序將它附加到用戶的 SQL 語句,從而動態(tài)修改用戶的數(shù)據(jù)訪問權限。
你可以通過編寫一個存儲過程將 SQL 謂詞附加到每個 SQL 語句(用于控制該語句的行級別訪問權限)來實施 VPD。例如,如果 John Doe(他屬于 Department 10)輸入 SELECT * FROM emp 語句,則可以使用 VPD 添加 WHERE DEPT = 10 子句。這樣,您便可以通過對查詢進行修改來限制訪問某些行的數(shù)據(jù)。
虛擬專用數(shù)據(jù)庫確保無論用戶以何種方式訪問數(shù)據(jù)(通過應用程序、報表編寫工具或 SQL*Plus),都將強制實施同一強大的訪問權限控制策略。這樣,使用 VPD ,銀行便可以確??蛻糁豢吹剿麄冏约旱膸簦娦殴究梢园踩馗綦x客戶記錄,人力資源應用程序可以支持復雜的員工記錄數(shù)據(jù)訪問原則。
案例說明
1. 搭建環(huán)境--創(chuàng)建模式擁有者和相應的用戶,賦予權限
- CONNECT sys/password@service AS SYSDBA;
- CREATE USER schemaowner IDENTIFIED BY schemaowner
- DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp;
- GRANT connect, resource TO schemaowner;
-
- CREATE USER user1 IDENTIFIED BY user1
- DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp;
- GRANT connect, resource TO user1;
-
- CREATE USER user2 IDENTIFIED BY user2
- DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp;
- GRANT connect, resource TO user2;
-
- GRANT EXECUTE ON DBMS_RLS TO PUBLIC;
-
- CONN schemaowner/schemaowner@service
-
- CREATE TABLE users
- (id NUMBER(10) NOT NULL,
- ouser VARCHAR2(30) NOT NULL,
- first_name VARCHAR2(50) NOT NULL,
- last_name VARCHAR2(50) NOT NULL);
-
- CREATE TABLE user_data
- (column1 VARCHAR2(50) NOT NULL,
- user_id NUMBER(10) NOT NULL);
-
- INSERT INTO users VALUES (1,'USER1','User','One');
- INSERT INTO users VALUES (2,'USER2','User','Two');
- COMMIT;
-
- GRANT SELECT, INSERT ON user_data TO user1, user2
2. 創(chuàng)建上下文和上下文的包(Context)
- CONNECT sys/password@service AS SYSDBA;
- GRANT create any context, create public synonym TO schemaowner;
-
- CONNECT schemaowner/schemaowner@service;
-
- CREATE CONTEXT SCHEMAOWNER USING SCHEMAOWNER.Context_Package;
-
- CREATE OR REPLACE PACKAGE Context_Package AS
- PROCEDURE Set_Context;
- END;
- /
包的具體代碼:
- CREATE OR REPLACE PACKAGE BODY Context_Package IS
- PROCEDURE Set_Context IS
- v_ouser VARCHAR2(30);
- v_id NUMBER;
- BEGIN
- DBMS_Session.Set_Context('SCHEMAOWNER','SETUP','TRUE');
- v_ouser := SYS_CONTEXT('USERENV','SESSION_USER');
-
- BEGIN
- SELECT id
- INTO v_id
- FROM users
- WHERE ouser = v_ouser;
-
- DBMS_Session.Set_Context('SCHEMAOWNER','USER_ID', v_id);
- EXCEPTION
- WHEN NO_DATA_FOUND THEN
- DBMS_Session.Set_Context('SCHEMAOWNER','USER_ID', 0);
- END;
-
- DBMS_Session.Set_Context('SCHEMAOWNER','SETUP','FALSE');
- END Set_Context;
- END Context_Package;
- /
- SHOW ERRORS
-
- GRANT EXECUTE ON SCHEMAOWNER.Context_Package TO PUBLIC;
- CREATE PUBLIC SYNONYM Context_Package FOR SCHEMAOWNER.Context_Package;
解釋: 獲取當前登陸用戶名,并設置上下文中的變量USER_ID為當前用戶的ID,同時進行相應授權
3. 創(chuàng)建登陸觸發(fā)器
- CONNECT sys/password@service AS SYSDBA;
- CREATE OR REPLACE TRIGGER SCHEMAOWNER.Set_Security_Context
- AFTER LOGON ON DATABASE
- BEGIN
- SCHEMAOWNER.Context_Package.Set_Context;
- END;
- /
- SHOW ERRORS
4. 建立安全策略
- CONNECT schemaowner/schemaowner@service;
-
- CREATE OR REPLACE PACKAGE Security_Package AS
- FUNCTION User_Data_Insert_Security(Owner VARCHAR2, Objname VARCHAR2)
- RETURN VARCHAR2;
-
- FUNCTION User_Data_Select_Security(Owner VARCHAR2, Objname VARCHAR2)
- RETURN VARCHAR2;
- END Security_Package;
- /
-
-
- CREATE OR REPLACE PACKAGE BODY Security_Package IS
- FUNCTION User_Data_Select_Security(Owner VARCHAR2, Objname VARCHAR2)RETURN VARCHAR2 IS
- Predicate VARCHAR2(2000);
- BEGIN
- Predicate := '1=2';
- IF (SYS_CONTEXT('USERENV','SESSION_USER') = 'SCHEMAOWNER') THEN
- Predicate := NULL;
- ELSE
- Predicate := 'USER_ID = SYS_CONTEXT(''SCHEMAOWNER'',''USER_ID'')';
- END IF;
- RETURN Predicate;
- END User_Data_Select_Security;
-
- FUNCTION User_Data_Insert_Security(Owner VARCHAR2, Objname VARCHAR2) RETURN VARCHAR2 IS
- Predicate VARCHAR2(2000);
- BEGIN
- Predicate := '1=2';
- IF (SYS_CONTEXT('USERENV','SESSION_USER') = 'SCHEMAOWNER') THEN
- Predicate := NULL;
- ELSE
- Predicate := 'USER_ID = SYS_CONTEXT(''SCHEMAOWNER'',''USER_ID'')';
- END IF;
- RETURN Predicate;
- END User_Data_Insert_Security;
- END Security_Package;
- /
- SHOW ERRORS
-
-
- GRANT EXECUTE ON SCHEMAOWNER.Security_Package TO PUBLIC;
- CREATE PUBLIC SYNONYM Security_Package FOR SCHEMAOWNER.Security_Package;
5. 應用策略到相應的表,使用RDBMS_RLS
- BEGIN
- DBMS_Rls.Add_Policy('SCHEMAOWNER', 'USER_DATA', 'USER_DATA_INSERT_POLICY',
- 'SCHEMAOWNER', 'SECURITY_PACKAGE.USER_DATA_INSERT_SECURITY',
- 'INSERT', TRUE);
- DBMS_Rls.Add_Policy('SCHEMAOWNER', 'USER_DATA', 'USER_DATA_SELECT_POLICY',
- 'SCHEMAOWNER', 'SECURITY_PACKAGE.USER_DATA_SELECT_SECURITY',
- 'SELECT');
- END;
- /
5. VPD測試
- CONNECT user1/user1@service;
- INSERT INTO schemaowner.user_data (column1, user_id) VALUES ('User 1', 1);
- INSERT INTO schemaowner.user_data (column1, user_id) VALUES ('User 2', 2);
- COMMIT;
-
- CONNECT user2/user2@service
- INSERT INTO schemaowner.user_data (column1, user_id) VALUES ('User 1', 1);
- INSERT INTO schemaowner.user_data (column1, user_id) VALUES ('User 2', 2);
- COMMIT;
-
- CONNECT schemaowner/schemaowner@service
- SELECT * FROM schemaowner.user_data;
- CONNECT user1/user1@Service;
- SELECT * FROM schemaowner.user_data;
- CONNECT user2/user2@Service
- SELECT * FROM schemaowner.user_data;
測試結果:
(1) user1用戶登陸的: 只用第一條insert成功
(2) user2用戶登陸的: 只用第二條insert成功
(3) 關于查詢:
user1和user2都只能看到自己的資料,而schemaowner可以看到所有的資料。這里重點是所添加的謂詞不同,在安全策略中的
Predicate := 'USER_ID = SYS_CONTEXT(''SCHEMAOWNER'',''USER_ID'')';