引言
在一些对安全审计有较高要求的系统中,管理员需要查看每个应用程序的登录用户执行了哪些数据库操作,而通常我们应用程序在访问数据库时,都是公用同一个数据库的认证用户去获取数据库连接的,这样我们的登录用户的标识无法传递到数据库端。而很多客户是需要在数据库端能审计登录用户的操作。当然,在应用服务器端的程序里写日志,记录下每个登录用户执行了哪些操作也能达到审计的需求,但这种方式往往会有性能的开销。经过实践,本文介绍的解决途径是将用户标识通过数据库连接传递到数据库端,从而完成在数据库端的审计,这是一种较轻量级的方式。
解决方案简述
在 JDBC 4.0 之前,JDBC 规范没有提供传递用户标识的 API,我们只能通过数据库厂商提供的 API 去实现。考虑到这一需求的实用性,JDBC 4.0 为我们增加了相应的 API。目前,Oracle 11g Release 1 (11.1) 和 DB2 9.5 的 JDBC driver 都支持 JDBC 4.0 规范,但在此之前的版本中,我们只能借助于厂商提供的 API。本文介绍了使用这些 API 的一些实践,同时说明了如何在数据库端查看传递过来的用户标识。
传递用户标识的基本模式为:
数据库会话(session)
JDBC framework(或 O/R mapping 框架)如 Hibernate 和 iBatis 都提供了 session 的概念,session 是对数据库连接和事务使用的封装。一个数据库会话期间通常使用一个连接,对应一个事务。
对于纯 JDBC 来说,获得一个数据库连接就相当于开启了一个会话。
打开一个数据库会话。
设置用户标识信息。
执行一些数据库操作。
清除连接上的用户标识。
关闭数据库会话。
其中,清除连接上的标识非常重要,因为我们通常使用的数据库连接都是逻辑连接,关闭逻辑连接后其对应的物理连接 (TCP/IP 连接 ) 并未关闭,所以清除连接上的标识信息可以确保不影响别的数据库逻辑连接。
JDBC 4.0 提供的支持
Java 6 支持 JDBC 4.0 规范,在 JDBC 4.0 中提供了在数据库连接 java.sql.Connection 上传递用户信息的支持。在该接口中提供了两个方法:
void setClientInfo(String name, String value) throws SQLClientInfoException;
void setClientInfo(Properties properties) throws SQLClientInfoException;
第一个方法允许我们在 Connection 上传递三个属性:
ApplicationName:访问数据库的应用程序名称。
ClientUser: 访问数据库的用户标识,这个用户和建立数据库连接的用户是不同的。建立数据库连接的用户是被能数据库认证和被授权过的用户。
ClientHostname:访问数据库客户端的主机名。
第二个方法和第一个方法功能类似,只是将参数放到了一个 Properties 对象中。我们通常 setClientInfo(“ClientUser” , userId) 将用户标识附加在数据库连接上。使用该方法的常见模式是:
清单 1. 使用 JDBC 4.0 API 传递用户标识
Connection conn = getConnection();
conn.setClientInfo("ClientUser" , currentUserId);
//do something on the connection
conn.setClientInfo("ClientUser" , null);
conn.close();
注意,清除连接上的用户标识的方式是将标识置为空。下面我们针对两种的常用数据库类型介绍标识传递的方法。
针对 DB2 的标识传递
DB2 提供了 com.ibm.db2.jcc.DB2Connection,该类有下列方法,支持用户信息传递:
public void setDB2ClientUser(String s) throws SQLException;
public void setDB2ClientWorkstation(String s) throws SQLException;
public void setDB2ClientApplicationInformation(String s) throws SQLException;
public void setDB2ClientAccountingInformation(String s) throws SQLException;