Oracle Account

Manage your account and access personalized content. Sign up for an Oracle Account

Sign in to Cloud

Access your cloud dashboard, manage orders, and more. Sign up for a free trial

学习如何在 JEE 应用程序(从数据库到缓存)中增强行级安全性。

2007 年 7 月发布

安全性是应用程序的一个很重要的方面。用户需要进行身份验证,数据需要进行保护,以防止未经授权的访问。Oracle 为数据库中的行级安全性提供了现成的解决方案:Oracle Label Security。通过使用开源的 Oracle TopLink 作为 ORM 解决方案,您可以确保在应用程序的缓存和其他部分中维持行级安全性。本文将通过一个很简单的示例应用程序说明如何实现这一点。

示例应用程序

示例应用程序 是一个使用 HR 模式的简单 Web 应用程序。用户可以登录该应用程序并查看授权查看的所有位置。

该应用程序的模式概述显示了四个部分:

  • 数据源层。 数据源层是属于 Oracle RDBMS 的 HR 模式。在该应用程序中,我们只使用 Countries、Locations 和 LOCATION_SEQ 对象。为 LOCATIONS 表定义的策略将在下一节中进行解释。
  • 域层。 域层使用 Java、Oracle TopLink 和 EJB 会话外观在给定的域类上实现简单的操作。该功能通过本地和远程接口公开。
  • 表示层。 表示层由一个 JavaServer Faces (JSF) 页面构成,显示允许用户查看的所有位置。该页面使用基本的 HTTP 身份验证保持示例简单并使 ADF 数据绑定与域层连接。
  • 集成测试。 该组件包含域层与数据源层的集成测试。使用了域层的远程接口。
  • 图 1 示例应用程序的模式概述

    Oracle Label Security 概述

    Oracle 在数据库中提供了不同类型的访问控制。以下将分别对其进行详细说明。

    自主访问控制 (DAC)。 通过 DAC 可以将对象级权限授予数据库用户。从而授予或拒绝对整个对象的访问。例如,通过以下语句可授予数据库用户 SKING 从位置中进行选择的权限:

    grant select to hr.locations to sking; SKING 现在可以选择 LOCATIONS 表中的所有行。您可以定义角色,而不使用数据库用户。您可以将选择权限授予角色然后再将角色授予数据库用户,而不是将选择权限授予数据库用户。 create role emp_role; grant connect to emp_role; grant select to hr.locations to emp_role; grant emp_role to SKING; 细粒度访问控制 (FGAC)。 FGAC,也称为行级安全性,是一种可以根据数据内容限制访问的方法。针对此类要求的 Oracle 数据库解决方案称为 虚拟专用数据库 (VPD),这是企业版的一个特性。数据库将根据安全性策略和会话上下文动态地更改查询。除了限制返回的行之外,您还可以屏蔽列。

    可以使用存储过程创建安全性策略。此类策略通过使用存储在 Oracle 数据库中的应用程序数据的内容或上下文变量(例如用户名或 IP 地址)来限制访问。

    Oracle Label Security (OLS) 是一个企业版选件,是 VPD 的实施。使用 OLS,管理员无需编写 PL/SQL 即可创建策略。对数据的访问权限的仲裁取决于四个因素:

  • 用户会话标签
  • 会话的策略权限
  • 表的策略强制执行选项
  • 可以根据保密级别为表的每行创建标签。每个标签包含三个组件:

  • 单级别(敏感性)分级
  • 零个或多个水平间隔或类别
  • 零个或多个层次组
  • 在示例应用程序中,您将使用名为“access_locations”的策略来决定谁可以看到什么位置。您不会使用间隔或组。级别为“公开”('pub')、“机密”('conf')和“敏感”('sens')。

    可以授予用户标签授权以确定他们对带标签的行所具有的访问权限类别(读取或写入)。将标签应用到行之后,只有授权访问该标签的用户才可以查看或更改该标签。

    无论何时用户连接到数据库,用户会话的级别都与行的级别相匹配。

    OLS 支持允许授权用户跳过策略特定部分的特殊权限。在示例应用程序中,HR 是模式的拥有者,具有 FULL 权限。这允许其对受策略保护的所有数据进行完全的读取和写入访问。

    策略强制执行

    当用户写入数据时,他或她可以为该行设置标签。可以将该标签的级别设置为管理员指定范围内的任何级别。如果用户写入数据时没有指定标签,系统将使用用户的会话标签自动分配行标签。

    在示例应用程序中定义了以下用户标签:

    图 2. 带 DAC 和 FGAC 的流

    首先,数据库根据 DAC 确定用户是否具有足够的权限。如果用户可以从 hr.locations 选择数据,数据库将检查用户是否具有跳过策略的权限。如果用户没有特殊权限,则根据策略仲裁访问。如果用户具有特殊权限,则跳过仲裁并根据权限执行查询。

    示例应用程序包含一个测试脚本(在 SQL*Plus 中运行),以显示不同的用户登录并执行同一个查询时所发生的事情。

    在不同的用户下,打开 SQL*Plus 并键入以下内容,选择所有位置

    @[path-to_file]\ols_test_security_policy.sql 您将看到同一查询具有不同的结果,具体取决于连接到数据库的用户。

    Oracle TopLink 会话

    Oracle TopLink 会话是 Oracle TopLink 运行时中的通信机制。会话有不同的类型。会话包含以下组件:

  • Java 对象构建器。 Oracle TopLink 将从数据源读取的结果转换为对象并在执行写入操作时将对象转换为查询。
  • 查询机制。 会话在对象上执行所有持久性操作。
  • 连接池。 连接池是一个到单个数据源的可重用连接的集合。它可以显著提高应用程序的性能,因为它减少了与创建连接相关的开销。Oracle TopLink 可以使用由 Java 平台、企业版 (JEE) 服务器或 JDBC 驱动程序提供的内部连接池或外部连接池。
  • 共享缓存。 缓存容纳从数据库读取或写入数据库的所有对象。从服务器会话获取的所有客户端会话共享该缓存。从性能的角度来看这也是很重要的。
  • 在下面的段落中,我们将观察“常规”Java EE 应用程序(在该应用程序中没有使用 VPD 或 OLS)中的 Oracle TopLink 会话。

    读取数据。 要从数据源中读取数据,需与 Oracle TopLink 运行时进行几个交互操作:

  • 从服务器会话获取客户端会话
  • 使用查询构建器构建查询
  • 通过检查缓存读取数据,如果对象不在缓存中则从数据库中读取
  • 将结果转换为对象
  • 下表列出了读取所有国家/地区的代码: * finds all countries * @return List<Country> or an empty list if none are found public List<Country> findAllCountries() { Session session = getSessionFactory().acquireSession(); List<Country> results = (List<Country>)session.executeQuery("findAllCountries", Country.class); session.release(); results = (List<Country>)getSessionFactory().detach(results); return results; 查询在类描述符中进行定义,如下所示: <class-descriptor-query-manager> <query-manager> <descriptor-alias>Countries</descriptor-alias> <query-list> <query> <name>findAllCountries</name> <query-type> oracle.toplink.queryframework.ReadAllQuery </query-type> <cache-usage>Check Cache by Primary Key</cache-usage> <lock-mode>Do Not Acquire Locks</lock-mode> <distinct-state>Uncomputed Distinct</distinct-state> <in-memory-query-indirection-policy> Throw Indirection Exception ..etc.. </query> </query-list> </query-manager> session.executeQuery(...) 将首先尝试从共享缓存中获取国家/地区。如果对象不在缓存中,它将使用在 sessions.xml 中定义的连接从数据库中读取。

    持久保存数据。 要将数据写入数据源,需要采取类似的步骤:

  • 使用全局 JTA TX 或活动会话中的新 JTA TX 获取 UnitOfWork。
  • 在工作单元中创建一个对象。
  • 使用查询构建器和连接池中的连接将数据提交给数据库。
  • 下表显示了如何持久保存对象的示例: * saves an entity * @param entity that needs to persisted * @return Object that is persisted. public Object persistEntity(Object entity) { UnitOfWork uow = getSessionFactory().acquireUnitOfWork(); Object existingObject = uow.readObject(entity); if (existingObject != null) throw new RuntimeException("Entity already exists"); Object newInstance = uow.deepMergeClone(entity); uow.commit(); return newInstance; UnitOfWork 充当一个事务单元,并确保写入数据库的内容也会写入服务器缓存。如此,当读取从缓存中获取更改的国家/地区时会返回正确的更新。

    连接池。 通常,开发 JEE 应用程序时,使用带 Oracle TopLink 的外部连接池。这意味着您有一个连接池用于读取和写入数据。

    默认情况下,TopLink 使用内部连接池。此处有两个池:一个用于写入数据,一个用于读取数据。

    图 3 Toplink 连接池选项

    Oracle TopLink 和 VPD

    在上一节里,我们讨论了带共享缓存和连接池以提高性能的常规 Java EE 应用程序配置。但是,对于您的示例应用程序,则需要一个更加精密的解决方案。首先,您不能针对所有用户使用一个共享缓存,因为不是所有用户都有权查看所有数据。第二,数据库需要具有有关连接的用户的信息才能执行仲裁。最后,您需要控制插入数据库的新数据的安全性级别。

    因此,要将 VPD 与 Oracle TopLink 结合使用,需要配置隔离的客户端会话并使用代理身份验证。

    隔离的客户端会话。 隔离的客户端会话是提供其自己的会话缓存的客户端会话。

    图 4 隔离的客户端会话

    需要隔离使用 VPD 或 OLS 的每个表。您可以隔离整个项目或仅隔离类。可以从隔离的类引用共享的类,反之则显然不行。在示例应用程序中,您从位置引用国家/地区,而不是从国家/地区引用位置。这意味着您可以在类级别上使用隔离。隔离类有两种方法:使用工作台以声明方式或使用 Java 代码。

    在工作台中,您可以很容易地通过以下方式隔离类:
  • 在导航器中选择 Location
  • 单击 Cache 选项卡
  • 选择 isolated
  • session.xml 现在如下所示: <transactional-policy type="relational"> <descriptor-alias>Location</descriptor-alias> <refresh-cache-policy<//> <caching-policy> <cache-coordination>None</cache-coordination> <cache-isolation>Isolated</cache-isolation> </caching-policy> <query-manager type="relational"<//> <locking-policy type="relational"<//> <primary-key-policy> <primary-key-handles> <column-handle> <column-table-name>LOCATIONS</column-table-name> <column-name>LOCATION_ID</column-name> </column-handle> </primary-key-handles> </primary-key-policy> </transactional-policy> 在 Oracle JDeveloper 中,您不能使用映射编辑器配置隔离的缓存。您可以使用两种不同的方法自定义会话:使用会话 SessionEventListener 的 preLogin 方法或使用 SessionManager 的 getSession() 方法。

    要使用 preLogin 方法,您需要实现 SessionEventListener 并使用该会话注册该监听器。

    public class LocationEventListener extends SessionEventAdapter { //other methods you want to override * We isolate the Location class here. * @param event that is raised before the session is logged in. public void preLogin(SessionEvent event) { logger.info("in prelogin event"); ClassDescriptor descriptor = event.getSession().getClassDescriptor(Location.class); descriptor.setIsIsolated(true); 将类添加到映射编辑器的会话中,如下所示。 图 5 使用事件监听器配置会话

    要使用 SessionManager.getSession(...) 方法,如果在未登录的情况下获得会话,您可以将类描述符设置为 false 然后再登录。

    private Server getSession(){ Server server = (Server)sessionManager.getSession(xmlSessionConfigLoader, "hr", false); ClassDescriptor descriptor = server.getClassDescriptor(Location.class); descriptor.setIsIsolated(true); server.login(); //.... rest of code....

    自 OC4J 10.1.3 以来,有两种数据源:托管和原生。托管数据源是 OC4J 提供的 java.sql.DataSource 接口实现,充当 JDBC 驱动程序或数据源的包装。它可以参与全局事务并使用连接池。原生数据源实现 java.sql.DataSource 接口,并且由 JDBC 驱动程序供应商提供。

    在 Oracle TopLink 项目中,您可以定义不同的服务器会话。在下面的示例中,您可以看到一个包含托管数据源 (jdbc/hrDS) 和原生数据源的示例。这是在 sessions.xml 中完成的:

    <?xml version = '1.0' encoding = 'UTF-8'?> <!DOCTYPE toplink-configuration PUBLIC "-//Oracle Corp.//DTD TopLink Sessions 9.0.4//EN" "sessions_9_0_4.dtd"> <toplink-configuration> <session> <!-- managed datasource --> <name>hr</name> <project-xml>META-INF/locationMap.xml</project-xml> <session-type> <server-session<//> </session-type> <login> <datasource>jdbc/hrDS</datasource> <uses-native-sequencing>true</uses-native-sequencing> </login> <!-- etc --> </session> <session> <!-- native datasource --> <name>sking</name> <project-xml>META-INF/locationMap.xml</project-xml> <session-type> <server-session<//> </session-type> <login> <driver-class>oracle.jdbc.OracleDriver</driver-class> <connection-url>jdbc:oracle:thin:@localhost:1521:ORCL</connection-url> <platform-class>oracle.toplink.platform.database.oracle.Oracle10Platform</platform-class> <user-name>sking</user-name> <encryption-class-name>oracle.toplink.internal.security.JCEEncryptor</encryption-class-name> <encrypted-password>F21B2AE50E304BA0D81243DD794296A5</encrypted-password> </login> </session> </toplink-configuration> 可以使用会话工厂按名称获取正确的会话。以下显示了如何定义您要使用的会话。 this.sessionFactory = new SessionFactory("META-INF/sessions.xml", "hr");
    Session session = getSessionFactory().acquireSession();
    当使用托管数据源时,您需要自行添加将特定于 VPD 的 SQL 追加到查询中的逻辑。但是,还有一种更加简单的方法:使用代理身份验证。在该示例中,数据库知道所有用户,因此您可以使用代理身份验证。OC4J 10 g (10.1.3.x) 通过 Oracle JDBC 原生数据源对此进行支持。不能通过工作台或 Oracle JDeveloper 对此进行配置,您需要 Java 代码来完成实现。

    在这种情况下,我们将仅根据用户名使用代理身份验证。(有关详细的解释,请参阅 如何通过 OC4J 10 g (10.1.3) 数据源配置和使用代理身份验证 。)

    您需要采取以下步骤:

  • 更改用户: alter user ldoran grant connect through hr;
  • 通过添加以下代码获取包含代理身份验证的隔离客户端会话: * Returns an isolated client session from the server session * @return isolated client session. private Session getSession() { Server server = (Server)sessionManager.getSession(new XMLSessionConfigLoader(), HR_SESSION_CONFIG, Thread.currentThread().getContextClassLoader()); DatabaseLogin login = (DatabaseLogin)server.getLogin().clone(); login.dontUseExternalConnectionPooling(); // this also sets isLazy flag to false ConnectionPolicy policy = new ConnectionPolicy(login); policy.setShouldUseExclusiveConnection(server.getDefaultConnectionPolicy(). shouldUseExclusiveConnection()); // Set proxy properties into connection policy's login JNDIConnector connector = (JNDIConnector)login.getConnector(); login.setConnector(new OracleJDBC10_1_0_2ProxyConnector(connector.getName())); String user = getUser(); login.setProperty(PROXYTYPE, Integer.toString(OracleConnection.PROXYTYPE_USER_NAME)); login.setProperty(OracleConnection.PROXY_USER_NAME, user); return server.acquireClientSession(policy); 您可以检索从上下文登录到 Web 应用程序的用户: * Gets the Principal that logged in from the context * @return the name of the caller principal. private String getUser(){ String user = ctx.getCallerPrincipal().getName(); logger.info("user that logged in: " + user); return user;

    专用连接。 通常,在使用 VPD 和 OLS 时,您会在应用程序中使用专用连接。专用连接是由 Oracle TopLink 分配给客户端会话的,用于在会话的生命周期内读取隔离的数据并写入。它是从服务器会话的写入连接池获取的。Oracle TopLink 仍然从读取连接池中获取共享的连接,用以读取非隔离的数据,例如,在示例应用程序中获取国家/地区。

    要使用专用连接,请添加以下代码:

    //...... ConnectionPolicy policy = new ConnectionPolicy(login); policy.setShouldUseExclusiveConnection(server.getDefaultConnectionPolicy(). shouldUseExclusiveConnection()); //... rest of the code

    下一步要做什么呢?

    在本文中,您已经了解了如何通过 TopLink 在 Java EE Web 应用程序中实现 Oracle Label Security。在该示例中,您使用了 jazn 来配置 Web 应用程序中的安全性,并使用了数据库来定义您的安全性策略。该组合功能十分强大:使用了 Oracle 数据库以及 Java EE 的安全性功能。

    在实际的应用程序中,在 Oracle Internet Directory 中对二者进行定义比较合理,这样会使得用户、角色和权限更容易维护。该示例仅从数据库读取数据,没有持久保存任何数据。将数据持久保存到数据库并使用乐观锁定时,您需要为未修改行的事件添加一个处理程序。如果出现安全违规以及锁定异常,则会引发异常。如果是锁定,应用程序可以尝试重新持久保存该对象。如果是安全违规,则需要引发异常。

    祝您编程之旅充满快乐!

    Lonneke Dikmans 为 Oracle 融合中间件区域总监和 Oracle ACE,同时又是荷兰 Approach Alliance 的管理合作伙伴。她是一名架构师,专攻 SOA 和快速开发。Lonneke 自从 2000 年以来一直使用 JDeveloper,在设计、开发和部署 Java 应用程序方面经验丰富。
  •