JPA 和 EclipseLink 通常用于具有共享连接池的中间层/服务器环境。 连接池允许共享数据库连接,以避免重新连接到数据库的成本。 通常,用户登录到应用程序,但没有自己的数据库登录名,因为共享登录名用于连接池。 这是与传统的两层应用程序不同的模型,其中每个用户都有自己的数据库登录名。 大多数数据库都提供审核支持来记录更改并提供基于用户的安全性。 但是,在具有共享连接池和 Web 用户的三层环境中,这通常不起作用。
审核和安全有几种解决方案:
- 为每个应用程序用户提供一个数据库用户 ID,并为每个用户提供自己的数据库连接。
- 使用通用数据库用户 ID,并管理应用程序中的审核和安全性。
- 使用 Oracle 代理身份验证允许共享连接池和用户上下文。
为每个应用程序用户提供一个数据库用户 ID
这允许基于数据库用户的审核和安全性。 如果每个应用程序用户都有自己的数据库用户标识,则无法共享连接。 每个用户在创建其实体管理器时都需要创建新的数据库连接。
EclipseLink 通过允许将 JPA 持久性单元属性传递给 EntityManagerFactory.createEntityManager(Map) API 来提供对此模型的支持。 应用程序可以传递“javax.persistence.jdbc.user”和“javax.persistence.jdbc.password”属性,以触发要为此 EntityManager 创建的新连接。 请注意,默认情况下,此连接仅用于写入,读取仍将使用共享连接池。要强制读取也使用连接,“eclipselink.jdbc.exclusive-connection.mode”属性应设置为“始终”,但这取决于应用程序是否希望审核写入或读取。 EclipseLink 还定义了一个“eclipselink.jdbc.exclusive-connection.is-lazy”属性,用于配置是应该预先连接连接,还是仅在第一次需要时才连接。如果仅审核写入操作,则延迟连接可以避免创建新数据库连接的成本,除非发生写入操作。 默认情况下,EclipseLink 还具有共享连接池,如果读取和写入都使用用户连接,则应通过将大小设置为 0 来禁用它。 EclipseLink 仍然需要单个连接和一个有效的用户来验证数据库连接,但这只会在启动时使用一次,然后断开连接。
示例 使用用户连接的实体管理器
要仅审核,请写入:
Map properties = new HashMap(); properties.put("javax.persistence.jdbc.user", user); properties.put("javax.persistence.jdbc.password", password); properties.put("eclipselink.jdbc.exclusive-connection.mode", "Transactional"); properties.put("eclipselink.jdbc.exclusive-connection.is-lazy", "true"); EntityManager em = factory.createEntityManager(properties);
审核读取和写入:
Map properties = new HashMap(); properties.put("javax.persistence.jdbc.user", user); properties.put("javax.persistence.jdbc.password", password); properties.put("eclipselink.jdbc.exclusive-connection.mode", "Always"); properties.put("eclipselink.jdbc.exclusive-connection.is-lazy", "false"); EntityManager em = factory.createEntityManager(properties);
示例持久性.xml没有连接池
要在审核读取和写入时禁用连接池,请执行以下操作:
<persistence-unit name="default" transaction-type="RESOURCE_LOCAL"> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <properties> <property name="javax.persistence.jdbc.driver" value="oracle.jdbc.OracleDriver"/> <property name="javax.persistence.jdbc.url" value="jdbc:oracle:thin:@oracle:1521:orcl"/> <property name="javax.persistence.jdbc.user" value="guest"/> <property name="javax.persistence.jdbc.password" value="welcome"/> <property name="eclipselink.jdbc.connections.initial" value="0"/> <property name="eclipselink.jdbc.connections.min" value="0"/> <property name="eclipselink.jdbc.connections.max" value="1"/> </properties> </persistence-unit>
Java EE 和 JTA
如果使用 Java EE 和数据源,则可以以相同的方式传递数据库用户名和密码。 EclipseLink 只会在创建连接时将用户/密码传递给数据源,而不是直接传递给 JDBC。
如果使用 JEE 和 JTA 管理的实体管理器,则指定用户/密码可能会更加困难,因为实体管理器和 JDBC 连接不受应用程序控制。 持久性单元属性仍可在实体管理器上指定。 只要在实体管理器建立数据库连接之前完成此操作,这仍然有效。
在JEE实体管理器上设置用户/密码的示例
如果使用 JPA 2.0,则可以使用 setProperty API:
em.setProperty("javax.persistence.jdbc.user", user); em.setProperty("javax.persistence.jdbc.password", password); em.setProperty("eclipselink.jdbc.exclusive-connection.mode", "Always"); em.setProperty("eclipselink.jdbc.exclusive-connection.is-lazy", "false");
否则,可以使用 getDelegate API:
Map properties = new HashMap(); properties.put("javax.persistence.jdbc.user", user); properties.put("javax.persistence.jdbc.password", password); properties.put("eclipselink.jdbc.exclusive-connection.mode", "Always"); properties.put("eclipselink.jdbc.exclusive-connection.is-lazy", "false"); ((org.eclipse.persistence.internal.jpa.EntityManagerImpl)em.getDelegate()).setProperties(properties);
缓存和安全性
缺省情况下,EclipseLink 维护共享 (L2) 对象高速缓存。 这对于审核来说很好,但如果使用基于用户的安全性来阻止读取某些表/类,则可能需要为这些安全类禁用缓存。 要禁用类的共享缓存,请参阅缓存。
如果使用数据库用户来检查读取的安全性,则可以将“eclipselink.jdbc.exclusive-connection.mode”设置为“隔离”,以便仅使用用户连接读取已禁用共享高速缓存(隔离)的类。
角色
通常,不会为每个用户分配不同的安全权限,而是定义一组分配安全权限的角色 并将用户分配给一个角色。如果为每个角色创建单个应用程序数据库用户,则可以具有多个安全级别,但仍允许连接池。
在 JPA 中启用此功能的一种方法是为每个角色定义不同的连接池和持久性单元。 这将允许连接池并处理基于角色的数据库安全性,但在角色之间共享缓存时存在问题。
EclipseLink 允许单个持久性单元定义多个连接池。 然后,可以在创建实体管理器时传递连接策略,以使用实体管理器属性“eclipselink.jdbc.connection-policy”选择要使用的连接池。
可以在 EclipseLink 中使用 SessionCustomizer 和 ConnectionPool 类定义多个连接池。
使用通用数据库用户 ID,并管理应用程序中的审核和安全性
审核通常通过具有应用程序用户和单个共享数据库用户在应用程序中进行管理。 这通常是通过向所有审核表添加 AUDIT_USER 和 AUDIT_TIMESTAMP 列以及向所有审核对象添加 auditUser 和 auditTimestamp 字段来实现的。当应用程序插入或更新对象时,它将设置这些字段,它们将存储在数据库中。JPA 或 EclipseLink 事件也可用于记录审计信息,或写入单独的审计表。
EclipseLink 还支持完整的历史记录支持,这允许在镜像历史记录表中跟踪对数据库所做的所有更改的完整历史记录。
安全性也由应用程序控制,允许用户根据其应用程序用户ID或角色访问应用程序的不同部分。 数据库可用于存储用户登录信息、角色和访问权限,但这些信息只是在普通表中,与数据库用户没有任何关系,数据库不会强制执行自己的安全性。
此模型允许完全连接池,并使应用程序能够控制审核和安全性。
示例审核对象类
import javax.persistence.*; @MappedSuperclass public Class AuditedObject { public static ThreadLocal currentUser = new ThreadLocal(); @Column("AUDIT_USER"); protected String auditUser; @Column("AUDIT_TIMESTAMP"); protected Calendar auditTimestamp; public String getAuditUser() { return auditUser; } public void setAuditUser(String auditUser) { this.auditUser = auditUser; } public Calendar getAuditTimestamp() { return auditTimestamp; } public void setAuditTimestamp(Calendar auditTimestamp) { this.auditTimestamp= auditTimestamp; } @PrePersist @PreUpdate public void updateAuditInfo() { setAuditUser((String)AuditedObject.currentUser.get()); setAuditTimestamp(Calendar.getInstance()); } }
使用 EclipseLink 事件,您还可以启用应用程序审计,而无需向对象添加公共超类或额外字段。 DescriptorEvent aboutToInsert 和 aboutToUpdate 可用于向数据库写入添加列。 可以使用 DescriptorEventListener 接口或 DescriptorEventAdapter 类以及 JPA @EntityListeners注释或 xml 元素注册描述符事件。 也可以使用 DescriptorCustomizer 或 SessionCustomizer 注册它们。
import java.util.*; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.descriptors.DescriptorEventAdapter; import org.eclipse.persistence.descriptors.DescriptorEvent; import org.eclipse.persistence.config.DescriptorCustomizer; import org.eclipse.persistence.config.SessionCustomizer; public Class AuditListener extends DescriptorEventAdapter implements SessionCustomizer, DescriptorCustomizer { public static ThreadLocal currentUser = new ThreadLocal(); /** This will audit a specific class. */ public void customize(ClassDescriptor descriptor) { descriptor.getEventManager().addListener(this); } /** This will audit all classes. */ public void customize(Session session) { for (ClassDescriptor descriptor : session.getDescriptors().values()) { customize(descriptor); } } public void aboutToInsert(DescriptorEvent event) { for (String table : (List<String>)event.getDescriptor().getTableNames()) { event.getRecord().put(table + ".AUDIT_USER", (String)AuditListener.currentUser.get()); event.getRecord().put(table + ".AUDIT_TIMESTAMP", Calendar.getInstance()); } } public void aboutToUpdate(DescriptorEvent event) { for (String table : (List<String>)event.getDescriptor().getTableNames()) { event.getRecord().put(table + ".AUDIT_USER", (String)AuditListener.currentUser.get()); event.getRecord().put(table + ".AUDIT_TIMESTAMP", Calendar.getInstance()); } } }
使用 Oracle 代理身份验证允许共享连接池和用户上下文
Oracle 数据库提供了一种在现有数据库连接上设置代理用户的机制。 这允许使用共享连接池,但也为数据库提供用户上下文。
EclipseLink 通过允许在 EntityManager 上指定以下持久性单元属性来提供对 Oracle 代理身份验证的支持:
- “eclipselink.oracle.proxy-type” : oracle.jdbc.OracleConnection.PROXYTYPE_USER_NAME, PROXYTYPE_CERTIFICATE, PROXYTYPE_DISTINGUISHED_NAME
- oracle.jdbc.OracleConnection.PROXY_USER_NAME : <user_name>
- oracle.jdbc.OracleConnection.PROXY_USER_PASSWORD : <密码>
- oracle.jdbc.OracleConnection.PROXY_DISTINGUISHED_NAME
- oracle.jdbc.OracleConnection.PROXY_CERTIFICATE
- oracle.jdbc.OracleConnection.PROXY_ROLES
请注意,默认情况下,此连接仅用于写入,读取仍将使用共享连接池。要强制读取也使用连接,“eclipselink.jdbc.exclusive-connection.mode”属性应设置为“始终”,但这取决于应用程序是否希望审核写入或读取。 EclipseLink 还定义了一个“eclipselink.jdbc.exclusive-connection.is-lazy”属性,用于配置是应该预先连接连接,还是仅在第一次需要时才连接。如果仅审核写入操作,则延迟连接可以避免创建新数据库连接的成本,除非发生写入操作。
使用代理身份验证的示例实体管理器
要仅审核,请写入:
Map properties = new HashMap(); properties.put("eclipselink.oracle.proxy-type", oracle.jdbc.OracleConnection.PROXYTYPE_USER_NAME); properties.put(oracle.jdbc.OracleConnection.PROXY_USER_NAME, user); properties.put(oracle.jdbc.OracleConnection.PROXY_USER_PASSWORD, password); properties.put("eclipselink.jdbc.exclusive-connection.mode", "Transactional"); properties.put("eclipselink.jdbc.exclusive-connection.is-lazy", "true"); EntityManager em = factory.createEntityManager(properties);
审核读取和写入:
Map properties = new HashMap(); properties.put("eclipselink.oracle.proxy-type", oracle.jdbc.OracleConnection.PROXYTYPE_USER_NAME); properties.put(oracle.jdbc.OracleConnection.PROXY_USER_NAME, user); properties.put(oracle.jdbc.OracleConnection.PROXY_USER_PASSWORD, password); properties.put("eclipselink.jdbc.exclusive-connection.mode", "Always"); properties.put("eclipselink.jdbc.exclusive-connection.is-lazy", "false"); EntityManager em = factory.createEntityManager(properties);
Java EE 和 JTA
如果使用 JEE 和 JTA 管理的实体管理器,则指定代理用户/密码可能会更加困难,因为实体管理器和 JDBC 连接不受应用程序控制。 持久性单元属性仍可在实体管理器上指定。 只要在实体管理器建立数据库连接之前完成此操作,这仍然有效。
在JEE实体管理器上设置用户/密码的示例
如果使用 JPA 2.0,则可以使用 setProperty API:
em.setProperty("eclipselink.oracle.proxy-type", oracle.jdbc.OracleConnection.PROXYTYPE_USER_NAME); em.setProperty(oracle.jdbc.OracleConnection.PROXY_USER_NAME, user); em.setProperty(oracle.jdbc.OracleConnection.PROXY_USER_PASSWORD, password); em.setProperty("eclipselink.jdbc.exclusive-connection.mode", "Always"); em.setProperty("eclipselink.jdbc.exclusive-connection.is-lazy", "false");
否则,可以使用 getDelegate API:
Map properties = new HashMap(); properties.put("eclipselink.oracle.proxy-type", oracle.jdbc.OracleConnection.PROXYTYPE_USER_NAME); properties.put(oracle.jdbc.OracleConnection.PROXY_USER_NAME, user); properties.put(oracle.jdbc.OracleConnection.PROXY_USER_PASSWORD, password); properties.put("eclipselink.jdbc.exclusive-connection.mode", "Always"); properties.put("eclipselink.jdbc.exclusive-connection.is-lazy", "false"); ((org.eclipse.persistence.internal.jpa.EntityManagerImpl)em.getDelegate()).setProperties(properties);
缓存和安全性
缺省情况下,EclipseLink 维护共享 (L2) 对象高速缓存。 这对于审计来说很好,但如果使用 VPD 或基于用户的安全性来阻止读取某些表/类,则可能需要为这些安全类禁用缓存。 要禁用类的共享缓存,请参阅缓存。
如果使用数据库用户来检查读取的安全性,则可以将“eclipselink.jdbc.exclusive-connection.mode”设置为“隔离”,以便仅使用用户连接读取已禁用共享高速缓存(隔离)的类。
VPD
Oracle 虚拟专用数据库功能允许在 Oracle 数据库中实现行级别安全性。 典型的数据库安全性仅允许为每个表分配访问权限。 行级别安全性允许不同的用户访问每个表中的不同行。
EclipseLink 的 Oracle 代理身份验证可用于支持 Oracle VPD。 代理用户允许检查行级别安全性。 使用 Oracle VPD 时,禁用安全对象的共享缓存也很重要,因为这些对象不应共享。