程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java EE應用中對前端用戶的數據庫操作的審計

Java EE應用中對前端用戶的數據庫操作的審計

編輯:關於JAVA

引言

在一些對安全審計有較高要求的系統中,管理員需要查看每個應用程序的登錄用戶執行了哪些數據庫操作,而通常我們應用程序在訪問數據庫時,都是公用同一個數據庫的認證用戶去獲取數據庫連接的,這樣我們的登錄用戶的標識無法傳遞到數據庫端。而很多客戶是需要在數據庫端能審計登錄用戶的操作。當然,在應用服務器端的程序裡寫日志,記錄下每個登錄用戶執行了哪些操作也能達到審計的需求,但這種方式往往會有性能的開銷。經過實踐,本文介紹的解決途徑是將用戶標識通過數據庫連接傳遞到數據庫端,從而完成在數據庫端的審計,這是一種較輕量級的方式。

解決方案簡述

在 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;

在獲得連接後,通過上面的方法在連接上設置用戶信息,在使用完畢後通過置空來清除連接上的用戶信息。示例代碼如下:

清單 2. 使用 DB2Connection 傳遞用戶標識

DB2Connection conn ;
DriverManager.registerDriver(new com.ibm.db2.jcc.DB2Driver());
String connString = "jdbc:db2://hostname:50000/dbname" ;
conn = (DB2Connection)DriverManager.getConnection(connString, "connUser", "connPasswd");
// 上面的連接也可以從 DataSource 上獲取
conn.setDB2ClientUser(“loginUser”) ;
//do something on the connection 
conn.setDB2ClientUser(null) ;
conn.close() ;

在開放式平台上,通過下面的 DB2 命令來查看傳遞過來的用戶信息:db2 get snapshot for applications on databasealias,輸出結果示例:

TP Monitor client user ID = DB2UserID 
TP Monitor client workstation name = yourApplication 
TP Monitor client application name = clientWorkstation 
TP Monitor client accounting string = yourAccountingInfo

在主機(z/OS)上,通過 DB2 命令: -DISPLAY THREAD(*) DETAIL 來查看,輸出結果示例:

DSNV401I -DB8G DISPLAY THREAD REPORT FOLLOWS - DSNV402I -DB8G ACTIVE THREADS
-NAME ST A REQ ID AUTHID PLAN ASID TOKEN SERVER RA * 4 V2.27.1302 DB2USER DISTSERV
0042 17 V437-WORKSTATION=clientWorkstation, USERID=DB2UserID,
APPLICATION NAME=yourApplication

針對 Oracle 的標識傳遞

在 Oracle 11g Release 1 之前的版本中,Oracle JDBC driver 提供了接口 oracle.jdbc.driver.OracleConnection,通過 OracleConnection 上的兩個方法 setClientIdentifier() 和 clearClientIdentifier() 可以完成標識傳遞。OracleConnection 只能傳遞一個屬性 clientIdentifier,但通常這已經足夠。

示例如下:

清單 3. 使用 OracleConnection 傳遞用戶標識

OracleDataSource dataSource = new OracleDataSource();
dataSource.setURL("jdbc:oracle:thin:@hostname:1521:orcl");
dataSource.setUser("username");
dataSource.setPassword("passwd");
conn = (OracleConnection) dataSource.getConnection();
conn.setClientIdentifier(clientId) ;
// do something on the connection   
conn.clearClientIdentifier(clientId) ;
conn.close() ;
dataSource.close() ;

這個 client_id 傳到 oracle 後,可以通過下面 sql 語句來查看每個 session 上的用戶標識。

select client_identifier from v$session

那如何看到每個 client_id 執行的 sql 呢?需打開 oracle 的審計開關。例如可以打開對查詢語句的審計:

audit select table by session;

然後執行:

select sql_text,CLIENT_ID from dba_audit_trail where username='connectionUser'
order by EXTENDED_TIMESTAMP desc

可以列出每個用戶執行的 sql 語句。

數據源在 WebSphere 應用服務器上的情形

如果是采用 WebSphere 應用服務器上配置的數據源,則無法將數據源上獲得的連接轉化為 OracleConnection 或 DB2Connection,須采用 WAS 提供的 connection wrapper 類 com.ibm.websphere.rsadapter.WSConnection。編程模型如下:

清單 4. 使用 WSConnection 傳遞用戶標識

import com.ibm.websphere.rsadapter.WSConnection;

InitialContext ctx = new InitialContext();
DataSource ds = (javax.sql.DataSource) ctx.lookup("jbdc/mydatasource") ;
conn = ds.getConnection();
WSConnection wsconn = (WSConnection) conn ;
Properties props = new Properties();
props.setProperty(WSConnection.CLIENT_ID, clientId);

wsconn.setClientInformation(props);
//do something on the wsconn 
wsconn.setClientInformation(null); // 清除連接上的用戶信息 

