1、介绍定义者(definer)权限和调用者(invoker)权限的概念
定义者权限:定义者权限PL/SQL程序单元是以这个程序单元拥有者的特权来执行它的,也就是说,任何具有这个PL/SQL程序单元执行权的用户都可以访问程序中的对象。所有具有执行权的用户都有相同的访问权限,在定义者权限下,执行的用户操作的schema为定义者,所操作的对象是定义者在编译时指定的对象。
调用者权限:调用者权限是指当前用户(而不是程序的创建者)执行PL/SQL程序体的权限。这意味着不同的用户对于某个对象具有的权限很可能是不同的,这个思想的提出,解决了不同用户更新不同表的方法。在调用者权限下,执行的用户操作的schema为当前用户,所操作的对象是当前模式下的对象。
下面证明:执行的schema不同,操作的对象也不同
创建用户tmpa,赋权:
SQL> create user tmpa identified by tmpa;
用户已创建。
SQL> grant connect,resource to tmpa;
授权成功。
创建用户tmpb,赋权:
SQL> create user tmpb identified by tmpb;
用户已创建。
SQL> grant connect ,resource to tmpb;
授权成功。
SQL>conn tmpa/tmpa
已连接。
SQL>set sqlprompt TMPA>
TMPA>create table tmptbl(str varchar2(50));
表已创建。
TMPA>insert into tmptbl values ('I''m ownered by user:tmpa');
已创建 1 行。
TMPA>commit;
提交完成。
TMPA>create or replace procedure definer_proc as
begin
for x in (select sys_context('userenv', 'current_user') current_user,
sys_context('userenv', 'session_user') session_user,
sys_context('userenv', 'current_schema') current_schema,
str
from tmptbl) loop
dbms_output.put_line('Current User: ' || x.current_user);
dbms_output.put_line('Session User: ' || x.session_user);
dbms_output.put_line('Current Schema: ' || x.current_schema);
dbms_output.put_line('Tables Value: ' || x.str);
end loop;
end;
/
过程已创建。
TMPA>create or replace procedure invoker_proc AUTHID CURRENT_USER as
begin
for x in (select sys_context('userenv', 'current_user') current_user,
sys_context('userenv', 'session_user') session_user,
sys_context('userenv', 'current_schema') current_schema,
str
from tmptbl) loop
dbms_output.put_line('Current User: ' || x.current_user);
dbms_output.put_line('Session User: ' || x.session_user);
dbms_output.put_line('Current Schema: ' || x.current_schema);
dbms_output.put_line('Tables Value: ' || x.str);
end loop;
end;
/
过程已创建。
给用户tmpb授予执行这两个过程的权限:
TMPA>set serveroutput on
TMPA>grant execute on definer_proc to tmpb;
授权成功。
TMPA>grant execute on invoker_proc to tmpb;
授权成功。
TMPA>exec definer_proc;
Current User: TMPA
Session User: TMPA
Current Schema: TMPA
Tables Value: I'm ownered by user:tmpa
PL/SQL 过程已成功完成。
TMPA>exec invoker_proc;
Current User: TMPA
Session User: TMPA
Current Schema: TMPA
Tables Value: I'm ownered by user:tmpa
PL/SQL 过程已成功完成。
可以看到,对于owner所拥有的对象,当前用户和session用户都是当前执行过程的用户;
新开一个连接,以tmpb用户登陆再执行看看:
E:\ora10g>sqlplus tmpb/tmpb
连接到:
Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - Production
With the Partitioning, OLAP and Data Mining options
SQL> set sqlprompt TMPB>
TMPB>create table tmptbl(str varchar2(50));
表已创建。
TMPB>insert into tmptbl values ('I''m ownered by user:tmpb');
已创建 1 行。
TMPB>commit;
提交完成。
TMPB>set serveroutput on
TMPB>exec tmpa.definer_proc;
Current User: TMPA
Session User: TMPB
Current Schema: TMPA
Tables Value: I'm ownered by user:tmpa
PL/SQL 过程已成功完成。
TMPB>exec tmpa.invoker_proc;
Current User: TMPB
Session User: TMPB
Current Schema: TMPB
Tables Value: I'm ownered by user:tmpb
PL/SQL 过程已成功完成。
调用非owner的过程,对于定义者权限的过程,虽然session是tmpb,但当前用户仍然是tmpa,访问的对象也是tmpa的表,而对于调用者权限的过程,当前session和用户都是当前执行过程的用户tmpb,而且访问的对象也是当前用户的对象。
执行的权限不同
在定义者(definer)权限下,当前用户的权限为角色无效情况下所拥有的权限。
在调用者(invoker)权限下,当前用户的权限为当前所拥有的权限(含角色)。
例如:仍用前文中的用户
TMPA>create or replace procedure createtbl_definer as
begin
execute immediate 'create table tmptbl2 (id number)';
end;
/
过程已创建。
TMPA>create or replace procedure createtbl_invoker AUTHID CURRENT_USER as
begin
execute immediate 'create table tmptbl2 (id number)';
end;
/
过程已创建。
首先执行定义者权限过程:
TMPA>exec createtbl_definer;
BEGIN createtbl_definer; END;
*
第 1 行出现错误:
ORA-01031: 权限不足
ORA-06512: 在 "TMPA.CREATETBL_DEFINER", line 3
ORA-06512: 在 line 1
--
由于角色无效,相当于当前用户没有了建表权限,因此创建失败,这也正是为什么过程中执行DDL语句需要显示授权的原因。
TMPA>exec createtbl_invoker;
PL/SQL 过程已成功完成。
TMPA>desc tmptbl2
名称 是否为空? 类型
----------------------------------------- -------- ----------------------------
ID NUMBER
--
执行调用者权限过程,能够成功创建!
执行的效率不同
在定义者(definer)权限下,过程被静态编译静态执行(相对而言),所执行sql语句在共享区池中是可被共享使用的
在调用者(invoker)权限下,过程静态编译,但动态执行,虽然执行的语句相同,但不同用户执行,其sql语句在共享池中并不能共享。
归根结底,正如tom所说,调用者权限体系结构的确拥有非常强大的功能,但只有当你使用得当时才能感受到其优势。
2、理解体系结构:
使用定义者权限的原因:
当遇到下面两种情况之一时,我们使用定义者权限模式。一种情况是,我们对所有的外部模式都授予相同的执行特权,并且所有的数据都在一张单独的表中;另一种情况是,应用程序有一个基于连接和会话信息的安全系统。
以定义者模式工作的oracle应用程序,通过使用一组复杂的存储PL/SQL应用程序元数据集合 和 共享库,实现了 APPS 这种单一模式下的并行操作,同时对APPLSYSPUB 和 APPLSYS 模式进行安全管理。
使用调用者权限的原因:
当处理分布式数据的时候,我们使用调用者权限。调用者权限体系结构可以使得对分布式数据的访问控制变得简单并且代价较低。
如果实际的应用适合创建分布式程序,那么我们建议使用调用者权限。应用调用者权限模式将有机会将数据隔离开,并将数据集中在公司的数据仓库中。我们发现最容易聚集数据源的方法就是在夜间运行批处理程序和计划任务。。
定义者权限的体系结构:
在定义者权限体系结构中数据像萨拉米香肠一样被切成一片一片的,每个用户只能看见数据的一小片。定义者权限模式要求将数据集中在一个概念文件中。如果有一些用户,他们只能看到数据的一个子集,那我们就需要建立一些关于用户安全级别的表格,以划分终端用户可以看到的内容。一个通用的方法就是根据会话信息来创建用户视图。
在设置之前,假定您会在PLSQL模式下对CLIENT_INFO值进行测试。
DBMS_APPLICATION_INFO包用于为连接或会话设置CLIENT_INFO值。这个包中有两个过程,它们的签名稍有不同,SET_CLIENT_INFO过程的签名是一个IN模式的 VARCHAR2变量,而READ_CLIENT_INFO过程的签名则是一个OUT模式的VARCHAR2变量。如下所示:
过程名和签名
SET_CLIENT_INFO(client_info IN VARCHAR2)
READ_CLIENT_INFO(client_info OUT VARCHAR2)
SET_CLIENT_INFO过程的作用是存储 V$SESSION.CLIENT_INFO列的信息,这列的长度是64个字符。当使用这种方法时,应该使用编码模式以获得最大的分配空间。下面的脚本说明了怎样在PL/SQL中设置和检索CLIENT_INFO值,设置完后,在PLSQL和MYAPP两种模式下测试CLIENT_INFO值。
-- Available online as part of demo_client_info1.sql file.
-- Anonymous block program to demonstrate DBMS_APPLICATION_INFO package.
DECLARE
-- Declare a test variable.
my_env VARCHAR2(1);
BEGIN
-- Read CLIENT_INFO into a variable.
DBMS_APPLICATION_INFO.READ_CLIENT_INFO(my_env);
-- Print the current value of CLIENT_INFO.
DBMS_OUTPUT.PUT_LINE('Read CLIENT_INFO value ['||my_env||'].');
-- Display message.
DBMS_OUTPUT.PUT_LINE('Set CLIENT_INFO to [1].');
-- Set the value to one.
DBMS_APPLICATION_INFO.SET_CLIENT_INFO('1');
-- Read CLIENT_INFO into a variable.
DBMS_APPLICATION_INFO.READ_CLIENT_INFO(my_env);
-- Print the current value of CLIENT_INFO.
DBMS_OUTPUT.PUT_LINE('Read CLIENT_INFO value ['||my_env||'].');
END;
/
脚本执行了如下3个操作:
● 读取V$SESSION.CLIENT_INFO的默认值或当前值,并打印在控制台上。
● 设置V$SESSION.CLIENT_INFO的值,并打印在控制台上。
● 读取V$SESSION.CLIENT_INFO修改后的值或当前值,并打印在控制台上。
只要我们还没有设置CLIENT_INFO的值,那么LSQL和MYAPP模式中这个脚本都可以显示下面的输出:
Read CLIENT_INFO value [].
Set CLIENT_INFO to [1].
Read CLIENT_INFO value [1].
注意:
如果我们没有看到V$SESSION.CLIENT_INFO是空值,断开并重新连接SQL会话。
除非用户得到允许,否则任何试图验证V$SESSION.CLIENT_INFO内容的操作都会失败,并产生下面的错误:
FROM V$SESSION
*
ERROR at line 2:
ORA-00942: table or view does not exist
如果断开连接并以SYSTEM用户或拥有DBA特权的授权用户重新连接,那么您通常不能看见V$SESSION视图中CLIENT_INFO列的值。如果您拥有DBA权限,那么通过创建一个新的会话并查询 V$SESSION视图,您可以看见自己的CLIENT_INFO信息。
另一个方法是使用SQL的USERENV 函数和dual 表,就像demo_client_info2.sql脚本中给出的那样。这个脚本通过执行DBMS_APPLICATION_INFO.SET_CLIENT_INFO过程设置CLIENT_INFO,同时使用了select语句对其进行查询。
-- Available online as part of demo_client_info2.sql file.
-- Select the default value from V$SESSION.CLIENT_INFO.
SELECT USERENV('CLIENT_INFO')
FROM dual;
-- Execute the DBMS_APPLICATION_INFO package.
EXECUTE DBMS_APPLICATION_INFO.SET_CLIENT_INFO('1');
-- Select the overridden value from V$SESSION.CLIENT_INFO.
SELECT USERENV('CLIENT_INFO')
FROM dual;
demo_client_info2.sql脚本的执行不受权限限制,执行脚本后输出如下:
USERENV('CLIENT_INFO')
----------------------------------------------------------------
<Null>
USERENV('CLIENT_INFO')
----------------------------------------------------------------
1
USERENV('CLIENT_INFO')
----------------------------------------------------------------
1
小提示:
<Null>的显示是通过SQL*Plus命令SET NULL"<Null>"产生的,这样的设置在空值返回调试时,对我们是很有帮助的。
通过上面的内容,我们已经了解了在PL/SQL和SQL中设置和检索CLIENT_INFO值的方法。下面介绍实现数据条状视图的方法。
如果您知道几个窍门并理解了发布-订阅方法(在面向对象的程序设计中称为观察者模式),那么条状表对您来说是相当简单的。
发布-订阅模式的意思是一个程序发布某个事件,其他程序会订阅这个事件以便在事件发布时读取它。对于条状视图来说,视图就是发布事件的那个程序,而能够读取视图内容的用户就是订阅者。
这些视图有两个关键窍门:它们必须在运行时验证会话信息,以及它们之间存在两个动态的相关性。一个相关性是订阅者必须在他们的会话中设置CLIENT_INFO值,另一个相关性是视图中必须存在这样一列,其值是每个数据条的一个非惟一的标识符。
在本节中我们将使用MYAPP模式以及PLSQL模式,也就是说,我们应该在MYAPP和PLSQL两种模式下连接会话。
create_striped_view1.sql脚本创建了具有一个非惟一的标识符列striping_id的视图。您应该注意到了这一列的值是非数字的。但如果在PLSQL模式下执行脚本,则会返回数字的列值和CLIENT_INFO值。
-- Available online as part of create_striped_view1.sql file.
-- Drop shared table.
DROP TABLE shared_all;
-- Create shared table.
CREATE TABLE shared_all
( shared_id NUMBER
, shared_text VARCHAR2(20 CHAR)
, striping_id VARCHAR2(10 CHAR));
-- Select the default value from V$SESSION.CLIENT_INFO.
CREATE OR REPLACE VIEW shared
SELECT shared_id
, shared_text
FROM shared_all
WHERE NVL(TO_NUMBER(striping_id),0) =
NVL(TO_NUMBER(SUBSTR(USERENV('CLIENT_INFO'),1,10)),0)
-- Insert a non-striped row.
INSERT
INTO shared_all
VALUES
( 1,'One','');
-- Insert a striped row.
INSERT
INTO shared_all
VALUES
( 2,'Two','1');
-- Insert a striped row.
INSERT
INTO shared_all
VALUES
( 3,'Three','2');
脚本执行以下任务:
● 删除并创建共享数据仓库shared_all表,并将shared_all. striping_id作为非惟一的标识符。
● 创建了一个共享视图,视图用于发布基于条纹标识符返回的动态内容。这可通过表中数据的空值函数来计算shared_all. striping_id列的内容,同时将返回值与USERE-NV函数的封闭函数集合合并。后面由里至外做了如下事情:
● 为当前会话捕获V$SESSION.CLIENT_INFO值,这个值以64位字符长的VARCHAR2类型返回。
● 使用SUBSTR函数截取V$SESSION.CLIENT_INFO的前10个字符,并转换为字符串。
● 使用TO_NUMBER函数将被解析的字符串转换为数字。
● 使用NVL函数检查数字是否为空。
● 将条状视图的select、insert、update和delete权限授予MYAPP模式。
● 在shared_all表插入3行值以适应不同的条状标准。
● 使用下面select语句返回表中的所有值。
COL shared_text FORMAT A11
COL shared_id FORMAT A11
SELECT *
FROM shared_all;
在PLSQL模式下运行脚本,返回结果如下:
SHARED_ID SHARED_TEXT STRIPING_ID
---------- ----------- ---------------
1 One <Null>
2 Two 1
3 Three 2
现在我们已经建立发布-订阅模式的发布方。现在转换到MYAPP模式的会话,运行下面的subscribing.sql脚本:
-- Available online as part of subscribing.sql file.
-- Drop synonym to the striped view.
DROP SYNONYM shared;
-- Create synonym to the striped view.
CREATE SYNONYM shared FOR plsql.shared;
-- Select the default value from V$SESSION.CLIENT_INFO.
SELECT USERENV('CLIENT_INFO')
FROM dual;
-- Format column value returns.
COL shared_id FORMAT 990
COL shared_text FORMAT A20
COL userenv_col FORMAT A10
-- Select the non-striped values from the view.
SELECT shared_id
, shared_text
, USERENV('CLIENT_INFO') userenv_col
FROM shared;
-- Execute the DBMS_APPLICATION_INFO package.
EXECUTE DBMS_APPLICATION_INFO.SET_CLIENT_INFO('1');
COL shared_id FORMAT 990
COL shared_text FORMAT A20
COL userenv_col FORMAT A10
-- Select the overridden value from V$SESSION.CLIENT_INFO.
SELECT shared_id
, shared_text
, USERENV('CLIENT_INFO') userenv_col
FROM shared;
脚本执行以下任务:
● 删除并创建条状shared视图的同义词。
● 验证会话的CLIENT_INFO值是空。
● 将CLIENT_INFO值设为1。
● 设置输出的格式,避免在SQL*Plus中输出超过一行。
● 查询条状shared视图和CLIENT_INFO值。
您能看到它映射插入表中的第二行数据。
注意:
如果在早些时候已经设置了CLIENT_INFO值,那我们应该清除这些设置。使用EXECUTEdbms_application_info.set_client_info(NULL)语句重新设置CLIENT_INFO值的默认空值。
使用条状化的原因:
我们需要理解的是为什么条状化是非常重要的,这对正确使用Oracle应用程序至关重要。它的重要性在于统一的定义者权限模式中,使用条状化建立的概念视图可以限制用户访问。正如您所看到的那样,这项技术有助于在集中式的数据格式中建立功能强大的应用程序。我们得感谢Oracle通过应用程序的划分来实现这项技术,从而解决了在同一数据库中存在多个组织的问题。
下面的输出没有设置CLIENT_INFO值,因为表中的第一行不是条状化的:
SHARED_ID SHARED_TEXT USERENV_CO
--------- -------------------- ----------
1 One <Null>
因为shared_all表的第2行记录的非惟一的条状标示符的值等于您的CLIENT_INFO值,所以在设置CLIENT_INFO值后应该返回下列输出:
SHARED_ID SHARED_TEXT USERENV_CO
--------- -------------------- ----------
2 Two 1
注意:
Oracle应用程序必须使用定义者权限体系结构才能生效,在会话连接的过程中会保存几个数值,这些数值用于为多组织、多币种报表(multiple reporting currencies)以及应用程序体系结构的其他方面保留条状化。
此种实现的一个替换方法是定义条状表中的数据类型为数字。这避免了在共享视图中使用TO_NUMBER函数处理非惟一的条状化列。在create_striping_view2.sql脚本中应用了PLSQL模式创建视图,但创建视图的方法有所改变,就像下面这样:
-- Available online as part of create_striped_view2.sql.
-- Build a striped view.
CREATE OR REPLACE VIEW shared AS
SELECT shared_id
, shared_text
FROM shared_all
WHERE NVL(striping_id,0) =
NVL(TO_NUMBER(SUBSTR(USERENV('CLIENT_INFO'),1,10)),0);
小提示:
我们可以通过连接同一模式清除CLIENT_INFO。例如,如果当前是MYAPP用户,那么键入CONNECTION MYAPP/MYAPP命令,我们就可以清除CLIENT_INFO值以便重新测试示例的代码。
注意:
Oracle应用程序使用数据表中的ORG_ID列,并加上后缀_ALL来条状化子系统数据。Oracle实现条状化的方法就像旧的车轮,像应付账或者总账这样的子系统在辐条的末端。子系统授予了中心代码仓库select、insert、update和delete权限。子系统之间的所有接口都要经过中心代码仓库。
现在我们已经学习了创建和实现定义者权限的方法,下面将介绍调用者权限方法的创建和实现。
调用者权限体系结构中的数据可能会也可能不会分切成片。调用者权限体系结构提供了两种类型的实现策略。一种是支持处理分布式数据的方法,另一种方法则支持处理集中式数据,它非常类似于前面讨论的使用条状表来实现定义者权限体系结构的方法。
使用调用者权限体系结构的原因:
在运行处理分布式数据的应用程序时,您需要知道使用调用者权限体系结构的方法。我们在利用相同的代码仓库时,总是使用调用者权限体系结构来确保分布式数据的完整性。调用者体系结构为建立功能强大的应用程序提供了保证。
有两个通用原则适用于所有实现策略。一个原则是遵循一个通用命名模式控制数据仓库的集中,另一个原则是只保存应用程序的单一副本来控制代码仓库的集中。我们只需要注意分布式的解决方案,因为定义者权限通常都是使用条状化方法。
如果因为某些原因如距离太远、资源或生产能力不足,不能实现分布式系统,那么调用者权限体系结构能够支持实现完全的分布式方法。对于Oracle的数据库的分布式计算来说,这是一个廉价的工具。
调用者权限体系结构能支持同一数据库或分布式数据库中的多个数据集共享单一的代码仓库。下面的插图4-2说明了调用者权限体系结构:
图4-2 调用者权限体系结构
在上面的图中,4个数据仓库共享一个代码仓库,而示例代码简化可了这种体系结构,只使用一个MYAPP模式的数据仓库,另外,示例中的代码仓库是PLSQL模式的。
create_invoker_package.sql脚本创建了data_store样表,同时允许用户向data_store表中插入数据,在PLSQL模式下运行下面的代码:
-- Available online as part of create_striped_view2.sql.
-- Create local table.
CREATE TABLE data_store
( data_id NUMBER
, data_text VARCHAR2(20 CHAR));
-- Create the data management package.
CREATE OR REPLACE PACKAGE data_management
AUTHID CURRENT_USER AS
-- Define the insert_data procedure.
PROCEDURE insert_data
( data_id NUMBER
, data_text VARCHAR2);
END data_management;
/
-- Create the data management package body.
CREATE OR REPLACE PACKAGE BODY data_management AS
-- Implement the insert_data procedure.
PROCEDURE insert_data
( data_id NUMBER
, data_text VARCHAR2) IS
BEGIN
-- Insert into the table.
INSERT
INTO data_store
VALUES
( data_id
, data_text );
END insert_data;
END data_management;
/
GRANT EXECUTE ON data_management TO MYAPP;
/
脚本执行以下任务:
● 删除并创建本地表data_store。
● 定义data_management包的包规格,包中含有一个过程insert_data。
● 创建包主体,并实现insert_data过程。
● 授予MYAPP模式data_management包的执行特权。
我们已经完成了代码仓库的安装,现在开始安装数据仓库,这需要2到3个步骤,如下所示:
(1) 创建指向PLSQL.DATA_MANAGEMENT包的同义词。
(2) 创建一个表,表的名称应与它的调用者权限模式的名称相同,或者另取一个名称。
(3) 创建一个视图,如果表有另外一个名称,那么视图名称应和调用者权限模式中目标表的名称相同。您可以使用下面的create_invoker_data_store.sql脚本配置MYAPP数据存储模式,在MYAPP模式下运行如下脚本:
-- Available online as part of create_invoker_data_store.sql.
CREATE SYNONYM data_management FOR plsql.data_management;
-- Create local table.
CREATE TABLE myapp_data_store
( data_id NUMBER
, data_text VARCHAR2(20 CHAR));
-- Create a local view that mirrors the code repository.
CREATE OR REPLACE VIEW data_store AS
SELECT *
FROM myapp_data_store;
-- Call the invoker's-right stored package.
EXECUTE data_management.insert_data(1,'One');
-- Use SQL*Plus to format the output.
COL data_text FORMAT A20
-- Query the local MYAPP_DATA_STORE table.
SELECT data_id
, data_text
FROM myapp_data_store;
该脚本执行以下任务:
● 删除并创建plsql.data_management包的本地同义词。
● 定义本地表myapp_data_store,表的名称与它在调用者权限模式中使用的名称不相同。这就要求data_store视图的名称和PLSQL模式中的表名相同。
● 执行plsql.data_management.insert_data过程,通过本地data_store视图向myapp_data_store表中插入CURRENT_USER对象中的一行记录。
● 设置myapp_data_store.data_text列的格式,然后查询本地表。
查询返回下面的结果:
DATA_ID DATA_TEXT
---------- --------------------
1 One
现在我们已经了解了调用者权限的体系结构,它与定义者体系结构在某些地方有明显的区别。下面将对这两种体系结构进行比较和对照。