-- Application Context
应用上下文是一个具有只读属性的内存容器,在应用程序中可以显式或者隐式的使用这些属性;
使用应用上下文非常简单,就好像是你把一个经常使用的电话号码存在电话本中,当你想使用时很容易找到它;
上下文拥有的内存区域叫一个命名空间,命名空间中有已命名的属性,每个属性都可以保存一个字符串的值;感觉像是cookie;
应用上下文命名空间标示一个应用上下文,也就是它们是互相独立的,比如有两个命名空间分别叫HRAPP和OEAPP,分别代表了人力资源(Human Resources)和订单输入(Order Entry)应用程序;
使用命名空间的意义想必都很清楚,就像c#中的命名空间,java中的包,和oracle中的schema一样,不同命名空间下可以有相同的对象或者属性;
-- Namespace
一个命名空间应该是唯一的,每一个上下文在它自己的命名空间中创建;在幻灯片中就是创建了一个hrapp的上下文;
当一个上下文被创建,就会分配相应的内存区域,并且要关联一个包;这个包是唯一被允许设置上下文属性的包;幻灯片中的dbms_session.set_context设置属性的过程只能在这个包中完成;
幻灯片中给hrapp上下文中的emp_id属性设置了一个值叫v_emp_id;
-- Using the Application Context
那么怎么使用应用上下文呢?
虽然使用应用程序上下文的主要好处是提高性能,但是上下文还可以用于其它Oracle数据库的安全功能,如虚拟专用数据库(VPD),之后会讲到;
使用应用上下文,可以通过上下文属性来完成以下功能:
1.在用户登录时授权:当用户登录时验证的用户的IP地址;
2.设置上下文属性所使用的细粒度访问控制(FGAC,也会在之后讲到):会话属性可以被用于限制用户访问的记录;比如在订单输入应用程序,用户可以通过设置与当前用户的用户号对应的上下文属性只能访问自己的订单;因为上下文设置和使用的方式,可以无需更改应用程序来实现FGAC。
3.设置在应用程序中使用的属性:在这种情况下,设置的属性作为上下文的一部分,而不是从一个表读取属性;例如,如果公司员工的数量在应用程序经常被使用,则可以创建一个包含雇员号对应的上下文属性,而不是每次都去从员工表中汇总得到;
-- Setting the Application Context
这个包一般是由应用程序或者登陆触发器调用的;包里面设置的属性一般都是经常被应用程序访问的;
从而体现出了上下文的性能,总是从内存中获取值,而不是通过SQL语句或者系统调用获得;
USERENV上下文是一个内置的特殊的上下文,USERENV里面的值经常被用于填充v$session和v$process视图;并且它对所有的应用程序都是可用的;
-- Application Context Data Sources
上下文可以根据数据的来源分类四类:
1.Built-in Context:内置的上下文叫USEREVN,主要包括几种类型的属性:(查看sys_context函数并测试)
1.客户端属性:例如客户端IP;
2.用户属性:例如代理,会话和操作系统的登录用户名等;
3.认证信息:认证类型,操作系统和语言设置;
2.Local Application Contexts:在FGAC的应用中可以查询任何数据库对象的属性,比如可以从HR.EMPLOYEES表中得到用户的名称和部门信息;也可以从一个存储过程或者函数中得到返回值;
3.Externalized Application Contexts:外部初始化应用程序上下文的特征是使用外部资源,例如OCI调用;语法为:CREATE CONTEXT external USINGext_package INITIALIZED EXTERNALLY;
4.Global Application Contexts:组织集中化的用户信息和管理使用LDAP或者活动目录,Oracle中类似的是OID(Oracle Internet Directory),应用程序上下文属性值可以存储在OID中;创建语法:CREATE CONTEXT hrgapp USING hr_g_contextINITIALIZED GLOBALLY;
-- Implement a Local Context
第一步创建的上下文要跟第二步创建的包关联,从而保证上下文的属性只能通过这个包来设置;
-- Create an Application Context
CREATE CONTEXT HRAPP USING PKG_HR_CONTEXT;
上下文都属于sys用户,可以通过dba_context数据字典查看(schema列表示包所属的架构);
设置上下文的属性只能通过两种途径:1.CREATE CONTEXT时候指定的包;2.FGAC中策略关联的函数;
使用DBMS_SESSION.SET_CONTEXT设置的属性的生命周期是:被reset了或者用户退出会话了;
OPM(Oracle Policy Manager)是一个用户管理应用上下文,FGAC策略,Oracle LabelSecurity策略图形化的接口;也可以使用EM访问(Server->Security);
-- Create a PL/SQL Package
CREATE OR REPLACE PACKAGE pkg_hr_context IS
PROCEDURE set_emp_id;
END pkg_hr_context;
CREATE OR REPLACE PACKAGE BODY pkg_hr_context IS
PROCEDURE set_emp_id IS
v_emp_id NUMBER;
BEGIN
SELECTemployee_id INTO v_emp_id FROM hr.employees WHERE email = sys_context('USERENV','SESSION_USER');
dbms_session.set_context('hrapp','emp_id', v_emp_id);
EXCEPTION
WHENno_data_found THEN
dbms_session.set_context('hrapp','emp_id', 0);
END;
END pkg_hr_context;
-- Call the Package
CREATE OR REPLACE TRIGGER tgr_hr_context_logon
AFTER logon ON DATABASE
BEGIN
pkg_hr_context.set_emp_id();
END;
尽管登陆触发器不是必须的,因为应用程序可以在任何时候初始化上下文,但是强烈推荐在用户访问数据之前设置上下文;
如果所有用户都用自己的名称登陆,那么可以用[ON DATABASE],如果都统一使用一个账户的话,可以使用[ON username.SCHEMA];
-- Read the Context Attribute
conn / AS SYSDBA
SELECT sys_context('HRAPP', 'EMP_ID') FROM dual; -- 0
CREATE USER sking IDENTIFIED BY oracle;
GRANT CONNECT TO sking;
GRANT SELECT ON hr.employees TO sking;
conn sking/oracle
SELECT sys_context('HRAPP', 'EMP_ID') FROM dual; -- 100
?可以实现部门领导查看部门员工信息的逻辑;
SELECT employee_id, first_name, phone_number, job_id, salary FROMhr.employees WHERE manager_id = sys_context('hrapp', 'emp_id');
-- SYS_CONTEXT函数,查看ONLINE Help
获得客户端IP:SELECT sys_context('USERENV', 'IP_ADDRESS') FROMdual;
获得当前会话的用户名:SELECT sys_context('USERENV', 'SESSION_USER') FROMdual;
获得客户端的用户名:SELECT sys_context('USERENV', 'OS_USER') FROM dual;
-- Application Context AccessedGlobally,可全局访问的应用上下文
默认的,应用上下文所使用的内存区域是从PGA中分配的;在许多应用程序架构中,都是由中间层应用程序帮助应用程序用户来管理连接池;用户在应用程序中验证后,然后使用一个单一的身份登录到数据库,并保持所有的连接(其实就是一个长连接);
在连接池的环境中,一般任何用户都有可能使用任何一个连接,在这种环境中,不可能由会话相关的安全应用上下文来维护应用程序的属性,因为对于每一个会话来说上下文都是私有的;同样的,应用程序没有会话模式,所以用户可以使用任何会话;
全局访问的应用上下文是一种安全的应用程序上下文,它可以在信任的会话之间共享;它为什么能够被共享?因为上下文的内存区是在SGA中分配的;中间层应用程序可以使用全局可访问的应用上下文来管理程序的安全性和全局性,因为它允许多个连接来访问一个或者多个上下文;
它通过连接的重用来提高性能,这些应用程序的上下文被初始化一次,而不用每次都要为单独的会话进行初始化;
使用DBMS_SESSION.SET_IDENTIFIER接口可以为每一个应用程序添加一个客户端标示,而每个客户端只能看到分配给它的应用程序上下文;
因为DBMS_SESSION是授予PUBLIC用户组的,允许未授权的用户访问,所以要防止恶意的调用和SQL注入;
-- How the Application ContextAccessed Globally Works
一共有9个步骤要去做,幻灯片中左边的部分是由用户来做的,右边的部分是应用程序做的;
1.启动应用服务器,并创建一个连接池;
2.用户U1通过应用程序登录数据库;
3.应用服务器为用户创建一个session;
1.使用连接池中的连接;
2.调用SET_IDENTIFIER给U1用户的会话指定一个客户端标示;
3.设置应用上下文;
4.保存客户端标示到U1的cookie中;
4.此时应用服务器可以处理U1的请求了,当应用程序读取上下文时,就不再包含客户端标示了,因为在第三步骤已经对会话设置标示了;
5.当应用完成U1的请求:
1.调用CLEAR_IDENTIFIER清除标示;
2.把连接交给连接池;
6.用户U1发起另外一个请求;
7.应用程序处理的方式就不同了,因为本地的cookie保存了当前的上下文信息:
1.客户端从cookie中读取会话的客户端标示;
2.从连接池中拿到连接后,调用SET_IDENTIFIER直接使用客户端标示;
8.用户U1登出;
9.应用程序调用CLEAR_CONTEXT来清除应用程序上下文;
--PL/SQL包和过程
主要使用DBMS_SESSION包,相关的过程有:(打开帮助文档介绍;)
SET_CONTEXT:设置上下文,并且与一个客户端关联;
SET_IDENTIFIER(client_id):设置一个用于全局上下文的标示;client_id是用于设置会话的标示符,这个标示符是任意的;因为标示符通常会被放到浏览器的cookie中,所以客户端标识符绝对不能含有可能被用来窃取用户隐私的信息;它应该是一个随意的字符串或者数字;而且client_id会被审计的;
CLEAR_IDENTIFIER:清除标示;
CLEAR_CONTEXT:清除上下文信息;
LIST_CONTEXT:列举上下文信息;
UNIQUE_SESSION_ID:返回当前连接到数据库会话的标示符,由SET_IDENTIFIED设置;
-- Implementing theApplication Context Accessed Globally
1.创建全局可访问的应用上下文;
2.修改建立会话的程序:
1.设置应用上下文;
2.设置会话客户端标示符;
3.当请求结束时清除客户端标示符;
3.修改相同会话处理接下来请求的程序:
1.从cookie中读取并设置会话的客户端标示符;
2.当请求结束时清除客户端标示符;
4.修改当结束会话清除上下文的应用程序;
-- Create the ApplicationContext Accessed Globally
DROP CONTEXT hrapp;
CREATE CONTEXT hrapp USING pkg_hr_context ACCESSED GLOBALLY;
之后的步骤需要在包中实现;因为要结合应用程序,所以无法实验;
-- Data Dictionary Views
SELECT * FROM dba_context;所有的上下信息;
SELECT * FROM dba_global_context;是dba_context的子集,只列出了全局访问的上下文;
conn sking/oracle
SELECT * FROM session_context/v$context;两个视图相同,都是列出当前会话设置的属性和值;
-- Guidelines
1.从包的外部设置上下文:会报权限不足的错误;
2.性能:如果sys_context参数是参数是常数时,它的工作原理就像一个绑定变量,使游标共享;
3.在应用程序上下文中的版本:当执行一个语句时,数据库要获得上下文的快照,保证查询过程中的一致性;版本控制不适用于全局可访问的应用上下文;
4.并行查询:如果在一个并行查询的环境中使用sys_context,会报错;
5.全局可访问的应用上下文不支持RAC;
6.验证上下文数据源的合法性:如果上下文的数据源是用户输入的,就有可能被改变导致无法访问;