WSConnection 支持下列屬性的傳遞:

WSConnection.CLIENT_ACCOUNTING_INFO

WSConnection.CLIENT_LOCATION

WSConnection.CLIENT_ID

WSConnection.CLIENT_APPLICATION_NAME

WSConnection.CLIENT_OTHER_INFO

WSConnection.OTHER_CLIENT_TYPE

和開源項目的結合

在實際大型項目中,直接通過 JDBC API 訪問數據庫比較少見,大多通過 O/R mapping 框架如 iBatis 或 Hibernate 去操縱數據庫。這些框架往往對數據庫連接進行了封裝,同時客戶的框架又經常進行了二次封裝,這使得在連接上傳遞屬性變得不太容易。下面針對 iBatis 和 Hibernate 提出了自己的一些實踐解法。

下面都是針對 JDBC 4.0 之前的 JDBC driver 的編程實踐。

在 iBatis 中傳遞連接屬性

iBatis 提供了一個接口 com.ibatis.sqlmap.client.SqlMapClient,這個接口包含了數據庫增刪改查的常用方法。很多客戶都是基於該接口的一個 wrapper 類去完成數據庫操作。但 SqlMapClient 默認的方法封裝掉了對連接的使用,即開發者無須獲得連接和釋放連接即可使用。

客戶常用的 SqlMapClient 包裝類的形式:

清單 5. 一個典型的 SqlMapClient 封裝類

public class SqlMapClientUtil {

   private SqlMapClient sqlMap ;

   public SqlMapClientUtil(SqlMapClient sqlMap) {
     this.sqlMap = sqlMap ;
   }

   public SqlMapClient getSqlMap() {
     return sqlMap ;
   }

   …
}

客戶使用這種包裝類的好處是減輕調用方對 SqlMapClient 的初始化工作,同時也可以對 SqlMapClient 做一些增強。但如果需要在連接上傳遞屬性,需要進行一些改造。改造辦法是寫一個自己的 SqlMapClient 實現,逐一實現 SqlMapClient 裡的方法。

清單 6. 一個自定制的 SqlMapClient 實現

public class MySqlMapClient implements SqlMapClient{

   SqlMapClient sqlMap ;

   public MySqlMapClient(SqlMapClient sqlMap) {
     this.sqlMap = sqlMap ;
   }

   public Object insert(String id, Object parameterObject) throws SQLException {

     Object retObj = null ;
     OracleDataSource dataSource = null ;
     OracleConnection conn = null ;
     try {

       conn = (OracleConnection)dataSource.getConnection();
       SqlMapSession session = sqlMap.openSession(conn);
       conn.setClientIdentifier("") ;
       sqlMap.setUserConnection(conn) ;
       retObj = session.insert(id, parameterObject) ;
       conn.clearClientIdentifier("") ;
       conn.commit() ;

     } catch (Exception e) {
       // TODO: handle exception 
     } finally {
       try {
         conn.close() ;
       } catch (Exception e2) {
         // TODO: handle exception 
       }
     }

     return retObj ;
   }

   //other methods … . 
}

於是將上面的 SqlMapClientUtil 重構成:

清單 7. 重構過的 SqlMapClient 封裝類

public class SqlMapClientUtil {
   private SqlMapClient sqlMap ;
   public SqlMapClient getSqlMapClient() {
     return new MySqlMapClient(sqlMap) ;
  }

}

在 Hibernate 中傳遞連接屬性

典型的使用 Hibernate 操作數據庫的編程模型如下:

清單 8. Hibernate 的典型編程模型

Session sess = factory.openSession();
Transaction tx;
try {
   tx = sess.beginTransaction();
   //do some work 
   ... 
   tx.commit();
} catch (Exception e) {
   if (tx!=null) tx.rollback();
   throw e;
} finally {
   sess.close();
}

為了能在會話內傳遞用戶標識,將上述編程模型改造成下面方式即可:

清單 9. 加進傳遞用戶標識後的 Hibernate 編程模型

Session sess = factory.openSession();
Transaction tx;
Connection conn ;
try {
   tx = sess.beginTransaction() ;
   conn = sess.connection() ;
   OracleConnection oraconn = (OracleConnection)conn ;
   // 上面連接或轉換成 DB2Connection,視數據庫而定 
   oraconn.setClientIdentifier("") ;
   //do some work 
   oraconn.clearClientIdentifier("") ;
   tx.commit();
} catch (Exception e) {
   if (tx!=null) tx.rollback();
     throw e;
} finally {
   sess.close();
}

結束語

本文源於客戶的真實場景,很多客戶在實際 Java EE 項目中都有“在數據庫端審計前端登錄用戶”的需求。本文針對幾種典型場景給出了如何傳遞用戶標識的編程實踐,並介紹了如何在數據庫端進行審計查看。希望能給相關開發者提供一些借鑒。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved