开发访问数据库的Web应用程序时,大多数开发人员要设置一个连接池(一批预先打开的由中间层保持的数据库会话期)来提高连接速度。应用程序从连接池获得连接,要比为每一个用户请求创建新的连接快得多。
连接池的缺点是每个用户要作为一个单一的、高级权限数据库账户向数据库注册。尽管Web用户通常是用他们唯一的身份向应用程序注册,但只要数据库已经被连接,则所有的系统用户就都是匿名的。
Oracle数据库在安全性方面有很好的声誉,因而利用它的重要安全性功能来执行你的应用程序安全性政策是非常适宜的。假如已知用户身份,该数据库就能够进行审计并实施基于角色的和细粒度化的访问控制,包括虚拟专用数据库(Virtual Private Database,VPD)。
Oracle至少提供两种在所有各层(tiers)保持用户身份的方法:代理认证和应用程序上下文参数CLIENT_IDENTIFIER。
代理认证
代理认证使中间层能够用"普通(generic)"或"应用程序(application)"账户对数据库的访问进行认证,然后即代表真实的用户建立轻便会话。一个代理会话可以通过提交用户的辨认名(Distinguished Name (DN)),即一个x.509证书,或全局唯一用户名来建立。
例如,为了答应用户Kyle通过具有角色admin的中间层(它作为用户webapp保持一个连接池)连接到数据库,数据库治理员首先授予如下的权限:ALTER USER Kyle GRANT CONNECT THROUGH webapp WITH ROLE admin;
接着,该应用程序代码(在这里是一个servlet)翻译从Web接收到的用户名并建立如下所示的代理会话:
String userName = request.getRemoteUser();
InitialContext initial =
new InitialContext();
OracleOCIConnectionPool ds =
(OracleOCIConnectionPool)
initial.lookup("jdbc/OracleOciDS");
oracle.jdbc.OracleConnection conn = null;
Properties p = new Properties();
p.setProperty(PROXY_USER_NAME, username);
conn = ds.getProxyConnection(
PROXYTYPE_USER_NAME, p);
代理认证答应使用包括脚色在内的所有数据库安全性功能,但是它要求你设置真实的数据库用户(数据库或企业)。
使用CLIENT_IDENTIFIER
但是,假定你有成百上千的用户,而你不想为每个用户都设立一个Oracle数据库或企业用户。那么你也可以通过使用应用程序上下文将用户身份从中间层传递到该数据库。
应用程序上下文是一组可用于数据库会话的名字/值对。Oracle9i有预定义的应用程序上下文名字空间USERENV,它含有用户会话信息,包括预定义的属性CLIENT_IDENTIFIER。这一属性通常用作从全局应用程序上下文选取值的会话标识符,但是我们将稍加变更,将它用于保持Web用户标识符。
从servlet或Enterprise JavaBeans (EJB)会话bean,应用程序代码将从Web请求检索用户名,然后调用内部PL/SQL过程设置CLIENT_IDENTIFIER:
String userName = request.getRemoteUser();
...(set up the JDBC connection)...
PreparedStatement ps = conn.prepareCall(
"begin dbms_session.set_identifier(?);
end;");
ps.setString(1, username);
ps.execute();
SELECT sys_context('userenv', 'client_identifier') FROM dual;
使用这种方法没有使用代理认证那样安全。因为任何人都能设置这个上下文值,你必须借助额外的安全性措施,如细粒度的访问控制和安全的应用程序角色等。你的应用程序还应在将数据库连接返回给连接池之前清除CLIENT_IDENTIFIER属性。