Wildfly - set datasource password at runtime

我怕爱的太早我们不能终老 提交于 2021-02-08 03:45:57

问题


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;

...
}

回答1:


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.




回答2:


How you are access the class SubjectActions? the class access modifier is default so it is throwing error at compile time error: class EncryptedPasswordLoginModule is public, should be declared in a file named EncryptedPasswordLoginModule.java




回答3:


@javaguy this is my full class, hope it helps

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.resource.spi.security.PasswordCredential;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

/**
 * This is an Authentication module that fetches credentials from a file
 * This will be used in Cloud scenarios 
 * 
 */
public class FileLoginModule implements LoginModule {

    private static final Logger LOG = Logger.getLogger(MaterialLoginModule.class.getName());

    private Subject subject;
    private Map shared;
    private Map options;

    @Override
    public void initialize(Subject subject, CallbackHandler handler, Map<String, ?> shared, Map<String, ?> options) {
        this.subject = subject;
        this.shared = shared;
        this.options = options;

        LOG.log(Level.INFO, "Options: {0}", options);
    }

    @Override
    public boolean login() throws LoginException {

        try {
            String filePath = (String) this.options.get("creds");
            LOG.log(Level.FINE, "Fetching credentials from {0}", filePath);
            Path path = Paths.get(filePath);
            List<String> lines = Files.readAllLines(path);
            Iterator<String> it = lines.iterator();
            this.shared.put("user", it.hasNext() ? it.next() : "");
            this.shared.put("pass", it.hasNext() ? it.next().toCharArray() : null);
            return true;
        } catch (IOException e) {
            LOG.log(Level.SEVERE, e.getMessage(), e);
        }

        return false;
    }

    @Override
    public boolean commit() throws LoginException {
        subject.getPrivateCredentials().add(new PasswordCredential((String) this.shared.get("user"), (char[]) this.shared.get("pass")));
        LOG.log(Level.FINE, "Commiting...");
        return true;
    }

    @Override
    public boolean logout() throws LoginException {
        LOG.log(Level.INFO, "logging OUT {0}");
        return true;
    }

    @Override
    public boolean abort() throws LoginException {
        LOG.log(Level.INFO, "abort");
        return true;
    }
}


来源:https://stackoverflow.com/questions/26465493/wildfly-set-datasource-password-at-runtime

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!