I am developing a server application using Jboss wildfly 8.1 and JPA Hibernate. The problem is, that the JPA datasource creditials have to be loaded at runtime (password). When the server starts, it connects to an encrypted storage where it retrieves password to real database. After that, it should establish connection to the real database.
I tried several things already: Lookup the datasource through JNDI and rebind it with actual DS. Lookup the entityManagerFactory through JNDI and rebind it with custom EntityManager.
but none of these work. Do you have idea how to solve it?
my config:
persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0">
<persistence-unit name="PERSISTENCE_UNIT" transaction-type="JTA">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jta-data-source>java:jboss/datasources/Datasource</jta-data-source>
...classes...
<properties>
<!-- Properties for Hibernate -->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="false"/>
<property name="hibernate.format_sql" value="false"></property>
<property name="hibernate.connection.useUnicode" value="true"/>
<property name="hibernate.connection.characterEncoding" value="UTF-8"/>
<property name="hibernate.connection.charSet" value="UTF-8"/>
<property name="org.hibernate.flushMode" value="commit" /> <!-- THIS ONE IS IMPORTANT -->
<property name="jboss.entity.manager.factory.jndi.name" value="java:/EntityManagerFactory"/>
<property name="jboss.entity.manager.jndi.name" value="java:/Manager1"/>
</properties>
</persistence-unit>
</persistence>
datasource (defined in standalone.xml):
<datasource jndi-name="java:jboss/datasources/Datasource" pool-name="DS" enabled="true" use-java-context="true">
<connection-url>jdbc:mysql://localhost:3306/repository</connection-url>
<connection-property name="useCompression">
false
</connection-property>
<connection-property name="logSlowQueries">
false
</connection-property>
<connection-property name="zeroDateTimeBehavior">
convertToNull
</connection-property>
<connection-property name="characterEncoding">
utf8
</connection-property>
<connection-property name="useUnicode">
true
</connection-property>
<connection-property name="connectionCollation">
utf8_unicode_ci
</connection-property>
<driver>mysql</driver>
<security>
<user-name>user</user-name>
<password>TO_BE_DEFINED</password>
</security>
</datasource>
accessing entity manager:
@Stateless
@Local
public class GenericDataBean {
@PersistenceContext(type=PersistenceContextType.TRANSACTION)
private EntityManager em;
...
}
A possible solution for your problem is use a security domain for the datasource.
In your case you must create custom login module responsible to load password from encrypted storage.
Your configuration should be similar to.
Datasource:
<datasource ... >
.....
<security>
<security-domain>EncryptedPassword</security-domain>
</security>
</datasource>
Security Donain:
<security-domain name="EncryptedPassword">
<authentication>
<login-module code="com.example.EncryptedPasswordLoginModule" flag="required">
<!-- list of options -->
<module-option name="username" value="theusername"/>
<module-option name="managedConnectionFactoryName" value="jboss.jca:service=LocalTxCM,name=DS"/>
</login-module>
</authentication>
</security-domain>
Login module implementation:
public class EncryptedPasswordLoginModule
extends AbstractPasswordCredentialLoginModule{
private String username;
public void initialize(Subject subject, CallbackHandler handler, Map sharedState, Map options){
super.initialize(subject, handler, sharedState, options);
username = (String) options.get("username");
if( username == null ){
throw new IllegalArgumentException("The user name is a required option");
}
}
public boolean login() throws LoginException{
if( super.login() == true )
return true;
super.loginOk = true;
return true;
}
public boolean commit() throws LoginException{
Principal principal = new SimplePrincipal(username);
SubjectActions.addPrincipals(subject, principal);
sharedState.put("javax.security.auth.login.name", username);
try{
char[] password = .... //code to load encrypted password;
PasswordCredential cred = new PasswordCredential(username, password);
cred.setManagedConnectionFactory(getMcf());
SubjectActions.addCredentials(subject, cred);
}
catch(Exception e){
throw new LoginException("Failed to load encrypted password: "+e.getMessage());
}
return true;
}
public boolean abort(){
username = null;
return true;
}
protected Principal getIdentity(){
Principal principal = new SimplePrincipal(username);
return principal;
}
protected Group[] getRoleSets() throws LoginException{
Group[] empty = new Group[0];
return empty;
}
}
maybe this can help.